mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 16:02:55 +00:00
build(ssh): apply native signing returns safely
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -135,6 +135,8 @@ jobs:
|
||||
node --check config/scripts/ssh-relay-runtime-windows-authenticode-assessment.test.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-native-signing-stage.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-native-signing-stage.test.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-native-signing-apply.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-native-signing-apply.test.mjs
|
||||
pnpm exec vitest run --config config/vitest.config.ts \
|
||||
config/scripts/ssh-relay-node-release-verification.test.mjs \
|
||||
config/scripts/ssh-relay-node-tar-inspection.test.mjs \
|
||||
@@ -152,6 +154,7 @@ jobs:
|
||||
config/scripts/ssh-relay-runtime-native-signing-payload.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-windows-authenticode-assessment.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-native-signing-stage.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-native-signing-apply.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-closure.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-pty-smoke.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-reproducibility.test.mjs \
|
||||
@@ -451,6 +454,10 @@ jobs:
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime native signing stage script syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-native-signing-stage.test.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime native signing stage test syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-native-signing-apply.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime native signing apply script syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-native-signing-apply.test.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime native signing apply test syntax check failed' }
|
||||
pnpm exec vitest run --config config/vitest.config.ts `
|
||||
config/scripts/ssh-relay-node-release-verification.test.mjs `
|
||||
config/scripts/ssh-relay-node-tar-inspection.test.mjs `
|
||||
@@ -468,6 +475,7 @@ jobs:
|
||||
config/scripts/ssh-relay-runtime-native-signing-payload.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-windows-authenticode-assessment.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-native-signing-stage.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-native-signing-apply.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-closure.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-pty-smoke.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-reproducibility.test.mjs `
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
import { constants } from 'node:fs'
|
||||
import { chmod, copyFile, lstat, mkdir, realpath, rm } from 'node:fs/promises'
|
||||
import { basename, dirname, isAbsolute, relative, resolve, sep } from 'node:path'
|
||||
import { isDeepStrictEqual } from 'node:util'
|
||||
|
||||
import { assertSshRelayRuntimeClosureEntries } from './ssh-relay-runtime-closure.mjs'
|
||||
import { computeSshRelayRuntimeContentId } from './ssh-relay-runtime-identity.mjs'
|
||||
import { verifySshRelayRuntimeNativeSigningReturn } from './ssh-relay-runtime-native-signing-payload.mjs'
|
||||
import { buildSshRelayRuntimeNativeSigningSelection } from './ssh-relay-runtime-native-signing-selection.mjs'
|
||||
import { verifyRuntimeTree } from './verify-ssh-relay-runtime.mjs'
|
||||
|
||||
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.isSymbolicLink() || !metadata.isDirectory()) {
|
||||
throw new Error(`Runtime native signing ${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 native signing apply requires an exclusive output root')
|
||||
}
|
||||
|
||||
export async function assertSshRelayRuntimeNativeSigningApplyRoots({
|
||||
sourceRuntimeRoot,
|
||||
returnedRoot,
|
||||
outputRuntimeRoot
|
||||
}) {
|
||||
const absoluteOutput = resolve(outputRuntimeRoot)
|
||||
const [physicalSource, physicalReturned, physicalOutputParent] = await Promise.all([
|
||||
physicalDirectory(resolve(sourceRuntimeRoot), 'source root'),
|
||||
physicalDirectory(resolve(returnedRoot), 'returned root'),
|
||||
physicalDirectory(dirname(absoluteOutput), 'output parent')
|
||||
])
|
||||
const physicalOutput = resolve(physicalOutputParent, basename(absoluteOutput))
|
||||
const roots = [physicalSource, physicalReturned, physicalOutput]
|
||||
for (let left = 0; left < roots.length; left += 1) {
|
||||
for (let right = left + 1; right < roots.length; right += 1) {
|
||||
if (containsPath(roots[left], roots[right]) || containsPath(roots[right], roots[left])) {
|
||||
throw new Error('Runtime native signing apply roots must be physically disjoint')
|
||||
}
|
||||
}
|
||||
}
|
||||
await assertAbsent(physicalOutput)
|
||||
return { physicalSource, physicalReturned, physicalOutput }
|
||||
}
|
||||
|
||||
function localPath(root, portablePath) {
|
||||
return resolve(root, ...portablePath.split('/'))
|
||||
}
|
||||
|
||||
function assertIdentityBoundSelection(identity, selection) {
|
||||
if (
|
||||
!selection ||
|
||||
typeof selection !== 'object' ||
|
||||
!Array.isArray(selection.signingFiles) ||
|
||||
!Array.isArray(selection.preservedUpstreamFiles)
|
||||
) {
|
||||
throw new Error('Runtime native signing apply requires a complete selection')
|
||||
}
|
||||
const assessments =
|
||||
identity.os === 'win32'
|
||||
? [
|
||||
...selection.signingFiles.map((entry) => ({
|
||||
path: entry.path,
|
||||
sourceSha256: entry.sourceSha256,
|
||||
status: 'unsigned'
|
||||
})),
|
||||
...selection.preservedUpstreamFiles.map((entry) => ({
|
||||
path: entry.path,
|
||||
sourceSha256: entry.sourceSha256,
|
||||
status: 'valid-upstream',
|
||||
signerSubject: entry.signerSubject,
|
||||
signerThumbprint: entry.signerThumbprint
|
||||
}))
|
||||
]
|
||||
: []
|
||||
const expected = buildSshRelayRuntimeNativeSigningSelection(identity, assessments)
|
||||
if (!isDeepStrictEqual(selection, expected)) {
|
||||
// Why: signing selections cross a job boundary and must remain bound to the authenticated tree.
|
||||
throw new Error('Runtime native signing apply selection and identity disagree')
|
||||
}
|
||||
}
|
||||
|
||||
function finalIdentity(identity, returnedFiles) {
|
||||
const returned = new Map(returnedFiles.map((entry) => [entry.path, entry]))
|
||||
const entries = identity.entries.map((entry) => {
|
||||
const signed = returned.get(entry.path)
|
||||
return signed
|
||||
? { ...entry, size: signed.signedSize, sha256: signed.signedSha256 }
|
||||
: { ...entry }
|
||||
})
|
||||
const files = entries.filter((entry) => entry.type === 'file')
|
||||
const {
|
||||
archive: _archive,
|
||||
contentId: _contentId,
|
||||
expandedSize: _size,
|
||||
fileCount: _count,
|
||||
...base
|
||||
} = identity
|
||||
const candidate = {
|
||||
...base,
|
||||
entries,
|
||||
fileCount: files.length,
|
||||
expandedSize: files.reduce((total, entry) => total + entry.size, 0)
|
||||
}
|
||||
return { ...candidate, contentId: computeSshRelayRuntimeContentId(candidate) }
|
||||
}
|
||||
|
||||
async function copyRuntimeTree({
|
||||
identity,
|
||||
returnedFiles,
|
||||
physicalSource,
|
||||
physicalReturned,
|
||||
physicalOutput,
|
||||
copyFileImpl
|
||||
}) {
|
||||
const returnedPaths = new Set(returnedFiles.map((entry) => entry.path))
|
||||
const directories = identity.entries
|
||||
.filter((entry) => entry.type === 'directory')
|
||||
.sort((left, right) => left.path.split('/').length - right.path.split('/').length)
|
||||
for (const entry of directories) {
|
||||
const destination = localPath(physicalOutput, entry.path)
|
||||
await mkdir(destination, { mode: entry.mode })
|
||||
if (process.platform !== 'win32') {
|
||||
await chmod(destination, entry.mode)
|
||||
}
|
||||
}
|
||||
for (const entry of identity.entries.filter((candidate) => candidate.type === 'file')) {
|
||||
const sourceRoot = returnedPaths.has(entry.path) ? physicalReturned : physicalSource
|
||||
const destination = localPath(physicalOutput, entry.path)
|
||||
await copyFileImpl(localPath(sourceRoot, entry.path), destination, constants.COPYFILE_EXCL)
|
||||
if (process.platform !== 'win32') {
|
||||
await chmod(destination, entry.mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function applySshRelayRuntimeNativeSigningReturn({
|
||||
sourceRuntimeRoot,
|
||||
returnedRoot,
|
||||
outputRuntimeRoot,
|
||||
identity,
|
||||
selection,
|
||||
copyFileImpl = copyFile
|
||||
}) {
|
||||
assertSshRelayRuntimeClosureEntries(identity)
|
||||
assertIdentityBoundSelection(identity, selection)
|
||||
if (selection.signingFiles.length === 0) {
|
||||
throw new Error('Runtime native signing apply selection has no signing files')
|
||||
}
|
||||
const roots = await assertSshRelayRuntimeNativeSigningApplyRoots({
|
||||
sourceRuntimeRoot,
|
||||
returnedRoot,
|
||||
outputRuntimeRoot
|
||||
})
|
||||
await verifyRuntimeTree(roots.physicalSource, identity)
|
||||
const returned = await verifySshRelayRuntimeNativeSigningReturn({
|
||||
returnedRoot: roots.physicalReturned,
|
||||
selection
|
||||
})
|
||||
const candidateIdentity = finalIdentity(identity, returned.returnedFiles)
|
||||
if (candidateIdentity.contentId === identity.contentId) {
|
||||
throw new Error('Runtime native signing apply did not change content identity')
|
||||
}
|
||||
assertSshRelayRuntimeClosureEntries(candidateIdentity)
|
||||
|
||||
let outputCreated = false
|
||||
try {
|
||||
await mkdir(roots.physicalOutput)
|
||||
outputCreated = true
|
||||
await copyRuntimeTree({
|
||||
identity,
|
||||
returnedFiles: returned.returnedFiles,
|
||||
...roots,
|
||||
copyFileImpl
|
||||
})
|
||||
await verifyRuntimeTree(roots.physicalOutput, candidateIdentity)
|
||||
return {
|
||||
tupleId: identity.tupleId,
|
||||
sourceContentId: identity.contentId,
|
||||
finalContentId: candidateIdentity.contentId,
|
||||
returnedFiles: returned.returnedFiles,
|
||||
identity: candidateIdentity
|
||||
}
|
||||
} catch (error) {
|
||||
if (outputCreated) {
|
||||
// Why: an incomplete post-sign tree must never survive as a launchable or archivable candidate.
|
||||
await rm(roots.physicalOutput, { recursive: true, force: true })
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import {
|
||||
appendFile,
|
||||
copyFile,
|
||||
cp,
|
||||
lstat,
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
readFile,
|
||||
rm,
|
||||
writeFile
|
||||
} from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { expectedSshRelayRuntimeClosureEntries } from './ssh-relay-runtime-closure.mjs'
|
||||
import { computeSshRelayRuntimeContentId } from './ssh-relay-runtime-identity.mjs'
|
||||
import {
|
||||
applySshRelayRuntimeNativeSigningReturn,
|
||||
assertSshRelayRuntimeNativeSigningApplyRoots
|
||||
} from './ssh-relay-runtime-native-signing-apply.mjs'
|
||||
import { buildSshRelayRuntimeNativeSigningPlan } from './ssh-relay-runtime-native-signing-plan.mjs'
|
||||
import { buildSshRelayRuntimeNativeSigningSelection } from './ssh-relay-runtime-native-signing-selection.mjs'
|
||||
import { sshRelayRuntimeCompatibility } from './ssh-relay-runtime-compatibility.mjs'
|
||||
|
||||
function digest(bytes) {
|
||||
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`
|
||||
}
|
||||
|
||||
function platformForTuple(tupleId) {
|
||||
return tupleId.startsWith('linux-') ? 'linux' : tupleId.startsWith('darwin-') ? 'darwin' : 'win32'
|
||||
}
|
||||
|
||||
async function runtimeFixture(tupleId) {
|
||||
const root = await mkdtemp(join(tmpdir(), 'ssh-relay-native-signing-apply-'))
|
||||
const runtimeRoot = join(root, 'runtime')
|
||||
await mkdir(runtimeRoot)
|
||||
const entries = []
|
||||
for (const entry of expectedSshRelayRuntimeClosureEntries(tupleId)) {
|
||||
if (entry.type === 'directory') {
|
||||
await mkdir(join(runtimeRoot, ...entry.path.split('/')), {
|
||||
recursive: true,
|
||||
mode: entry.mode
|
||||
})
|
||||
entries.push(entry)
|
||||
continue
|
||||
}
|
||||
const bytes = Buffer.from(`fixture:${tupleId}:${entry.path}`)
|
||||
const filePath = join(runtimeRoot, ...entry.path.split('/'))
|
||||
await mkdir(dirname(filePath), { recursive: true })
|
||||
await writeFile(filePath, bytes, { mode: entry.mode })
|
||||
entries.push({ ...entry, size: bytes.length, sha256: digest(bytes) })
|
||||
}
|
||||
const os = platformForTuple(tupleId)
|
||||
const base = {
|
||||
identitySchemaVersion: 1,
|
||||
tupleId,
|
||||
os,
|
||||
architecture: tupleId.includes('arm64') ? 'arm64' : 'x64',
|
||||
compatibility: sshRelayRuntimeCompatibility[tupleId],
|
||||
nodeVersion: '24.18.0',
|
||||
dependencies: { nodePtyVersion: '1.1.0', parcelWatcherVersion: '2.5.6' },
|
||||
entries
|
||||
}
|
||||
const files = entries.filter((entry) => entry.type === 'file')
|
||||
return {
|
||||
root,
|
||||
runtimeRoot,
|
||||
identity: {
|
||||
...base,
|
||||
archive: {
|
||||
fileName: `orca-relay-runtime-${tupleId}.fixture`,
|
||||
size: 1,
|
||||
sha256: digest('stale-unsigned-archive')
|
||||
},
|
||||
contentId: computeSshRelayRuntimeContentId(base),
|
||||
fileCount: files.length,
|
||||
expandedSize: files.reduce((total, entry) => total + entry.size, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function selectionFor(identity) {
|
||||
if (identity.os !== 'win32') {
|
||||
return buildSshRelayRuntimeNativeSigningSelection(identity, [])
|
||||
}
|
||||
const preserved = new Set([
|
||||
'node_modules/node-pty/build/Release/conpty/OpenConsole.exe',
|
||||
'node_modules/node-pty/build/Release/conpty/conpty.dll'
|
||||
])
|
||||
const assessments = buildSshRelayRuntimeNativeSigningPlan(identity).signingCandidates.map(
|
||||
(entry) =>
|
||||
preserved.has(entry.path)
|
||||
? {
|
||||
path: entry.path,
|
||||
sourceSha256: entry.sourceSha256,
|
||||
status: 'valid-upstream',
|
||||
signerSubject: 'CN=Microsoft Corporation',
|
||||
signerThumbprint: 'D'.repeat(40)
|
||||
}
|
||||
: { path: entry.path, sourceSha256: entry.sourceSha256, status: 'unsigned' }
|
||||
)
|
||||
return buildSshRelayRuntimeNativeSigningSelection(identity, assessments)
|
||||
}
|
||||
|
||||
async function returnedTree(fixture, selection) {
|
||||
const root = join(fixture.root, 'returned')
|
||||
for (const entry of selection.signingFiles) {
|
||||
const source = join(fixture.runtimeRoot, ...entry.path.split('/'))
|
||||
const destination = join(root, ...entry.path.split('/'))
|
||||
await mkdir(dirname(destination), { recursive: true })
|
||||
await cp(source, destination)
|
||||
await appendFile(destination, ':signed')
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
describe('SSH relay runtime native signing return application', () => {
|
||||
it('applies only exact returned files into a new full runtime and derives final identity', async () => {
|
||||
for (const tupleId of ['darwin-arm64', 'win32-x64']) {
|
||||
const fixture = await runtimeFixture(tupleId)
|
||||
try {
|
||||
const selection = selectionFor(fixture.identity)
|
||||
const returnedRoot = await returnedTree(fixture, selection)
|
||||
const outputRuntimeRoot = join(fixture.root, 'final-runtime')
|
||||
const originalIdentity = structuredClone(fixture.identity)
|
||||
const nodePath = fixture.identity.os === 'win32' ? 'bin/node.exe' : 'bin/node'
|
||||
const sourceNode = await readFile(join(fixture.runtimeRoot, ...nodePath.split('/')))
|
||||
const sourceSigningBytes = new Map()
|
||||
for (const entry of selection.signingFiles) {
|
||||
sourceSigningBytes.set(
|
||||
entry.path,
|
||||
await readFile(join(fixture.runtimeRoot, ...entry.path.split('/')))
|
||||
)
|
||||
}
|
||||
|
||||
const result = await applySshRelayRuntimeNativeSigningReturn({
|
||||
sourceRuntimeRoot: fixture.runtimeRoot,
|
||||
returnedRoot,
|
||||
outputRuntimeRoot,
|
||||
identity: fixture.identity,
|
||||
selection
|
||||
})
|
||||
|
||||
expect(fixture.identity).toEqual(originalIdentity)
|
||||
expect(result.identity.contentId).not.toBe(fixture.identity.contentId)
|
||||
expect(result.identity).not.toHaveProperty('archive')
|
||||
expect(result.identity.fileCount).toBe(fixture.identity.fileCount)
|
||||
expect(result.returnedFiles).toHaveLength(selection.signingFiles.length)
|
||||
expect(await readFile(join(outputRuntimeRoot, ...nodePath.split('/')))).toEqual(sourceNode)
|
||||
expect(result.identity.entries.find((entry) => entry.path === nodePath)).toEqual(
|
||||
fixture.identity.entries.find((entry) => entry.path === nodePath)
|
||||
)
|
||||
for (const returned of result.returnedFiles) {
|
||||
const finalEntry = result.identity.entries.find((entry) => entry.path === returned.path)
|
||||
expect(finalEntry).toEqual(
|
||||
expect.objectContaining({ size: returned.signedSize, sha256: returned.signedSha256 })
|
||||
)
|
||||
expect(await readFile(join(outputRuntimeRoot, ...returned.path.split('/')))).toEqual(
|
||||
await readFile(join(returnedRoot, ...returned.path.split('/')))
|
||||
)
|
||||
expect(await readFile(join(fixture.runtimeRoot, ...returned.path.split('/')))).toEqual(
|
||||
sourceSigningBytes.get(returned.path)
|
||||
)
|
||||
}
|
||||
for (const preserved of selection.preservedUpstreamFiles) {
|
||||
expect(await readFile(join(outputRuntimeRoot, ...preserved.path.split('/')))).toEqual(
|
||||
await readFile(join(fixture.runtimeRoot, ...preserved.path.split('/')))
|
||||
)
|
||||
expect(result.identity.entries.find((entry) => entry.path === preserved.path)).toEqual(
|
||||
fixture.identity.entries.find((entry) => entry.path === preserved.path)
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
await rm(fixture.root, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects existing, source-nested, returned-nested, or overlapping roots', async () => {
|
||||
const fixture = await runtimeFixture('darwin-x64')
|
||||
try {
|
||||
const selection = selectionFor(fixture.identity)
|
||||
const returnedRoot = await returnedTree(fixture, selection)
|
||||
const existing = join(fixture.root, 'existing')
|
||||
await mkdir(existing)
|
||||
await expect(
|
||||
assertSshRelayRuntimeNativeSigningApplyRoots({
|
||||
sourceRuntimeRoot: fixture.runtimeRoot,
|
||||
returnedRoot,
|
||||
outputRuntimeRoot: existing
|
||||
})
|
||||
).rejects.toThrow(/exclusive/i)
|
||||
await expect(
|
||||
assertSshRelayRuntimeNativeSigningApplyRoots({
|
||||
sourceRuntimeRoot: fixture.runtimeRoot,
|
||||
returnedRoot,
|
||||
outputRuntimeRoot: join(fixture.runtimeRoot, 'nested')
|
||||
})
|
||||
).rejects.toThrow(/disjoint/i)
|
||||
await expect(
|
||||
assertSshRelayRuntimeNativeSigningApplyRoots({
|
||||
sourceRuntimeRoot: fixture.runtimeRoot,
|
||||
returnedRoot,
|
||||
outputRuntimeRoot: join(returnedRoot, 'nested')
|
||||
})
|
||||
).rejects.toThrow(/disjoint/i)
|
||||
await expect(
|
||||
assertSshRelayRuntimeNativeSigningApplyRoots({
|
||||
sourceRuntimeRoot: fixture.runtimeRoot,
|
||||
returnedRoot: fixture.runtimeRoot,
|
||||
outputRuntimeRoot: join(fixture.root, 'output')
|
||||
})
|
||||
).rejects.toThrow(/disjoint/i)
|
||||
} finally {
|
||||
await rm(fixture.root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('removes the complete output if returned bytes change after initial verification', async () => {
|
||||
const fixture = await runtimeFixture('darwin-arm64')
|
||||
try {
|
||||
const selection = selectionFor(fixture.identity)
|
||||
const returnedRoot = await returnedTree(fixture, selection)
|
||||
const outputRuntimeRoot = join(fixture.root, 'final-runtime')
|
||||
const first = selection.signingFiles[0]
|
||||
const firstReturnedSuffix = join(...first.path.split('/'))
|
||||
let raced = false
|
||||
await expect(
|
||||
applySshRelayRuntimeNativeSigningReturn({
|
||||
sourceRuntimeRoot: fixture.runtimeRoot,
|
||||
returnedRoot,
|
||||
outputRuntimeRoot,
|
||||
identity: fixture.identity,
|
||||
selection,
|
||||
copyFileImpl: async (source, destination, mode) => {
|
||||
if (!raced && source.endsWith(firstReturnedSuffix)) {
|
||||
raced = true
|
||||
await appendFile(source, ':raced')
|
||||
}
|
||||
await copyFile(source, destination, mode)
|
||||
}
|
||||
})
|
||||
).rejects.toThrow(/integrity mismatch/i)
|
||||
await expect(lstat(outputRuntimeRoot)).rejects.toMatchObject({ code: 'ENOENT' })
|
||||
} finally {
|
||||
await rm(fixture.root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects Linux and a mismatched selection before creating output', async () => {
|
||||
const fixture = await runtimeFixture('linux-x64-glibc')
|
||||
try {
|
||||
const outputRuntimeRoot = join(fixture.root, 'final-runtime')
|
||||
await expect(
|
||||
applySshRelayRuntimeNativeSigningReturn({
|
||||
sourceRuntimeRoot: fixture.runtimeRoot,
|
||||
returnedRoot: join(fixture.root, 'missing-return'),
|
||||
outputRuntimeRoot,
|
||||
identity: fixture.identity,
|
||||
selection: selectionFor(fixture.identity)
|
||||
})
|
||||
).rejects.toThrow(/no signing files/i)
|
||||
await expect(lstat(outputRuntimeRoot)).rejects.toMatchObject({ code: 'ENOENT' })
|
||||
} finally {
|
||||
await rm(fixture.root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects a selection whose authenticated source bytes disagree with the identity', async () => {
|
||||
const fixture = await runtimeFixture('darwin-x64')
|
||||
try {
|
||||
const selection = selectionFor(fixture.identity)
|
||||
selection.signingFiles[0].sourceSha256 = `sha256:${'f'.repeat(64)}`
|
||||
const outputRuntimeRoot = join(fixture.root, 'final-runtime')
|
||||
await expect(
|
||||
applySshRelayRuntimeNativeSigningReturn({
|
||||
sourceRuntimeRoot: fixture.runtimeRoot,
|
||||
returnedRoot: join(fixture.root, 'missing-return'),
|
||||
outputRuntimeRoot,
|
||||
identity: fixture.identity,
|
||||
selection
|
||||
})
|
||||
).rejects.toThrow(/selection and identity disagree/i)
|
||||
await expect(lstat(outputRuntimeRoot)).rejects.toMatchObject({ code: 'ENOENT' })
|
||||
} finally {
|
||||
await rm(fixture.root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('removes the output if unsigned source bytes change during the copy', async () => {
|
||||
const fixture = await runtimeFixture('win32-x64')
|
||||
try {
|
||||
const selection = selectionFor(fixture.identity)
|
||||
const returnedRoot = await returnedTree(fixture, selection)
|
||||
const outputRuntimeRoot = join(fixture.root, 'final-runtime')
|
||||
const nodeSuffix = join('bin', 'node.exe')
|
||||
let raced = false
|
||||
await expect(
|
||||
applySshRelayRuntimeNativeSigningReturn({
|
||||
sourceRuntimeRoot: fixture.runtimeRoot,
|
||||
returnedRoot,
|
||||
outputRuntimeRoot,
|
||||
identity: fixture.identity,
|
||||
selection,
|
||||
copyFileImpl: async (source, destination, mode) => {
|
||||
if (!raced && source.endsWith(nodeSuffix)) {
|
||||
raced = true
|
||||
await appendFile(source, ':raced')
|
||||
}
|
||||
await copyFile(source, destination, mode)
|
||||
}
|
||||
})
|
||||
).rejects.toThrow(/integrity mismatch/i)
|
||||
await expect(lstat(outputRuntimeRoot)).rejects.toMatchObject({ code: 'ENOENT' })
|
||||
} finally {
|
||||
await rm(fixture.root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -88,6 +88,7 @@ describe('SSH relay runtime artifact workflow', () => {
|
||||
source.match(/ssh-relay-runtime-windows-authenticode-assessment\.test\.mjs/g)
|
||||
).toHaveLength(4)
|
||||
expect(source.match(/ssh-relay-runtime-native-signing-stage\.test\.mjs/g)).toHaveLength(4)
|
||||
expect(source.match(/ssh-relay-runtime-native-signing-apply\.test\.mjs/g)).toHaveLength(4)
|
||||
expect(
|
||||
source.match(/node --check config\/scripts\/ssh-relay-runtime-native-signing-plan\.mjs/g)
|
||||
).toHaveLength(2)
|
||||
@@ -105,6 +106,9 @@ describe('SSH relay runtime artifact workflow', () => {
|
||||
expect(
|
||||
source.match(/node --check config\/scripts\/ssh-relay-runtime-native-signing-stage\.mjs/g)
|
||||
).toHaveLength(2)
|
||||
expect(
|
||||
source.match(/node --check config\/scripts\/ssh-relay-runtime-native-signing-apply\.mjs/g)
|
||||
).toHaveLength(2)
|
||||
expect(
|
||||
source.match(/node --check config\/scripts\/ssh-relay-runtime-closure\.mjs/g)
|
||||
).toHaveLength(2)
|
||||
|
||||
Reference in New Issue
Block a user