Files
orca/config/scripts/ssh-relay-runtime-linux-finalization.mjs
T
2026-07-15 09:34:18 -04:00

227 lines
8.0 KiB
JavaScript

import { constants } from 'node:fs'
import { copyFile, lstat, mkdir, realpath, rm, writeFile } from 'node:fs/promises'
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'
import { pathToFileURL } from 'node:url'
import { writeSshRelayRuntimeManifestTupleDescriptor } from './ssh-relay-runtime-manifest-tuple.mjs'
import {
buildSshRelayRuntimeNativeSigningPlan,
readSshRelayRuntimeNativeSigningIdentity
} from './ssh-relay-runtime-native-signing-plan.mjs'
import { verifySshRelayRuntime } from './verify-ssh-relay-runtime.mjs'
const MAX_RECEIPT_BYTES = 32 * 1024 * 1024
const PATH_ARGUMENTS = new Map([
['--source-output-directory', 'sourceOutputDirectory'],
['--identity', 'identityPath'],
['--output-directory', 'outputDirectory']
])
const VALUE_ARGUMENTS = new Map([
['--verified-at', 'verifiedAt'],
['--native-verification-tool-version', 'nativeVerificationToolVersion']
])
function containsPath(parent, candidate) {
const path = relative(parent, candidate)
return path === '' || (path !== '..' && !path.startsWith(`..${sep}`) && !isAbsolute(path))
}
async function physicalDirectory(path, label) {
const metadata = await lstat(path)
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
throw new Error(`Runtime Linux finalization ${label} must be a real directory`)
}
return realpath(path)
}
async function assertAbsent(path) {
try {
await lstat(path)
} catch (error) {
if (error.code === 'ENOENT') {
return
}
throw error
}
throw new Error('Runtime Linux finalization requires an exclusive output directory')
}
async function copyRegularFile(source, destination, label) {
const before = await lstat(source, { bigint: true })
if (!before.isFile() || before.isSymbolicLink() || before.size <= 0n) {
throw new Error(`Runtime Linux finalization ${label} must be a non-empty regular file`)
}
await copyFile(source, destination, constants.COPYFILE_EXCL)
const after = await lstat(source, { bigint: true })
if (
before.dev !== after.dev ||
before.ino !== after.ino ||
before.size !== after.size ||
before.mtimeNs !== after.mtimeNs ||
before.ctimeNs !== after.ctimeNs
) {
throw new Error(`Runtime Linux finalization ${label} changed while copying`)
}
}
function aggregateAssetNames(identity) {
if (
typeof identity.archive?.name !== 'string' ||
basename(identity.archive.name) !== identity.archive.name
) {
throw new Error('Runtime Linux finalization archive identity is invalid')
}
const prefix = `orca-ssh-relay-runtime-${identity.tupleId}`
return {
archive: identity.archive.name,
sbom: `${prefix}.spdx.json`,
provenance: `${prefix}.provenance.json`
}
}
function linuxVerificationReport(identity, plan) {
return {
tupleId: identity.tupleId,
sourceContentId: identity.contentId,
finalContentId: identity.contentId,
verifiedFiles: plan.verificationFiles.map((file) => ({
path: file.path,
role: file.role,
sha256: file.sourceSha256
}))
}
}
async function writeReceipt(path, receipt) {
const bytes = Buffer.from(`${JSON.stringify(receipt, null, 2)}\n`, 'utf8')
if (bytes.length === 0 || bytes.length > MAX_RECEIPT_BYTES) {
throw new Error('Runtime Linux finalization receipt exceeds its size limit')
}
await writeFile(path, bytes, { flag: 'wx', mode: 0o600 })
}
export async function finalizeSshRelayRuntimeLinuxArtifact({
sourceOutputDirectory,
identityPath,
outputDirectory,
verifiedAt,
nativeVerificationTool,
readIdentityImpl = readSshRelayRuntimeNativeSigningIdentity,
buildPlanImpl = buildSshRelayRuntimeNativeSigningPlan,
verifyRuntimeImpl = verifySshRelayRuntime,
writeDescriptorImpl = writeSshRelayRuntimeManifestTupleDescriptor
}) {
const source = await physicalDirectory(resolve(sourceOutputDirectory), 'source output')
const absoluteIdentity = resolve(identityPath)
const physicalIdentity = await realpath(absoluteIdentity)
const identity = await readIdentityImpl(physicalIdentity)
if (identity.os !== 'linux' || !identity.tupleId?.startsWith('linux-')) {
throw new Error('Runtime Linux finalization accepts only Linux identities')
}
const plan = buildPlanImpl(identity)
if (plan.platform !== 'linux') {
throw new Error('Runtime Linux finalization signing plan must be hash-only Linux')
}
const expectedIdentityPath = join(
source,
`orca-ssh-relay-runtime-${identity.tupleId}.identity.json`
)
if (physicalIdentity !== expectedIdentityPath) {
throw new Error('Runtime Linux finalization identity must be the exact source output identity')
}
const outputParent = await physicalDirectory(dirname(resolve(outputDirectory)), 'output parent')
const output = resolve(outputParent, basename(resolve(outputDirectory)))
if (containsPath(source, output) || containsPath(output, source)) {
throw new Error('Runtime Linux finalization source and output must be physically disjoint')
}
await assertAbsent(output)
const names = aggregateAssetNames(identity)
const runtimeDirectory = join(source, 'runtime')
await physicalDirectory(runtimeDirectory, 'runtime')
const finalIdentity = { ...identity }
delete finalIdentity.archive
let outputCreated = false
try {
await mkdir(output)
outputCreated = true
const assetsRoot = join(output, 'assets')
const evidenceRoot = join(output, 'evidence')
await Promise.all([mkdir(assetsRoot), mkdir(evidenceRoot)])
for (const [label, name] of Object.entries(names)) {
await copyRegularFile(join(source, name), join(assetsRoot, name), label)
}
const verification = await verifyRuntimeImpl({
runtimeDirectory,
identityPath: physicalIdentity,
archivePath: join(source, names.archive)
})
// Why: Linux has no signing mutation, but its complete native hash projection must still be final.
const verificationReport = linuxVerificationReport(identity, plan)
const descriptor = await writeDescriptorImpl({
runtimeRoot: runtimeDirectory,
inputDirectory: assetsRoot,
finalIdentity,
verificationReport,
nativeVerificationTool,
verifiedAt
})
const receipt = {
tupleId: identity.tupleId,
contentId: identity.contentId,
verification,
aggregateInput: descriptor.input
}
const receiptPath = join(evidenceRoot, `${identity.tupleId}.linux-finalization.json`)
await writeReceipt(receiptPath, receipt)
return {
...receipt,
assetsRoot,
evidenceRoot,
receiptPath
}
} catch (error) {
if (outputCreated) {
// Why: an incomplete descriptor set must never become an aggregate candidate.
await rm(output, { recursive: true, force: true })
}
throw error
}
}
export function parseSshRelayRuntimeLinuxFinalizationArguments(argv) {
const result = {}
for (let index = 0; index < argv.length; index += 2) {
const flag = argv[index]
const field = PATH_ARGUMENTS.get(flag) ?? VALUE_ARGUMENTS.get(flag)
const value = argv[index + 1]
if (!field || !value || value.startsWith('--') || result[field]) {
throw new Error(`Invalid runtime Linux finalization argument: ${flag}`)
}
result[field] = PATH_ARGUMENTS.has(flag) ? resolve(value) : value
}
for (const field of [...PATH_ARGUMENTS.values(), ...VALUE_ARGUMENTS.values()]) {
if (!result[field]) {
throw new Error(`Missing runtime Linux finalization argument: ${field}`)
}
}
return result
}
async function main() {
const options = parseSshRelayRuntimeLinuxFinalizationArguments(process.argv.slice(2))
const result = await finalizeSshRelayRuntimeLinuxArtifact({
...options,
nativeVerificationTool: { name: 'node', version: options.nativeVerificationToolVersion }
})
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
}
if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) {
main().catch((error) => {
process.stderr.write(`SSH relay runtime Linux finalization failed: ${error.stack ?? error}\n`)
process.exitCode = 1
})
}