mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 00:03:47 +00:00
ci: add fail-closed relay manifest aggregation
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -0,0 +1,401 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { lstat, mkdir, readFile, realpath, rm, writeFile } from 'node:fs/promises'
|
||||
import { basename, dirname, join, resolve } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
|
||||
import { collectSshRelayRuntimeManifestArtifacts } from './ssh-relay-runtime-manifest-artifact-collection.mjs'
|
||||
import {
|
||||
finalizeSshRelayRuntimeManifestAggregate,
|
||||
prepareSshRelayRuntimeManifestAggregate
|
||||
} from './ssh-relay-runtime-manifest-aggregate.mjs'
|
||||
import {
|
||||
decodeSshRelayRuntimeManifestSigningRequestArtifact,
|
||||
encodeSshRelayRuntimeManifestSigningRequestArtifact
|
||||
} from './ssh-relay-runtime-manifest-seed-signing.mjs'
|
||||
import { sshRelayRuntimeManifestKeyId } from './ssh-relay-runtime-manifest-signing-handoff.mjs'
|
||||
|
||||
const MAX_JSON_BYTES = 48 * 1024 * 1024
|
||||
const SOURCE_SHA = /^[0-9a-f]{40}$/u
|
||||
const TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u
|
||||
const BASE64 = /^[A-Za-z0-9+/]+={0,2}$/u
|
||||
const DIGEST = /^sha256:[0-9a-f]{64}$/u
|
||||
const PREPARED_FIELDS = [
|
||||
'acceptedKeysSha256',
|
||||
'createdAt',
|
||||
'inputTuples',
|
||||
'inputTuplesSha256',
|
||||
'relayProtocolVersion',
|
||||
'releaseTag',
|
||||
'schemaVersion',
|
||||
'signingRequest',
|
||||
'sourceSha',
|
||||
'unsignedManifest'
|
||||
]
|
||||
|
||||
function assertObject(value, label) {
|
||||
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
||||
throw new Error(`SSH relay runtime manifest aggregate command ${label} must be an object`)
|
||||
}
|
||||
}
|
||||
|
||||
function assertExactFields(value, fields, label) {
|
||||
assertObject(value, label)
|
||||
const actual = Object.keys(value).sort()
|
||||
const expected = [...fields].sort()
|
||||
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
||||
throw new Error(`SSH relay runtime manifest aggregate command ${label} has invalid fields`)
|
||||
}
|
||||
}
|
||||
|
||||
function sha256(bytes) {
|
||||
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`
|
||||
}
|
||||
|
||||
function canonicalJson(value) {
|
||||
return Buffer.from(`${JSON.stringify(value)}\n`, 'utf8')
|
||||
}
|
||||
|
||||
function parseReleaseTag(tag) {
|
||||
for (const [pattern, channel] of [
|
||||
[/^v(\d+\.\d+\.\d+)$/u, 'stable'],
|
||||
[/^v(\d+\.\d+\.\d+-rc\.\d+)$/u, 'rc'],
|
||||
[/^v(\d+\.\d+\.\d+-rc\.\d+\.perf)$/u, 'perf']
|
||||
]) {
|
||||
const match = pattern.exec(tag)
|
||||
if (match) {
|
||||
return { tag, version: match[1], channel }
|
||||
}
|
||||
}
|
||||
throw new Error('SSH relay runtime manifest aggregate command release tag is invalid')
|
||||
}
|
||||
|
||||
function commandIdentity(input) {
|
||||
if (!SOURCE_SHA.test(input.sourceSha ?? '')) {
|
||||
throw new Error('SSH relay runtime manifest aggregate command source SHA is invalid')
|
||||
}
|
||||
const release = parseReleaseTag(input.releaseTag)
|
||||
if (
|
||||
typeof input.createdAt !== 'string' ||
|
||||
!TIMESTAMP.test(input.createdAt) ||
|
||||
new Date(input.createdAt).toISOString() !== input.createdAt
|
||||
) {
|
||||
throw new Error('SSH relay runtime manifest aggregate command timestamp is invalid')
|
||||
}
|
||||
if (!Number.isSafeInteger(input.relayProtocolVersion) || input.relayProtocolVersion <= 0) {
|
||||
throw new Error('SSH relay runtime manifest aggregate command protocol version is invalid')
|
||||
}
|
||||
return {
|
||||
sourceSha: input.sourceSha,
|
||||
releaseTag: input.releaseTag,
|
||||
createdAt: input.createdAt,
|
||||
relayProtocolVersion: input.relayProtocolVersion,
|
||||
build: { ...release, relayProtocolVersion: input.relayProtocolVersion }
|
||||
}
|
||||
}
|
||||
|
||||
async function readBoundedJson(path, label) {
|
||||
const before = await lstat(path, { bigint: true })
|
||||
if (
|
||||
!before.isFile() ||
|
||||
before.isSymbolicLink() ||
|
||||
before.size <= 0n ||
|
||||
before.size > BigInt(MAX_JSON_BYTES)
|
||||
) {
|
||||
throw new Error(
|
||||
`SSH relay runtime manifest aggregate command ${label} must be bounded regular JSON`
|
||||
)
|
||||
}
|
||||
try {
|
||||
const bytes = await readFile(path)
|
||||
const after = await lstat(path, { 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('file changed while reading')
|
||||
}
|
||||
return JSON.parse(bytes.toString('utf8'))
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`SSH relay runtime manifest aggregate command ${label} is invalid JSON: ${error.message}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function decodeCanonicalBase64(value, label, expectedSize) {
|
||||
if (typeof value !== 'string' || !BASE64.test(value)) {
|
||||
throw new Error(`SSH relay runtime manifest aggregate command ${label} is not canonical base64`)
|
||||
}
|
||||
const bytes = Buffer.from(value, 'base64')
|
||||
if (bytes.length !== expectedSize || bytes.toString('base64') !== value) {
|
||||
throw new Error(
|
||||
`SSH relay runtime manifest aggregate command ${label} must contain ${expectedSize} bytes`
|
||||
)
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
async function readAcceptedKeys(path) {
|
||||
const document = await readBoundedJson(resolve(path), 'accepted keys')
|
||||
assertExactFields(document, ['keys', 'schemaVersion'], 'accepted keys')
|
||||
if (
|
||||
document.schemaVersion !== 1 ||
|
||||
!Array.isArray(document.keys) ||
|
||||
document.keys.length === 0 ||
|
||||
document.keys.length > 4
|
||||
) {
|
||||
throw new Error('SSH relay runtime manifest aggregate command accepted keys are invalid')
|
||||
}
|
||||
const seen = new Set()
|
||||
const artifactKeys = document.keys
|
||||
.map((key, index) => {
|
||||
assertExactFields(key, ['keyId', 'publicKeyBase64'], `accepted key ${index}`)
|
||||
const publicKey = decodeCanonicalBase64(key.publicKeyBase64, 'accepted public key', 32)
|
||||
const keyId = sshRelayRuntimeManifestKeyId(publicKey)
|
||||
if (!DIGEST.test(key.keyId ?? '') || key.keyId !== keyId || seen.has(keyId)) {
|
||||
throw new Error(
|
||||
'SSH relay runtime manifest aggregate command accepted key identity is invalid'
|
||||
)
|
||||
}
|
||||
seen.add(keyId)
|
||||
return { keyId, publicKeyBase64: key.publicKeyBase64, publicKey }
|
||||
})
|
||||
.sort((left, right) => (left.keyId < right.keyId ? -1 : left.keyId > right.keyId ? 1 : 0))
|
||||
const serialized = artifactKeys.map(({ keyId, publicKeyBase64 }) => ({ keyId, publicKeyBase64 }))
|
||||
return {
|
||||
acceptedKeys: artifactKeys.map(({ keyId, publicKey }) => ({ keyId, publicKey })),
|
||||
sha256: sha256(canonicalJson({ schemaVersion: 1, keys: serialized }))
|
||||
}
|
||||
}
|
||||
|
||||
async function exclusiveOutputDirectory(outputDirectory) {
|
||||
const absolute = resolve(outputDirectory)
|
||||
const parent = resolve(await realpath(dirname(absolute)))
|
||||
const parentMetadata = await lstat(parent)
|
||||
if (!parentMetadata.isDirectory() || parentMetadata.isSymbolicLink()) {
|
||||
throw new Error('SSH relay runtime manifest aggregate command output parent must be real')
|
||||
}
|
||||
const output = resolve(parent, basename(absolute))
|
||||
try {
|
||||
await lstat(output)
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return output
|
||||
}
|
||||
throw error
|
||||
}
|
||||
throw new Error(
|
||||
'SSH relay runtime manifest aggregate command requires an exclusive output directory'
|
||||
)
|
||||
}
|
||||
|
||||
function assertReceiptBindings(bindings, prepared) {
|
||||
const tuples = new Map(prepared.unsignedManifest.tuples.map((tuple) => [tuple.tupleId, tuple]))
|
||||
for (const binding of bindings) {
|
||||
if (tuples.get(binding.tupleId)?.contentId !== binding.contentId) {
|
||||
throw new Error(
|
||||
`SSH relay runtime manifest aggregate command receipt content drifted: ${binding.tupleId}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function preparedArtifact(identity, acceptedKeysSha256, prepared) {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
sourceSha: identity.sourceSha,
|
||||
releaseTag: identity.releaseTag,
|
||||
createdAt: identity.createdAt,
|
||||
relayProtocolVersion: identity.relayProtocolVersion,
|
||||
acceptedKeysSha256,
|
||||
inputTuples: prepared.inputTuples,
|
||||
inputTuplesSha256: prepared.inputTuplesSha256,
|
||||
unsignedManifest: prepared.unsignedManifest,
|
||||
signingRequest: encodeSshRelayRuntimeManifestSigningRequestArtifact(prepared.signingRequest)
|
||||
}
|
||||
}
|
||||
|
||||
async function collectAndPrepare(input, output, identity, signal) {
|
||||
const stagingDirectory = join(output, 'aggregate-input')
|
||||
await mkdir(stagingDirectory)
|
||||
const bindings = await collectSshRelayRuntimeManifestArtifacts({
|
||||
artifactsDirectory: input.artifactsDirectory,
|
||||
stagingDirectory,
|
||||
signal
|
||||
})
|
||||
const prepared = await prepareSshRelayRuntimeManifestAggregate({
|
||||
inputDirectory: stagingDirectory,
|
||||
build: identity.build,
|
||||
createdAt: identity.createdAt,
|
||||
tupleInputs: bindings.map(({ aggregateInput }) => aggregateInput),
|
||||
signal
|
||||
})
|
||||
assertReceiptBindings(bindings, prepared)
|
||||
return prepared
|
||||
}
|
||||
|
||||
export async function prepareSshRelayRuntimeManifestAggregateCommand(input) {
|
||||
const identity = commandIdentity(input)
|
||||
// Why: public-key policy must be valid before canonical bytes can leave this boundary.
|
||||
const keys = await readAcceptedKeys(input.acceptedKeysPath)
|
||||
const output = await exclusiveOutputDirectory(input.outputDirectory)
|
||||
let created = false
|
||||
try {
|
||||
await mkdir(output, { mode: 0o700 })
|
||||
created = true
|
||||
const prepared = await collectAndPrepare(input, output, identity, input.signal)
|
||||
const artifact = preparedArtifact(identity, keys.sha256, prepared)
|
||||
await writeFile(join(output, 'prepared-aggregate.json'), canonicalJson(artifact), {
|
||||
flag: 'wx',
|
||||
mode: 0o600
|
||||
})
|
||||
await writeFile(join(output, 'signing-request.json'), canonicalJson(artifact.signingRequest), {
|
||||
flag: 'wx',
|
||||
mode: 0o600
|
||||
})
|
||||
await rm(join(output, 'aggregate-input'), { recursive: true, force: true })
|
||||
return prepared
|
||||
} catch (error) {
|
||||
if (created) {
|
||||
await rm(output, { recursive: true, force: true })
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePreparedArtifact(value) {
|
||||
assertExactFields(value, PREPARED_FIELDS, 'prepared aggregate')
|
||||
if (value.schemaVersion !== 1 || !DIGEST.test(value.acceptedKeysSha256 ?? '')) {
|
||||
throw new Error('SSH relay runtime manifest aggregate command prepared aggregate is invalid')
|
||||
}
|
||||
decodeSshRelayRuntimeManifestSigningRequestArtifact(value.signingRequest)
|
||||
return value
|
||||
}
|
||||
|
||||
function sameJson(left, right) {
|
||||
return JSON.stringify(left) === JSON.stringify(right)
|
||||
}
|
||||
|
||||
export async function finalizeSshRelayRuntimeManifestAggregateCommand(input) {
|
||||
const identity = commandIdentity(input)
|
||||
const keys = await readAcceptedKeys(input.acceptedKeysPath)
|
||||
const prior = normalizePreparedArtifact(
|
||||
await readBoundedJson(
|
||||
join(resolve(input.preparedDirectory), 'prepared-aggregate.json'),
|
||||
'prepared aggregate'
|
||||
)
|
||||
)
|
||||
const signingResult = await readBoundedJson(resolve(input.signingResultPath), 'signing result')
|
||||
assertExactFields(signingResult, ['keyId', 'signature'], 'signing result')
|
||||
const output = await exclusiveOutputDirectory(input.outputDirectory)
|
||||
let created = false
|
||||
try {
|
||||
await mkdir(output, { mode: 0o700 })
|
||||
created = true
|
||||
const prepared = await collectAndPrepare(input, output, identity, input.signal)
|
||||
const current = preparedArtifact(identity, keys.sha256, prepared)
|
||||
if (!sameJson(prior, current)) {
|
||||
throw new Error('SSH relay runtime manifest aggregate command prepared receipt drifted')
|
||||
}
|
||||
const finalized = finalizeSshRelayRuntimeManifestAggregate({
|
||||
prepared,
|
||||
signingResults: [signingResult],
|
||||
acceptedKeys: keys.acceptedKeys
|
||||
})
|
||||
const assets = join(output, 'assets')
|
||||
const evidence = join(output, 'evidence')
|
||||
await Promise.all([mkdir(assets), mkdir(evidence)])
|
||||
await writeFile(join(assets, finalized.manifestAsset.name), finalized.bytes, {
|
||||
flag: 'wx',
|
||||
mode: 0o600
|
||||
})
|
||||
await writeFile(
|
||||
join(evidence, 'manifest-aggregate.json'),
|
||||
canonicalJson({
|
||||
schemaVersion: 1,
|
||||
sourceSha: identity.sourceSha,
|
||||
releaseTag: identity.releaseTag,
|
||||
acceptedKeysSha256: keys.sha256,
|
||||
inputTuplesSha256: finalized.inputTuplesSha256,
|
||||
manifestAsset: finalized.manifestAsset
|
||||
}),
|
||||
{ flag: 'wx', mode: 0o600 }
|
||||
)
|
||||
await rm(join(output, 'aggregate-input'), { recursive: true, force: true })
|
||||
return finalized
|
||||
} catch (error) {
|
||||
if (created) {
|
||||
await rm(output, { recursive: true, force: true })
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export function parseSshRelayRuntimeManifestAggregateCommandArguments(argv) {
|
||||
const command = argv[0]
|
||||
if (command !== 'prepare' && command !== 'finalize') {
|
||||
throw new Error('SSH relay runtime manifest aggregate command requires prepare or finalize')
|
||||
}
|
||||
const fields = new Map([
|
||||
['--artifacts-directory', 'artifactsDirectory'],
|
||||
['--accepted-keys', 'acceptedKeysPath'],
|
||||
['--output-directory', 'outputDirectory'],
|
||||
['--source-sha', 'sourceSha'],
|
||||
['--release-tag', 'releaseTag'],
|
||||
['--created-at', 'createdAt'],
|
||||
['--relay-protocol-version', 'relayProtocolVersion'],
|
||||
['--prepared-directory', 'preparedDirectory'],
|
||||
['--signing-result', 'signingResultPath']
|
||||
])
|
||||
const result = { command }
|
||||
for (let index = 1; index < argv.length; index += 2) {
|
||||
const field = fields.get(argv[index])
|
||||
const value = argv[index + 1]
|
||||
if (!field || !value || value.startsWith('--') || result[field]) {
|
||||
throw new Error(
|
||||
`Invalid SSH relay runtime manifest aggregate command argument: ${argv[index]}`
|
||||
)
|
||||
}
|
||||
result[field] = field === 'relayProtocolVersion' ? Number(value) : value
|
||||
}
|
||||
const required = [
|
||||
'artifactsDirectory',
|
||||
'acceptedKeysPath',
|
||||
'outputDirectory',
|
||||
'sourceSha',
|
||||
'releaseTag',
|
||||
'createdAt',
|
||||
'relayProtocolVersion'
|
||||
]
|
||||
if (command === 'finalize') {
|
||||
required.push('preparedDirectory', 'signingResultPath')
|
||||
}
|
||||
for (const field of required) {
|
||||
if (!result[field]) {
|
||||
throw new Error(`Missing SSH relay runtime manifest aggregate command argument: ${field}`)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = parseSshRelayRuntimeManifestAggregateCommandArguments(process.argv.slice(2))
|
||||
const operation =
|
||||
options.command === 'prepare'
|
||||
? prepareSshRelayRuntimeManifestAggregateCommand
|
||||
: finalizeSshRelayRuntimeManifestAggregateCommand
|
||||
const result = await operation(options)
|
||||
process.stdout.write(`${JSON.stringify({ inputTuplesSha256: result.inputTuplesSha256 })}\n`)
|
||||
}
|
||||
|
||||
if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) {
|
||||
main().catch((error) => {
|
||||
process.stderr.write(
|
||||
`SSH relay runtime manifest aggregate command failed: ${error.stack ?? error}\n`
|
||||
)
|
||||
process.exitCode = 1
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { lstat, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import nacl from 'tweetnacl'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { createSshRelayArtifactTestManifest } from '../../src/main/ssh/ssh-relay-artifact-test-manifest.ts'
|
||||
import { verifySshRelayArtifactManifest } from '../../src/main/ssh/ssh-relay-manifest-signature.ts'
|
||||
import { sshRelayRuntimeCompatibility } from './ssh-relay-runtime-compatibility.mjs'
|
||||
import { computeSshRelayRuntimeContentId } from './ssh-relay-runtime-identity.mjs'
|
||||
import {
|
||||
finalizeSshRelayRuntimeManifestAggregateCommand,
|
||||
parseSshRelayRuntimeManifestAggregateCommandArguments,
|
||||
prepareSshRelayRuntimeManifestAggregateCommand
|
||||
} from './ssh-relay-runtime-manifest-aggregate-command.mjs'
|
||||
import {
|
||||
encodeSshRelayRuntimeManifestSigningRequestArtifact,
|
||||
signSshRelayRuntimeManifestRequest
|
||||
} from './ssh-relay-runtime-manifest-seed-signing.mjs'
|
||||
import { sshRelayRuntimeManifestKeyId } from './ssh-relay-runtime-manifest-signing-handoff.mjs'
|
||||
|
||||
const temporaryDirectories = []
|
||||
const keyPair = nacl.sign.keyPair.fromSeed(Uint8Array.from({ length: 32 }, (_, index) => index))
|
||||
const TUPLES = [
|
||||
'linux-x64-glibc',
|
||||
'linux-arm64-glibc',
|
||||
'darwin-x64',
|
||||
'darwin-arm64',
|
||||
'win32-x64',
|
||||
'win32-arm64'
|
||||
]
|
||||
|
||||
function digest(bytes) {
|
||||
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`
|
||||
}
|
||||
|
||||
function fileReference(name, bytes) {
|
||||
return { name, size: bytes.length, sha256: digest(bytes) }
|
||||
}
|
||||
|
||||
function watcherPackage(tupleId) {
|
||||
return tupleId.startsWith('linux-')
|
||||
? `@parcel/watcher-linux-${tupleId.split('-')[1]}-glibc`
|
||||
: `@parcel/watcher-${tupleId}`
|
||||
}
|
||||
|
||||
function nativeEntry(path, role, marker) {
|
||||
return {
|
||||
path,
|
||||
type: 'file',
|
||||
role,
|
||||
size: marker.length,
|
||||
mode: 0o755,
|
||||
sha256: digest(marker)
|
||||
}
|
||||
}
|
||||
|
||||
function tupleFor(tupleId) {
|
||||
const tuple = structuredClone(createSshRelayArtifactTestManifest().tuples[0])
|
||||
const [os, architecture] = tupleId.split('-')
|
||||
tuple.tupleId = tupleId
|
||||
tuple.os = os
|
||||
tuple.architecture = architecture
|
||||
tuple.compatibility = structuredClone(sshRelayRuntimeCompatibility[tupleId])
|
||||
const packageName = watcherPackage(tupleId)
|
||||
tuple.entries = tuple.entries.map((entry) => {
|
||||
const next = structuredClone(entry)
|
||||
next.path = next.path.replace('@parcel/watcher-linux-x64-glibc', packageName)
|
||||
if (next.role === 'node') {
|
||||
next.path = os === 'win32' ? 'bin/node.exe' : 'bin/node'
|
||||
}
|
||||
return next
|
||||
})
|
||||
if (os === 'darwin') {
|
||||
tuple.entries.push(
|
||||
nativeEntry(
|
||||
'node_modules/node-pty/build/Release/spawn-helper',
|
||||
'native-runtime',
|
||||
`${tupleId}:spawn-helper`
|
||||
)
|
||||
)
|
||||
}
|
||||
if (os === 'win32') {
|
||||
tuple.entries = tuple.entries.filter((entry) => entry.role !== 'node-pty-native')
|
||||
tuple.entries.push(
|
||||
{ path: 'node_modules/node-pty/build/Release/conpty', type: 'directory', mode: 0o755 },
|
||||
nativeEntry(
|
||||
'node_modules/node-pty/build/Release/conpty.node',
|
||||
'node-pty-native',
|
||||
`${tupleId}:conpty-node`
|
||||
),
|
||||
nativeEntry(
|
||||
'node_modules/node-pty/build/Release/conpty_console_list.node',
|
||||
'node-pty-native',
|
||||
`${tupleId}:console-list`
|
||||
),
|
||||
nativeEntry(
|
||||
'node_modules/node-pty/build/Release/conpty/OpenConsole.exe',
|
||||
'native-runtime',
|
||||
`${tupleId}:open-console`
|
||||
),
|
||||
nativeEntry(
|
||||
'node_modules/node-pty/build/Release/conpty/conpty.dll',
|
||||
'native-runtime',
|
||||
`${tupleId}:conpty-dll`
|
||||
)
|
||||
)
|
||||
}
|
||||
const nativeRoles = new Set([
|
||||
'node',
|
||||
'node-pty-native',
|
||||
'parcel-watcher-native',
|
||||
'native-runtime'
|
||||
])
|
||||
tuple.nativeVerification = {
|
||||
policy:
|
||||
os === 'linux'
|
||||
? 'linux-hash-only-v1'
|
||||
: os === 'darwin'
|
||||
? 'apple-developer-id-v1'
|
||||
: 'signpath-authenticode-v1',
|
||||
tool: { name: 'fixture-verifier', version: '1.0.0' },
|
||||
verifiedAt: '2026-07-15T00:00:00.000Z',
|
||||
files: tuple.entries
|
||||
.filter((entry) => entry.type === 'file' && nativeRoles.has(entry.role))
|
||||
.map(({ path, sha256 }) => ({ path, sha256 }))
|
||||
}
|
||||
const files = tuple.entries.filter((entry) => entry.type === 'file')
|
||||
tuple.archive.fileCount = files.length
|
||||
tuple.archive.expandedSize = files.reduce((total, entry) => total + entry.size, 0)
|
||||
tuple.metadataAssets.sbom.name = `orca-ssh-relay-runtime-${tupleId}.spdx.json`
|
||||
tuple.metadataAssets.provenance.name = `orca-ssh-relay-runtime-${tupleId}.provenance.json`
|
||||
tuple.contentId = computeSshRelayRuntimeContentId(tuple)
|
||||
const extension = os === 'win32' ? 'zip' : 'tar.xz'
|
||||
tuple.archive.name = `orca-ssh-relay-runtime-v1-${tupleId}-${tuple.contentId.slice(7)}.${extension}`
|
||||
return tuple
|
||||
}
|
||||
|
||||
async function writeTupleArtifact(root, tupleId) {
|
||||
const tuple = tupleFor(tupleId)
|
||||
const archiveBytes = Buffer.from(`${tupleId}:archive`)
|
||||
const sbomBytes = Buffer.from(`${JSON.stringify({ spdxVersion: 'SPDX-2.3', tupleId })}\n`)
|
||||
const provenanceBytes = Buffer.from(`${JSON.stringify({ _type: 'in-toto', tupleId })}\n`)
|
||||
tuple.archive.size = archiveBytes.length
|
||||
tuple.archive.sha256 = digest(archiveBytes)
|
||||
tuple.metadataAssets.sbom = fileReference(tuple.metadataAssets.sbom.name, sbomBytes)
|
||||
tuple.metadataAssets.provenance = fileReference(
|
||||
tuple.metadataAssets.provenance.name,
|
||||
provenanceBytes
|
||||
)
|
||||
const descriptorBytes = Buffer.from(
|
||||
`${JSON.stringify({ schemaVersion: 1, tuple }, null, 2)}\n`,
|
||||
'utf8'
|
||||
)
|
||||
const input = {
|
||||
tupleId,
|
||||
descriptor: fileReference(
|
||||
`orca-ssh-relay-runtime-${tupleId}.manifest-tuple.json`,
|
||||
descriptorBytes
|
||||
),
|
||||
archive: fileReference(tuple.archive.name, archiveBytes),
|
||||
sbom: { ...tuple.metadataAssets.sbom },
|
||||
provenance: { ...tuple.metadataAssets.provenance }
|
||||
}
|
||||
const artifactName = tupleId.startsWith('linux-')
|
||||
? `ssh-relay-runtime-${tupleId}`
|
||||
: `ssh-relay-runtime-signed-${tupleId}`
|
||||
const artifactRoot = join(root, artifactName)
|
||||
const assetsRoot = tupleId.startsWith('linux-') ? artifactRoot : join(artifactRoot, 'assets')
|
||||
const evidenceRoot = tupleId.startsWith('linux-') ? artifactRoot : join(artifactRoot, 'evidence')
|
||||
await mkdir(assetsRoot, { recursive: true })
|
||||
await mkdir(evidenceRoot, { recursive: true })
|
||||
await Promise.all([
|
||||
writeFile(join(assetsRoot, input.descriptor.name), descriptorBytes),
|
||||
writeFile(join(assetsRoot, input.archive.name), archiveBytes),
|
||||
writeFile(join(assetsRoot, input.sbom.name), sbomBytes),
|
||||
writeFile(join(assetsRoot, input.provenance.name), provenanceBytes)
|
||||
])
|
||||
const receipt = tupleId.startsWith('linux-')
|
||||
? { tupleId, contentId: tuple.contentId, verification: {}, aggregateInput: input }
|
||||
: {
|
||||
tupleId,
|
||||
sourceContentId: tuple.contentId,
|
||||
finalContentId: tuple.contentId,
|
||||
returnedFiles: [],
|
||||
nativeVerification: tuple.nativeVerification,
|
||||
smoke: {},
|
||||
metadata: {},
|
||||
aggregateInput: input
|
||||
}
|
||||
const suffix = tupleId.startsWith('linux-') ? 'linux-finalization' : 'finalization'
|
||||
await writeFile(join(evidenceRoot, `${tupleId}.${suffix}.json`), `${JSON.stringify(receipt)}\n`)
|
||||
return { artifactRoot, assetsRoot, evidenceRoot, input, tuple }
|
||||
}
|
||||
|
||||
async function fixture() {
|
||||
const root = await mkdtemp(join(tmpdir(), 'orca-manifest-command-'))
|
||||
temporaryDirectories.push(root)
|
||||
const artifactsDirectory = join(root, 'artifacts')
|
||||
await mkdir(artifactsDirectory)
|
||||
const tuples = []
|
||||
for (const tupleId of TUPLES) {
|
||||
tuples.push(await writeTupleArtifact(artifactsDirectory, tupleId))
|
||||
}
|
||||
const acceptedKeysPath = join(root, 'accepted-keys.json')
|
||||
const acceptedKeys = {
|
||||
schemaVersion: 1,
|
||||
keys: [
|
||||
{
|
||||
keyId: sshRelayRuntimeManifestKeyId(keyPair.publicKey),
|
||||
publicKeyBase64: Buffer.from(keyPair.publicKey).toString('base64')
|
||||
}
|
||||
]
|
||||
}
|
||||
await writeFile(acceptedKeysPath, `${JSON.stringify(acceptedKeys)}\n`)
|
||||
return {
|
||||
root,
|
||||
artifactsDirectory,
|
||||
acceptedKeysPath,
|
||||
acceptedKeys,
|
||||
tuples,
|
||||
prepareOutput: join(root, 'prepared'),
|
||||
finalOutput: join(root, 'final'),
|
||||
sourceSha: 'a'.repeat(40),
|
||||
releaseTag: 'v1.4.140-rc.1',
|
||||
createdAt: '2026-07-15T01:02:03.000Z',
|
||||
relayProtocolVersion: 1
|
||||
}
|
||||
}
|
||||
|
||||
function commandInput(value, outputDirectory = value.prepareOutput) {
|
||||
return {
|
||||
artifactsDirectory: value.artifactsDirectory,
|
||||
acceptedKeysPath: value.acceptedKeysPath,
|
||||
outputDirectory,
|
||||
sourceSha: value.sourceSha,
|
||||
releaseTag: value.releaseTag,
|
||||
createdAt: value.createdAt,
|
||||
relayProtocolVersion: value.relayProtocolVersion
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(
|
||||
temporaryDirectories
|
||||
.splice(0)
|
||||
.map((directory) => rm(directory, { recursive: true, force: true }))
|
||||
)
|
||||
})
|
||||
|
||||
describe('SSH relay runtime manifest aggregate filesystem command', () => {
|
||||
it('independently recollects six receipt-bound artifacts around signature-only handoff', async () => {
|
||||
const value = await fixture()
|
||||
const prepared = await prepareSshRelayRuntimeManifestAggregateCommand(commandInput(value))
|
||||
const requestArtifact = JSON.parse(
|
||||
await readFile(join(value.prepareOutput, 'signing-request.json'), 'utf8')
|
||||
)
|
||||
expect(requestArtifact).toEqual(
|
||||
encodeSshRelayRuntimeManifestSigningRequestArtifact(prepared.signingRequest)
|
||||
)
|
||||
const signingResult = signSshRelayRuntimeManifestRequest({
|
||||
requestArtifact,
|
||||
seedBase64: Buffer.from(keyPair.secretKey.subarray(0, 32)).toString('base64')
|
||||
})
|
||||
const signingResultPath = join(value.root, 'signing-result.json')
|
||||
await writeFile(signingResultPath, `${JSON.stringify(signingResult)}\n`)
|
||||
const finalized = await finalizeSshRelayRuntimeManifestAggregateCommand({
|
||||
...commandInput(value, value.finalOutput),
|
||||
preparedDirectory: value.prepareOutput,
|
||||
signingResultPath
|
||||
})
|
||||
|
||||
expect(finalized.manifest.tuples.map((tuple) => tuple.tupleId)).toEqual(TUPLES.toSorted())
|
||||
expect(
|
||||
verifySshRelayArtifactManifest(finalized.manifest, [
|
||||
{ keyId: value.acceptedKeys.keys[0].keyId, publicKey: keyPair.publicKey }
|
||||
])
|
||||
).toEqual(finalized.manifest)
|
||||
const finalEntries = (await lstat(value.finalOutput)).isDirectory()
|
||||
expect(finalEntries).toBe(true)
|
||||
await expect(lstat(join(value.finalOutput, 'aggregate-input'))).rejects.toMatchObject({
|
||||
code: 'ENOENT'
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects missing, extra, mutated, duplicate, and receipt-drifted artifact inputs', async () => {
|
||||
const missing = await fixture()
|
||||
await rm(missing.tuples[0].artifactRoot, { recursive: true })
|
||||
await expect(
|
||||
prepareSshRelayRuntimeManifestAggregateCommand(commandInput(missing))
|
||||
).rejects.toThrow(/missing|unexpected/i)
|
||||
|
||||
const extra = await fixture()
|
||||
await mkdir(join(extra.artifactsDirectory, 'ssh-relay-runtime-extra'))
|
||||
await expect(
|
||||
prepareSshRelayRuntimeManifestAggregateCommand(commandInput(extra))
|
||||
).rejects.toThrow(/missing|unexpected/i)
|
||||
|
||||
const mutated = await fixture()
|
||||
await writeFile(
|
||||
join(mutated.tuples[1].assetsRoot, mutated.tuples[1].input.archive.name),
|
||||
'drift'
|
||||
)
|
||||
await expect(
|
||||
prepareSshRelayRuntimeManifestAggregateCommand(commandInput(mutated))
|
||||
).rejects.toThrow(/size|sha-?256|changed/i)
|
||||
|
||||
const receiptDrift = await fixture()
|
||||
const receiptPath = join(receiptDrift.tuples[2].evidenceRoot, `${TUPLES[2]}.finalization.json`)
|
||||
const document = JSON.parse(await readFile(receiptPath, 'utf8'))
|
||||
document.tupleId = TUPLES[3]
|
||||
await writeFile(receiptPath, `${JSON.stringify(document)}\n`)
|
||||
await expect(
|
||||
prepareSshRelayRuntimeManifestAggregateCommand(commandInput(receiptDrift))
|
||||
).rejects.toThrow(/receipt|tuple/i)
|
||||
|
||||
const duplicate = await fixture()
|
||||
const duplicateReceiptPath = join(
|
||||
duplicate.tuples[1].evidenceRoot,
|
||||
`${TUPLES[1]}.linux-finalization.json`
|
||||
)
|
||||
const duplicateReceipt = JSON.parse(await readFile(duplicateReceiptPath, 'utf8'))
|
||||
duplicateReceipt.aggregateInput.archive.name = duplicate.tuples[0].input.archive.name
|
||||
await writeFile(duplicateReceiptPath, `${JSON.stringify(duplicateReceipt)}\n`)
|
||||
await expect(
|
||||
prepareSshRelayRuntimeManifestAggregateCommand(commandInput(duplicate))
|
||||
).rejects.toThrow(/duplicate/i)
|
||||
})
|
||||
|
||||
it.skipIf(process.platform === 'win32')('rejects linked finalization receipts', async () => {
|
||||
const linked = await fixture()
|
||||
const receipt = join(linked.tuples[0].evidenceRoot, `${TUPLES[0]}.linux-finalization.json`)
|
||||
const realReceipt = `${receipt}.real`
|
||||
await writeFile(realReceipt, await readFile(receipt))
|
||||
await rm(receipt)
|
||||
await symlink(realReceipt, receipt)
|
||||
await expect(
|
||||
prepareSshRelayRuntimeManifestAggregateCommand(commandInput(linked))
|
||||
).rejects.toThrow(/regular|symbolic|link/i)
|
||||
})
|
||||
|
||||
it('validates accepted keys before exposing a request and removes partial final output', async () => {
|
||||
const invalidKeys = await fixture()
|
||||
invalidKeys.acceptedKeys.keys[0].publicKeyBase64 = Buffer.alloc(31).toString('base64')
|
||||
await writeFile(invalidKeys.acceptedKeysPath, JSON.stringify(invalidKeys.acceptedKeys))
|
||||
await expect(
|
||||
prepareSshRelayRuntimeManifestAggregateCommand(commandInput(invalidKeys))
|
||||
).rejects.toThrow(/public key|32 bytes|accepted key/i)
|
||||
await expect(lstat(invalidKeys.prepareOutput)).rejects.toMatchObject({ code: 'ENOENT' })
|
||||
|
||||
const invalidSignature = await fixture()
|
||||
await prepareSshRelayRuntimeManifestAggregateCommand(commandInput(invalidSignature))
|
||||
const signingResultPath = join(invalidSignature.root, 'signing-result.json')
|
||||
await writeFile(
|
||||
signingResultPath,
|
||||
JSON.stringify({
|
||||
keyId: invalidSignature.acceptedKeys.keys[0].keyId,
|
||||
signature: Buffer.alloc(64).toString('base64')
|
||||
})
|
||||
)
|
||||
await expect(
|
||||
finalizeSshRelayRuntimeManifestAggregateCommand({
|
||||
...commandInput(invalidSignature, invalidSignature.finalOutput),
|
||||
preparedDirectory: invalidSignature.prepareOutput,
|
||||
signingResultPath
|
||||
})
|
||||
).rejects.toThrow(/signature/i)
|
||||
await expect(lstat(invalidSignature.finalOutput)).rejects.toMatchObject({ code: 'ENOENT' })
|
||||
})
|
||||
|
||||
it('rejects prepared handoff drift, cancellation, and incomplete CLI arguments', async () => {
|
||||
const drifted = await fixture()
|
||||
const prepared = await prepareSshRelayRuntimeManifestAggregateCommand(commandInput(drifted))
|
||||
const requestArtifact = encodeSshRelayRuntimeManifestSigningRequestArtifact(
|
||||
prepared.signingRequest
|
||||
)
|
||||
const signingResultPath = join(drifted.root, 'signing-result.json')
|
||||
await writeFile(
|
||||
signingResultPath,
|
||||
JSON.stringify({
|
||||
...signSshRelayRuntimeManifestRequest({
|
||||
requestArtifact,
|
||||
seedBase64: Buffer.from(keyPair.secretKey.subarray(0, 32)).toString('base64')
|
||||
})
|
||||
})
|
||||
)
|
||||
const preparedPath = join(drifted.prepareOutput, 'prepared-aggregate.json')
|
||||
const preparedDocument = JSON.parse(await readFile(preparedPath, 'utf8'))
|
||||
preparedDocument.sourceSha = 'b'.repeat(40)
|
||||
await writeFile(preparedPath, `${JSON.stringify(preparedDocument)}\n`)
|
||||
await expect(
|
||||
finalizeSshRelayRuntimeManifestAggregateCommand({
|
||||
...commandInput(drifted, drifted.finalOutput),
|
||||
preparedDirectory: drifted.prepareOutput,
|
||||
signingResultPath
|
||||
})
|
||||
).rejects.toThrow(/prepared|drift/i)
|
||||
await expect(lstat(drifted.finalOutput)).rejects.toMatchObject({ code: 'ENOENT' })
|
||||
|
||||
const cancelled = await fixture()
|
||||
const controller = new AbortController()
|
||||
controller.abort(new Error('cancel aggregate collection'))
|
||||
await expect(
|
||||
prepareSshRelayRuntimeManifestAggregateCommand({
|
||||
...commandInput(cancelled),
|
||||
signal: controller.signal
|
||||
})
|
||||
).rejects.toThrow(/cancel aggregate collection/i)
|
||||
await expect(lstat(cancelled.prepareOutput)).rejects.toMatchObject({ code: 'ENOENT' })
|
||||
|
||||
expect(() => parseSshRelayRuntimeManifestAggregateCommandArguments(['prepare'])).toThrow(
|
||||
/missing/i
|
||||
)
|
||||
expect(() =>
|
||||
parseSshRelayRuntimeManifestAggregateCommandArguments(['finalize', '--unknown', 'value'])
|
||||
).toThrow(/invalid/i)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,236 @@
|
||||
import { constants } from 'node:fs'
|
||||
import { copyFile, lstat, readFile, readdir } from 'node:fs/promises'
|
||||
import { basename, join, resolve } from 'node:path'
|
||||
|
||||
const MAX_RECEIPT_BYTES = 32 * 1024 * 1024
|
||||
const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,239}$/u
|
||||
const DIGEST = /^sha256:[0-9a-f]{64}$/u
|
||||
const INPUT_FIELDS = ['archive', 'descriptor', 'provenance', 'sbom', 'tupleId']
|
||||
const FILE_FIELDS = ['name', 'sha256', 'size']
|
||||
|
||||
export const SSH_RELAY_RUNTIME_MANIFEST_ARTIFACTS = Object.freeze([
|
||||
{
|
||||
tupleId: 'linux-x64-glibc',
|
||||
artifactName: 'ssh-relay-runtime-linux-x64-glibc',
|
||||
layout: 'flat'
|
||||
},
|
||||
{
|
||||
tupleId: 'linux-arm64-glibc',
|
||||
artifactName: 'ssh-relay-runtime-linux-arm64-glibc',
|
||||
layout: 'flat'
|
||||
},
|
||||
{
|
||||
tupleId: 'darwin-x64',
|
||||
artifactName: 'ssh-relay-runtime-signed-darwin-x64',
|
||||
layout: 'signed'
|
||||
},
|
||||
{
|
||||
tupleId: 'darwin-arm64',
|
||||
artifactName: 'ssh-relay-runtime-signed-darwin-arm64',
|
||||
layout: 'signed'
|
||||
},
|
||||
{
|
||||
tupleId: 'win32-x64',
|
||||
artifactName: 'ssh-relay-runtime-signed-win32-x64',
|
||||
layout: 'signed'
|
||||
},
|
||||
{
|
||||
tupleId: 'win32-arm64',
|
||||
artifactName: 'ssh-relay-runtime-signed-win32-arm64',
|
||||
layout: 'signed'
|
||||
}
|
||||
])
|
||||
|
||||
function assertObject(value, label) {
|
||||
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
||||
throw new Error(`SSH relay runtime manifest artifact ${label} must be an object`)
|
||||
}
|
||||
}
|
||||
|
||||
function assertExactFields(value, fields, label) {
|
||||
assertObject(value, label)
|
||||
const actual = Object.keys(value).sort()
|
||||
const expected = [...fields].sort()
|
||||
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
||||
throw new Error(`SSH relay runtime manifest artifact ${label} has invalid fields`)
|
||||
}
|
||||
}
|
||||
|
||||
function stableState(before, after) {
|
||||
return (
|
||||
before.dev === after.dev &&
|
||||
before.ino === after.ino &&
|
||||
before.size === after.size &&
|
||||
before.mtimeNs === after.mtimeNs &&
|
||||
before.ctimeNs === after.ctimeNs
|
||||
)
|
||||
}
|
||||
|
||||
async function realDirectory(path, label) {
|
||||
const metadata = await lstat(path)
|
||||
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
|
||||
throw new Error(`SSH relay runtime manifest artifact ${label} must be a real directory`)
|
||||
}
|
||||
return resolve(path)
|
||||
}
|
||||
|
||||
async function readStableJson(path, label, signal) {
|
||||
signal?.throwIfAborted()
|
||||
const before = await lstat(path, { bigint: true })
|
||||
if (
|
||||
!before.isFile() ||
|
||||
before.isSymbolicLink() ||
|
||||
before.size <= 0n ||
|
||||
before.size > BigInt(MAX_RECEIPT_BYTES)
|
||||
) {
|
||||
throw new Error(`SSH relay runtime manifest artifact ${label} must be bounded regular JSON`)
|
||||
}
|
||||
const bytes = await readFile(path, { signal })
|
||||
const after = await lstat(path, { bigint: true })
|
||||
if (!stableState(before, after)) {
|
||||
throw new Error(`SSH relay runtime manifest artifact ${label} changed while reading`)
|
||||
}
|
||||
try {
|
||||
return JSON.parse(bytes.toString('utf8'))
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`SSH relay runtime manifest artifact ${label} is invalid JSON: ${error.message}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeFile(value, label) {
|
||||
assertExactFields(value, FILE_FIELDS, label)
|
||||
if (
|
||||
typeof value.name !== 'string' ||
|
||||
basename(value.name) !== value.name ||
|
||||
!SAFE_NAME.test(value.name)
|
||||
) {
|
||||
throw new Error(`SSH relay runtime manifest artifact ${label} has an unsafe name`)
|
||||
}
|
||||
if (!DIGEST.test(value.sha256)) {
|
||||
throw new Error(`SSH relay runtime manifest artifact ${label} has an invalid SHA-256`)
|
||||
}
|
||||
if (!Number.isSafeInteger(value.size) || value.size <= 0) {
|
||||
throw new Error(`SSH relay runtime manifest artifact ${label} has an invalid size`)
|
||||
}
|
||||
return { name: value.name, sha256: value.sha256, size: value.size }
|
||||
}
|
||||
|
||||
function normalizeAggregateInput(value, expectedTupleId) {
|
||||
assertExactFields(value, INPUT_FIELDS, `${expectedTupleId} aggregate input`)
|
||||
if (value.tupleId !== expectedTupleId) {
|
||||
throw new Error(`SSH relay runtime manifest artifact receipt tuple drifted: ${expectedTupleId}`)
|
||||
}
|
||||
return {
|
||||
tupleId: expectedTupleId,
|
||||
descriptor: normalizeFile(value.descriptor, `${expectedTupleId} descriptor`),
|
||||
archive: normalizeFile(value.archive, `${expectedTupleId} archive`),
|
||||
sbom: normalizeFile(value.sbom, `${expectedTupleId} SBOM`),
|
||||
provenance: normalizeFile(value.provenance, `${expectedTupleId} provenance`)
|
||||
}
|
||||
}
|
||||
|
||||
function receiptBinding(receipt, artifact) {
|
||||
assertObject(receipt, `${artifact.tupleId} finalization receipt`)
|
||||
if (receipt.tupleId !== artifact.tupleId) {
|
||||
throw new Error(
|
||||
`SSH relay runtime manifest artifact receipt tuple drifted: ${artifact.tupleId}`
|
||||
)
|
||||
}
|
||||
const contentId = artifact.layout === 'flat' ? receipt.contentId : receipt.finalContentId
|
||||
if (!DIGEST.test(contentId ?? '')) {
|
||||
throw new Error(
|
||||
`SSH relay runtime manifest artifact receipt content drifted: ${artifact.tupleId}`
|
||||
)
|
||||
}
|
||||
return {
|
||||
tupleId: artifact.tupleId,
|
||||
contentId,
|
||||
aggregateInput: normalizeAggregateInput(receipt.aggregateInput, artifact.tupleId)
|
||||
}
|
||||
}
|
||||
|
||||
async function copyStableFile(source, destination, file, signal) {
|
||||
signal?.throwIfAborted()
|
||||
const before = await lstat(source, { bigint: true })
|
||||
if (!before.isFile() || before.isSymbolicLink()) {
|
||||
throw new Error(
|
||||
`SSH relay runtime manifest artifact input is not the declared regular file: ${file.name}`
|
||||
)
|
||||
}
|
||||
if (before.size !== BigInt(file.size)) {
|
||||
throw new Error(`SSH relay runtime manifest artifact input size mismatch: ${file.name}`)
|
||||
}
|
||||
await copyFile(source, destination, constants.COPYFILE_EXCL)
|
||||
const after = await lstat(source, { bigint: true })
|
||||
if (!stableState(before, after)) {
|
||||
throw new Error(`SSH relay runtime manifest artifact input changed while copying: ${file.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function assertExactArtifactDirectories(root) {
|
||||
const entries = await readdir(root, { withFileTypes: true })
|
||||
const actual = entries.map((entry) => entry.name).sort()
|
||||
const expected = SSH_RELAY_RUNTIME_MANIFEST_ARTIFACTS.map(
|
||||
({ artifactName }) => artifactName
|
||||
).sort()
|
||||
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
||||
throw new Error(
|
||||
'SSH relay runtime manifest artifact root has missing or unexpected directories'
|
||||
)
|
||||
}
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory() || entry.isSymbolicLink()) {
|
||||
throw new Error(`SSH relay runtime manifest artifact is not a real directory: ${entry.name}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function collectSshRelayRuntimeManifestArtifacts({
|
||||
artifactsDirectory,
|
||||
stagingDirectory,
|
||||
signal
|
||||
}) {
|
||||
const root = await realDirectory(resolve(artifactsDirectory), 'root')
|
||||
const staging = await realDirectory(resolve(stagingDirectory), 'staging directory')
|
||||
await assertExactArtifactDirectories(root)
|
||||
const names = new Set()
|
||||
const bindings = []
|
||||
for (const artifact of SSH_RELAY_RUNTIME_MANIFEST_ARTIFACTS) {
|
||||
signal?.throwIfAborted()
|
||||
const artifactRoot = await realDirectory(
|
||||
join(root, artifact.artifactName),
|
||||
artifact.artifactName
|
||||
)
|
||||
const assetsRoot =
|
||||
artifact.layout === 'flat'
|
||||
? artifactRoot
|
||||
: await realDirectory(join(artifactRoot, 'assets'), `${artifact.tupleId} assets`)
|
||||
const evidenceRoot =
|
||||
artifact.layout === 'flat'
|
||||
? artifactRoot
|
||||
: await realDirectory(join(artifactRoot, 'evidence'), `${artifact.tupleId} evidence`)
|
||||
const suffix = artifact.layout === 'flat' ? 'linux-finalization' : 'finalization'
|
||||
const receipt = await readStableJson(
|
||||
join(evidenceRoot, `${artifact.tupleId}.${suffix}.json`),
|
||||
`${artifact.tupleId} receipt`,
|
||||
signal
|
||||
)
|
||||
const binding = receiptBinding(receipt, artifact)
|
||||
for (const file of [
|
||||
binding.aggregateInput.descriptor,
|
||||
binding.aggregateInput.archive,
|
||||
binding.aggregateInput.sbom,
|
||||
binding.aggregateInput.provenance
|
||||
]) {
|
||||
if (names.has(file.name)) {
|
||||
throw new Error(`SSH relay runtime manifest artifact has a duplicate file: ${file.name}`)
|
||||
}
|
||||
names.add(file.name)
|
||||
await copyStableFile(join(assetsRoot, file.name), join(staging, file.name), file, signal)
|
||||
}
|
||||
bindings.push(binding)
|
||||
}
|
||||
return bindings
|
||||
}
|
||||
Reference in New Issue
Block a user