import { randomBytes } from 'node:crypto'; import { execFileSync } from 'node:child_process'; import { mkdirSync, writeFileSync, readFileSync, existsSync, chmodSync, chownSync, openSync, fsyncSync, closeSync } from 'node:fs'; // Local SANDBOX provisioning only. No secrets pass through logs, command arguments or source control. const roots = ['/secrets/admin', '/secrets/owner', '/secrets/runtime', '/secrets/tls']; for (const root of roots) mkdirSync(root, { recursive: true, mode: 0o755 }); const files = ['/secrets/admin/password', '/secrets/admin/url', '/secrets/owner/url', '/secrets/runtime/url', '/secrets/tls/server.key', '/secrets/tls/server.crt', '/secrets/owner/ca.crt', '/secrets/runtime/ca.crt', '/secrets/admin/ca.crt']; const marker = '/secrets/admin/complete'; if (existsSync(marker)) { if (readFileSync(marker, 'utf8') !== 'wikideck-local-pg-v1' || files.some(f => !existsSync(f))) { throw new Error('Incomplete secret volumes; restore the matching volumes, never regenerate over an existing database'); } execFileSync('openssl', ['x509', '-checkend', '86400', '-noout', '-in', '/secrets/tls/server.crt'], { stdio: 'ignore' }); console.log('Existing local sandbox credentials/certificate retained.'); } else { if (files.some(existsSync)) throw new Error('Partial secret initialization; inspect preserved volumes before retrying'); const admin = randomBytes(32).toString('hex'), owner = randomBytes(32).toString('hex'), runtime = randomBytes(32).toString('hex'); const put = (file, text, mode = 0o600) => { writeFileSync(file, text, { flag: 'wx', mode }); chownSync(file, 1000, 1000); const fd = openSync(file, 'r'); try { fsyncSync(fd); } finally { closeSync(fd); } }; // PostgreSQL's unprivileged entrypoint also reads POSTGRES_PASSWORD_FILE. This separate scoped // admin volume is never mounted into the application; the password file must be readable there. put('/secrets/admin/password', admin, 0o644); put('/secrets/admin/url', `postgresql://postgres:${admin}@database:5432/wikideck_custody`); put('/secrets/owner/url', `postgresql://custody_owner:${owner}@database:5432/wikideck_custody`); put('/secrets/runtime/url', `postgresql://custody_runtime:${runtime}@database:5432/wikideck_custody`); execFileSync('openssl', ['req', '-x509', '-newkey', 'rsa:3072', '-sha256', '-nodes', '-days', '30', '-subj', '/CN=Wikideck local sandbox CA', '-keyout', '/tmp/ca.key', '-out', '/tmp/ca.crt', '-addext', 'basicConstraints=critical,CA:TRUE', '-addext', 'keyUsage=critical,keyCertSign,cRLSign'], { stdio: 'ignore' }); execFileSync('openssl', ['req', '-new', '-newkey', 'rsa:3072', '-nodes', '-subj', '/CN=database', '-keyout', '/secrets/tls/server.key', '-out', '/tmp/server.csr'], { stdio: 'ignore' }); writeFileSync('/tmp/extensions', 'subjectAltName=DNS:database\nbasicConstraints=critical,CA:FALSE\nkeyUsage=critical,digitalSignature,keyEncipherment\nextendedKeyUsage=serverAuth\n'); execFileSync('openssl', ['x509', '-req', '-in', '/tmp/server.csr', '-CA', '/tmp/ca.crt', '-CAkey', '/tmp/ca.key', '-CAcreateserial', '-days', '30', '-sha256', '-extfile', '/tmp/extensions', '-out', '/secrets/tls/server.crt'], { stdio: 'ignore' }); for (const root of ['admin', 'owner', 'runtime']) put(`/secrets/${root}/ca.crt`, readFileSync('/tmp/ca.crt'), 0o644); // postgres:17-alpine runs as UID/GID 70. Private key never mounted into Wikideck or migrator. chownSync('/secrets/tls/server.key', 70, 70); chmodSync('/secrets/tls/server.key', 0o600); chmodSync('/secrets/tls/server.crt', 0o644); put(marker, 'wikideck-local-pg-v1', 0o644); console.log('Provisioned fresh local sandbox credentials and verified-TLS certificate; no secrets logged.'); }