import { spawnSync } from 'node:child_process'; import { existsSync } from 'node:fs'; import { copyFile, mkdir, mkdtemp, readFile, rm, writeFile, } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; const repositoryRoot = resolve(import.meta.dirname, '..'); const schedulerToken = 'scheduler-token-with-at-least-32-characters'; const infisicalStub = `#!/bin/sh set -eu case "\${1:-}" in login) printf '%s\n' "$@" > "$STUB_LOGIN_LOG" if [ "\${INFISICAL_UNIVERSAL_AUTH_CLIENT_ID:-}" != "$STUB_EXPECTED_CLIENT_ID" ] || \ [ "\${INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET:-}" != "$STUB_EXPECTED_CLIENT_SECRET" ]; then printf '%s\n' 'credentials=incorrect' >> "$STUB_LOGIN_LOG" exit 26 fi printf '%s\n' 'credentials=ok' >> "$STUB_LOGIN_LOG" case "\${STUB_LOGIN_RESULT:-success}" in fail) exit 23 ;; empty) exit 0 ;; *) printf '%s\n' stub-access-token ;; esac ;; export) [ "\${INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN:-}" = stub-access-token ] || exit 27 secret_path= output_file= for argument in "$@"; do case "$argument" in --path=*) secret_path=\${argument#--path=} ;; --output-file=*) output_file=\${argument#--output-file=} ;; esac done [ -n "$secret_path" ] && [ -n "$output_file" ] || exit 28 printf '%s|%s\n' "$secret_path" "$output_file" >> "$STUB_EXPORT_LOG" case "$secret_path" in /app) cp "$STUB_APP_EXPORT" "$output_file" ;; /shared/scheduler) cp "$STUB_SHARED_EXPORT" "$output_file" ;; *) exit 29 ;; esac ;; *) exit 25 ;; esac `; const environmentCapture = `#!/bin/sh env > "$CAPTURE_ENV_PATH" `; const gosuStub = `#!/bin/sh set -eu [ "\${1:-}" = shd-runtime ] || exit 30 printf '%s\n' 'target=shd-runtime' > "$STUB_GOSU_LOG" shift exec "$@" `; const idStub = `#!/bin/sh if [ "\${1:-}" = -u ] && [ "\${2:-}" = shd-runtime ]; then printf '%s\n' 10001 exit 0 fi exec /usr/bin/id "$@" `; const chownStub = `#!/bin/sh exit 0 `; describe('container runtime startup', () => { let temporaryRoot: string; let clientIdFile: string; let clientSecretFile: string; let loginLog: string; let exportLog: string; let gosuLog: string; let capturedEnvironment: string; let appExport: string; let sharedExport: string; beforeEach(async () => { temporaryRoot = await mkdtemp(join(tmpdir(), 'shd-runtime-start-')); const deployDirectory = join(temporaryRoot, 'deploy'); const scriptsDirectory = join(temporaryRoot, 'scripts'); await mkdir(deployDirectory); await mkdir(scriptsDirectory); await Promise.all([ copyFile( join(repositoryRoot, 'deploy/container-entrypoint.sh'), join(deployDirectory, 'container-entrypoint.sh'), ), copyFile( join(repositoryRoot, 'scripts/runtime-launcher.mjs'), join(scriptsDirectory, 'runtime-launcher.mjs'), ), copyFile( join(repositoryRoot, 'scripts/runtime-environment.js'), join(scriptsDirectory, 'runtime-environment.js'), ), ]); clientIdFile = join(temporaryRoot, 'client-id'); clientSecretFile = join(temporaryRoot, 'client-secret'); loginLog = join(temporaryRoot, 'login.log'); exportLog = join(temporaryRoot, 'export.log'); gosuLog = join(temporaryRoot, 'gosu.log'); capturedEnvironment = join(temporaryRoot, 'child.env'); appExport = join(temporaryRoot, 'app-export.json'); sharedExport = join(temporaryRoot, 'shared-export.json'); await Promise.all([ writeFile(clientIdFile, 'universal-client-id\n', { mode: 0o600 }), writeFile(clientSecretFile, 'universal-client-secret\n', { mode: 0o600 }), writeFile(join(temporaryRoot, 'infisical'), infisicalStub, { mode: 0o755, }), writeFile( join(temporaryRoot, 'capture-environment'), environmentCapture, { mode: 0o755, }, ), writeFile(join(temporaryRoot, 'gosu'), gosuStub, { mode: 0o755 }), writeFile(join(temporaryRoot, 'id'), idStub, { mode: 0o755 }), writeFile(join(temporaryRoot, 'chown'), chownStub, { mode: 0o755 }), writeExport(appExport, [ { key: 'APP_ORIGIN', value: 'https://exported.invalid' }, { key: 'DISCORD_CLIENT_ID', value: 'exported-discord-client' }, { key: 'DISCORD_CLIENT_SECRET', value: 'exported-discord-secret' }, ]), writeExport(sharedExport, [ { key: 'SCHEDULER_TOKEN', value: schedulerToken }, ]), ]); }); afterEach(async () => { await rm(temporaryRoot, { recursive: true, force: true }); }); function runtimeEnvironment( scope: 'app' | 'scheduler', ): Record { return { PATH: `${temporaryRoot}:${process.env.PATH ?? ''}`, INFISICAL_PROJECT_ID: 'project-id', INFISICAL_ENVIRONMENT: 'staging', INFISICAL_RUNTIME_SCOPE: scope, INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_FILE: clientIdFile, INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET_FILE: clientSecretFile, INFISICAL_UNIVERSAL_AUTH_CLIENT_ID: 'bootstrap-client-id', INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET: 'bootstrap-client-secret', INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN: 'bootstrap-access-token', INFISICAL_TOKEN: 'bootstrap-token', TOKEN: 'generic-bootstrap-token', STUB_EXPECTED_CLIENT_ID: 'universal-client-id', STUB_EXPECTED_CLIENT_SECRET: 'universal-client-secret', STUB_LOGIN_LOG: loginLog, STUB_EXPORT_LOG: exportLog, STUB_GOSU_LOG: gosuLog, STUB_APP_EXPORT: appExport, STUB_SHARED_EXPORT: sharedExport, CAPTURE_ENV_PATH: capturedEnvironment, SHD_BUILD_NUMBER: '42', SHD_COMMIT_SHA: 'abc123', APP_ORIGIN: 'https://parent.invalid', SCHEDULER_URL: 'https://parent.invalid/scheduler', BASH_ENV: '', CDPATH: temporaryRoot, ENV: '', IFS: 'hostile-ifs', LD_LIBRARY_PATH: temporaryRoot, LD_PRELOAD: '', NODE_OPTIONS: '--no-warnings', NODE_PATH: temporaryRoot, PYTHONPATH: temporaryRoot, }; } function start(environment: Record) { return spawnSync( '/bin/sh', ['./deploy/container-entrypoint.sh', 'capture-environment'], { cwd: temporaryRoot, env: environment, encoding: 'utf8', }, ); } it('exports app and shared secrets into a scrubbed app child environment', async () => { const result = start(runtimeEnvironment('app')); expect(result.status, result.stderr).toBe(0); expect(await exportPaths()).toEqual(['/app', '/shared/scheduler']); expect(await readFile(loginLog, 'utf8')).toBe( 'login\n--method=universal-auth\n--silent\n--plain\ncredentials=ok\n', ); const childEnvironment = parseEnvironment( await readFile(capturedEnvironment, 'utf8'), ); expect(childEnvironment).toMatchObject({ APP_ORIGIN: 'https://stage.shd.build', DISCORD_CLIENT_ID: 'exported-discord-client', DISCORD_CLIENT_SECRET: 'exported-discord-secret', ORIGIN: 'https://stage.shd.build', PASSKEY_RP_ID: 'shd.build', SCHEDULER_TOKEN: schedulerToken, SHD_BUILD_NUMBER: '42', SHD_COMMIT_SHA: 'abc123', TDR_AVATAR_STORAGE_PATH: '/app/data/avatars', TDR_DATABASE_PATH: '/app/data/shd-build-staging.db', TDR_GAME_ICON_STORAGE_PATH: '/app/data/game-icons', }); for (const name of removedEnvironmentNames) { expect(childEnvironment).not.toHaveProperty(name); } await expectSecretFilesRemoved(); await expectRootPrivilegeDrop(); }); it('exports only shared secrets into the scheduler child environment', async () => { const result = start(runtimeEnvironment('scheduler')); expect(result.status, result.stderr).toBe(0); expect(await exportPaths()).toEqual(['/shared/scheduler']); expect( parseEnvironment(await readFile(capturedEnvironment, 'utf8')), ).toMatchObject({ SCHEDULER_TOKEN: schedulerToken, SCHEDULER_URL: 'http://app:3000/api/internal/scheduler', }); await expectSecretFilesRemoved(); await expectRootPrivilegeDrop(); }); it('fails closed on an unknown secret before launching the child', async () => { await writeExport(appExport, [ { key: 'DISCORD_CLIENT_ID', value: 'exported-discord-client' }, { key: 'UNKNOWN_SECRET', value: 'must-not-reach-child' }, ]); const result = start(runtimeEnvironment('app')); expect(result.status).not.toBe(0); expect(result.stderr).toContain( 'Infisical secret UNKNOWN_SECRET is not allowed in this runtime scope.', ); expect(existsSync(capturedEnvironment)).toBe(false); await expectSecretFilesRemoved(); }); it('fails closed when a credential file setting is missing', () => { const environment = runtimeEnvironment('app'); delete environment.INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_FILE; const result = start(environment); expect(result.status).not.toBe(0); expect(result.stderr).toContain( 'INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_FILE is required', ); expect(existsSync(exportLog)).toBe(false); expect(existsSync(capturedEnvironment)).toBe(false); }); it('fails closed when either credential is empty', async () => { for (const credentialFile of [clientIdFile, clientSecretFile]) { await writeFile(credentialFile, ''); const result = start(runtimeEnvironment('app')); expect(result.status).not.toBe(0); expect(result.stderr).toContain('credential files must not be empty'); expect(existsSync(exportLog)).toBe(false); expect(existsSync(capturedEnvironment)).toBe(false); await writeFile( credentialFile, credentialFile === clientIdFile ? 'universal-client-id\n' : 'universal-client-secret\n', ); } }); it.each(['fail', 'empty'])( 'fails closed when login returns %s', (loginResult) => { const result = start({ ...runtimeEnvironment('app'), STUB_LOGIN_RESULT: loginResult, }); expect(result.status).not.toBe(0); expect(existsSync(exportLog)).toBe(false); expect(existsSync(capturedEnvironment)).toBe(false); }, ); it('fails closed on a malformed export and removes every secret file', async () => { await writeFile(appExport, '{not-json'); const result = start(runtimeEnvironment('app')); expect(result.status).not.toBe(0); expect(result.stderr).toContain( 'Infisical returned an unreadable secret export.', ); expect(existsSync(capturedEnvironment)).toBe(false); await expectSecretFilesRemoved(); }); async function exportPaths() { return (await exportRecords()).map(([path]) => path); } async function expectSecretFilesRemoved() { for (const [, file] of await exportRecords()) { expect(existsSync(file)).toBe(false); } } async function exportRecords() { return (await readFile(exportLog, 'utf8')) .trim() .split('\n') .map((line) => line.split('|')); } async function expectRootPrivilegeDrop() { if (typeof process.getuid === 'function' && process.getuid() === 0) { expect(await readFile(gosuLog, 'utf8')).toBe('target=shd-runtime\n'); } else { expect(existsSync(gosuLog)).toBe(false); } } }); const removedEnvironmentNames = [ 'BASH_ENV', 'CDPATH', 'ENV', 'IFS', 'INFISICAL_ENVIRONMENT', 'INFISICAL_PROJECT_ID', 'INFISICAL_RUNTIME_SCOPE', 'INFISICAL_TOKEN', 'INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN', 'INFISICAL_UNIVERSAL_AUTH_CLIENT_ID', 'INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_FILE', 'INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET', 'INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET_FILE', 'LD_LIBRARY_PATH', 'LD_PRELOAD', 'NODE_OPTIONS', 'NODE_PATH', 'PYTHONPATH', 'TOKEN', ]; function writeExport( file: string, entries: Array<{ key: string; value: string }>, ) { return writeFile(file, `${JSON.stringify(entries)}\n`); } function parseEnvironment(value: string) { return Object.fromEntries( value .trim() .split('\n') .map((line) => { const separator = line.indexOf('='); return [line.slice(0, separator), line.slice(separator + 1)]; }), ); }