/** Explicit offline operator init. No database, application imports or activation. */ import assert from 'node:assert/strict' import {createHash, generateKeyPairSync} from 'node:crypto' import {constants} from 'node:fs' import {lstat, realpath, open, mkdir, link, unlink} from 'node:fs/promises' import path from 'node:path' import {pathToFileURL} from 'node:url' const digest = bytes => createHash('sha256').update(bytes).digest('hex') const hex = value => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value) const uuid = value => typeof value === 'string' && /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/.test(value) const exact = (value, keys) => value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every(key => Object.hasOwn(value, key)) const roles = ['runtime', 'operator', 'capture', 'publisher', 'control'] export function deploymentRequest(value) { assert(exact(value, ['format', 'installationId', 'sourceHeadSha256', 'source', 'roles', 'tls'])) assert(value.format === 'application-writer-bootstrap-request-v1' && uuid(value.installationId) && hex(value.sourceHeadSha256)) assert(exact(value.source, ['systemIdentifier', 'databaseOid', 'databaseName', 'serverVersionNum', 'inRecovery'])) assert(typeof value.source.systemIdentifier === 'string' && /^[1-9][0-9]{0,19}$/.test(value.source.systemIdentifier) && BigInt(value.source.systemIdentifier) <= 18446744073709551615n) assert(typeof value.source.databaseOid === 'string' && /^[1-9][0-9]{0,9}$/.test(value.source.databaseOid) && BigInt(value.source.databaseOid) <= 4294967295n) assert(typeof value.source.databaseName === 'string' && /^[^\x00-\x1f\x7f]{1,63}$/.test(value.source.databaseName)) assert(Number.isInteger(value.source.serverVersionNum) && value.source.serverVersionNum >= 180000 && value.source.serverVersionNum <= 189999 && value.source.inRecovery === false) assert(exact(value.roles, roles)) for (const role of roles) { const pair = value.roles[role] assert(exact(pair, ['name', 'signedOid']) && typeof pair.name === 'string' && /^[a-z_][a-z0-9_]{0,62}$/.test(pair.name)) assert(typeof pair.signedOid === 'string' && /^[1-9][0-9]{0,9}$/.test(pair.signedOid) && BigInt(pair.signedOid) <= 4294967295n) } assert(new Set(roles.map(role => value.roles[role].name)).size === 5 && new Set(roles.map(role => value.roles[role].signedOid)).size === 5) assert(exact(value.tls, ['serverName', 'caSha256', 'spkiSha256']) && typeof value.tls.serverName === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9.-]{0,252}$/.test(value.tls.serverName) && hex(value.tls.caSha256) && hex(value.tls.spkiSha256)) return structuredClone(value) } export function createBootstrapMaterial(raw, requestSha256) { const request = deploymentRequest(raw) assert(hex(requestSha256)) const {privateKey, publicKey} = generateKeyPairSync('ed25519') const publicKeyPem = publicKey.export({format: 'pem', type: 'spki'}).toString() const publicKeySpkiSha256 = digest(publicKey.export({format: 'der', type: 'spki'})) return { privateKeyPem: privateKey.export({format: 'pem', type: 'pkcs8'}).toString(), publicKeyPem, bootstrap: {format: 'application-writer-bootstrap-v1', verificationSubject: {installationId: request.installationId, publicKeyPem, publicKeySpkiSha256}, deploymentRequest: request, deploymentRequestSha256: requestSha256, keyOrigin: 'explicit-offline-operator-init', databaseObserved: false, rolesAuthenticated: false, sourceAuthenticated: false, approvedDefinitionSha256: null, publicationAuthorized: false, generationAuthenticated: false, readyAuthorized: false}, } } async function protectedDirectory(directory) { assert(process.platform !== 'win32' && typeof process.getuid === 'function', 'Protected POSIX ownership required; Windows ACL backend unavailable') assert(path.isAbsolute(directory) && await realpath(directory) === directory) const uid = process.getuid(), stat = await lstat(directory) assert(stat.isDirectory() && !stat.isSymbolicLink() && stat.uid === uid && (stat.mode & 0o077) === 0) for (let ancestor = path.dirname(directory);; ancestor = path.dirname(ancestor)) { const metadata = await lstat(ancestor) assert(metadata.isDirectory() && !metadata.isSymbolicLink() && (metadata.uid === uid || metadata.uid === 0)) assert((metadata.mode & 0o022) === 0 || metadata.uid === 0 && (metadata.mode & 0o1000) !== 0) if (ancestor === path.dirname(ancestor)) break } return stat } async function publish(directory, name, bytes) { await protectedDirectory(directory) const temporary = path.join(directory, '.' + name + '.pending'), destination = path.join(directory, name) const handle = await open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600) try { await handle.writeFile(bytes); await handle.sync() } finally { await handle.close() } await link(temporary, destination) // Atomic no-replace publication, never rename-overwrite. await unlink(temporary) const parent = await open(directory, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW) try { await parent.sync() } finally { await parent.close() } } export async function initializeBootstrap({outputDirectory, deploymentFile, deploymentSha256}) { assert(path.isAbsolute(outputDirectory) && path.resolve(outputDirectory) === outputDirectory && hex(deploymentSha256)) await protectedDirectory(path.dirname(outputDirectory)) assert(path.isAbsolute(deploymentFile) && await realpath(deploymentFile) === deploymentFile) const requestHandle = await open(deploymentFile, constants.O_RDONLY | constants.O_NOFOLLOW) let bytes try { const before = await requestHandle.stat() assert(before.isFile() && before.uid === process.getuid() && (before.mode & 0o077) === 0 && before.size > 0 && before.size <= 16384) bytes = Buffer.alloc(before.size + 1) let size = 0 while (size < bytes.length) { const read = await requestHandle.read(bytes, size, bytes.length - size, size); if (!read.bytesRead) break; size += read.bytesRead } const after = await requestHandle.stat() assert(size === before.size && after.size === before.size && after.mtimeMs === before.mtimeMs && after.ctimeMs === before.ctimeMs) bytes = bytes.subarray(0, size) } finally { await requestHandle.close() } assert(digest(bytes) === deploymentSha256, 'Explicit independently approved deployment digest required') const request = deploymentRequest(JSON.parse(new TextDecoder('utf-8', {fatal: true}).decode(bytes))) await mkdir(outputDirectory, {mode: 0o700}) // EEXIST refuses; never adopt/overwrite. const material = createBootstrapMaterial(request, deploymentSha256) await publish(outputDirectory, 'signing-private.pem', material.privateKeyPem) await publish(outputDirectory, 'operator-public.pem', material.publicKeyPem) await publish(outputDirectory, 'bootstrap.json', JSON.stringify(material.bootstrap, null, 2) + '\n') // Completion marker last. return {event: 'application-writer-bootstrap-initialized', installationId: request.installationId, publicKeySpkiSha256: material.bootstrap.verificationSubject.publicKeySpkiSha256, deploymentRequestSha256: deploymentSha256, databaseObserved: false, publicationAuthorized: false, readyAuthorized: false} } export async function main(args) { assert(args.length === 7 && args[0] === 'init') const values = new Map() for (let i = 1; i < args.length; i += 2) { assert(['--output', '--deployment', '--deployment-sha256'].includes(args[i]) && !values.has(args[i])); values.set(args[i], args[i + 1]) } return initializeBootstrap({outputDirectory: values.get('--output'), deploymentFile: values.get('--deployment'), deploymentSha256: values.get('--deployment-sha256')}) } if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) { try { console.log(JSON.stringify(await main(process.argv.slice(2)))) } catch { console.error(JSON.stringify({event: 'application-writer-bootstrap-refused', publicationAuthorized: false, readyAuthorized: false})); process.exitCode = 1 } }