import { test } from 'node:test' import assert from 'node:assert/strict' import { randomUUID, generateKeyPairSync } from 'node:crypto' import { execFileSync, spawnSync } from 'node:child_process' import http from 'node:http' const docker = (...args) => execFileSync('docker', args, { encoding: 'utf8', timeout: 40_000 }).trim() test('real Nix web/auth images are isolated, read-only, and preserve broker sessions across auth replacement', { timeout: 90_000 }, async () => { const webImage = process.env.REAMS_WEB_IMAGE, authImage = process.env.REAMS_AUTH_IMAGE assert.ok(webImage && authImage) const id = randomUUID(), network = `split-net-${id}`, database = `split-db-${id}`, auth = `split-auth-${id}`, web = `split-web-${id}`, secrets = `split-secrets-${id}` const build = JSON.parse(docker('run', '--rm', '--entrypoint', 'node', webImage, '-e', 'console.log(require("fs").readFileSync(process.env.ATPROTO_DIST_DIR+"/deployment-config.json","utf8"))')) const hostname = new URL(build.clientId).host const key = generateKeyPairSync('ec', { namedCurve: 'P-256' }).privateKey.export({ type: 'pkcs8', format: 'pem' }) try { docker('network', 'create', network); docker('volume', 'create', secrets) execFileSync('docker', ['run', '-i', '--rm', '--user', '0:0', '--mount', `type=volume,src=${secrets},dst=/run/secrets`, '--entrypoint', 'node', authImage, '-e', 'const fs=require("fs");fs.writeFileSync("/run/secrets/key.pem",fs.readFileSync(0),{mode:0o400});fs.chownSync("/run/secrets/key.pem",1000,1000)'], { input: key }) docker('run', '-d', '--rm', '--name', database, '--network', network, '--network-alias', 'database', '-e', 'POSTGRES_PASSWORD=fixture-only', '-e', 'POSTGRES_DB=auth_test', 'postgres:17-bookworm@sha256:639ab7ceb90e13123085b741fb31ef493fba25463002f6da665352e7b534b652') await ready(async () => spawnSync('docker', ['exec', database, 'pg_isready', '-h', '127.0.0.1', '-U', 'postgres'], { stdio: 'ignore' }).status === 0) const common = ['--network', network, '--read-only', '--cap-drop=ALL', '--security-opt=no-new-privileges', '--stop-timeout', '120', '--tmpfs', '/tmp:rw,nosuid,nodev'] docker('run', '-d', '--name', auth, '--network-alias', 'auth-service', ...common, '--mount', `type=volume,src=${secrets},dst=/run/secrets,readonly`, '-e', 'DATABASE_URL=postgres://postgres:fixture-only@database:5432/auth_test', '-e', `OAUTH_CLIENT_ID=${build.clientId}`, '-e', `OAUTH_REDIRECT_URI=${build.redirectUri}`, '-e', `OAUTH_SCOPE=${build.scope}`, '-e', `OAUTH_BASE_SCOPE=${build.scope}`, '-e', 'OAUTH_SIGNING_KEY=/run/secrets/key.pem', '-e', 'OAUTH_KID=fixture', authImage) await ready(async () => spawnSync('docker', ['exec', auth, 'node', '-e', "fetch('http://127.0.0.1:4173/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"], { stdio: 'ignore' }).status === 0) docker('run', '-d', '--name', web, ...common, '-p', '127.0.0.1::4173', '-e', 'REAMS_AUTH_UPSTREAM_URL=http://auth-service:4173', webImage) const port = docker('port', web, '4173/tcp').split(':').at(-1) await ready(async () => (await request(port, '/healthz', hostname)).status === 200) const metadata = await request(port, '/oauth-client-metadata.json', hostname) assert.equal(metadata.status, 200) assert.equal(JSON.parse(metadata.body).client_id, build.clientId) assert.ok(JSON.parse(metadata.body).redirect_uris.includes(build.redirectUri)) assert.ok(build.scope.split(' ').every(value => JSON.parse(metadata.body).scope.split(' ').includes(value))) const effective = JSON.parse(docker('exec', auth, 'node', '-e', "fetch('http://127.0.0.1:4173/healthz').then(r=>r.text()).then(console.log)")) assert.equal(effective.loginScope, build.scope) assert.equal(effective.clientId, build.clientId) assert.equal(effective.redirectUri, build.redirectUri) assert.equal((await request(port, '/oauth-client-metadata.json', 'wrong.test')).status, 404) assert.equal(docker('exec', web, 'node', '-e', 'try {require.resolve("pg");process.exit(1)} catch{}; if(require("fs").existsSync("/app/src/server/oauth-broker.mjs")||process.env.DATABASE_URL||process.env.OAUTH_SIGNING_KEY)process.exit(1);console.log("isolated")'), 'isolated') const session = await request(port, '/oauth/broker/session', hostname) assert.equal(session.status, 200) const cookie = session.headers['set-cookie'][0].split(';')[0], csrf = JSON.parse(session.body).csrf_token docker('stop', '--time', '120', auth) assert.equal(JSON.parse(docker('inspect', auth))[0].State.ExitCode, 0) docker('start', auth) await ready(async () => spawnSync('docker', ['exec', auth, 'node', '-e', "fetch('http://127.0.0.1:4173/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"], { stdio: 'ignore' }).status === 0) const reused = await request(port, '/oauth/broker/session', hostname, { cookie, 'x-oauth-csrf': csrf }) assert.equal(reused.status, 200) assert.equal(JSON.parse(reused.body).csrf_token, csrf) docker('stop', '--time', '120', web) assert.equal(JSON.parse(docker('inspect', web))[0].State.ExitCode, 0) } catch (error) { for (const name of [web, auth]) { const logs = spawnSync('docker', ['logs', name], { encoding: 'utf8' }); console.error(logs.stdout, logs.stderr) } throw error } finally { spawnSync('docker', ['rm', '-f', web, auth, database], { stdio: 'ignore' }) spawnSync('docker', ['volume', 'rm', secrets], { stdio: 'ignore' }) spawnSync('docker', ['network', 'rm', network], { stdio: 'ignore' }) } }) function request(port, path, host, headers = {}) { return new Promise((resolve, reject) => { const req = http.get({ hostname: '127.0.0.1', port, path, headers: { host, ...headers } }, async res => { try { let body = ''; for await (const chunk of res) body += chunk; resolve({ status: res.statusCode, headers: res.headers, body }) } catch (error) { reject(error) } }) req.on('error', reject); req.setTimeout(5000, () => req.destroy(new Error('Image request deadline'))) }) } async function ready(check) { const until = Date.now() + 20_000; while (Date.now() < until) { try { if (await check()) return } catch{} await new Promise(resolve => setTimeout(resolve, 100)) } throw new Error('Split image readiness timed out') }