feat(hololake): bind release chain to public path namespace
This commit is contained in:
parent
ba80036282
commit
9006075310
13 changed files with 223 additions and 29 deletions
|
|
@ -14,6 +14,7 @@ const tauriConfig = readJson('src-tauri/tauri.conf.json')
|
|||
const capability = readJson('src-tauri/capabilities/default.json')
|
||||
const broadcast = readJson('contracts/release-broadcast.schema.json')
|
||||
const stageOne = readJson('contracts/stage-one-platform.json')
|
||||
const publicRoute = readJson('server/release-broadcast/public-route.json')
|
||||
|
||||
test('clean Tauri foundation contains no inherited product updater endpoint', () => {
|
||||
assert.deepEqual(foundation.upstream_product_update_endpoints, [])
|
||||
|
|
@ -30,6 +31,11 @@ test('clean Tauri foundation contains no inherited product updater endpoint', ()
|
|||
const source = readText(relative)
|
||||
assert.doesNotMatch(source, /refactoringhq|tolaria|outline/i)
|
||||
}
|
||||
assert.equal(publicRoute.releaseEndpoint, 'https://guanghulab.com/hololake/releases/latest.json')
|
||||
assert.equal(publicRoute.allowedMethods.join(','), 'GET,HEAD')
|
||||
assert.equal(publicRoute.requestBodyAllowed, false)
|
||||
assert.equal(publicRoute.automaticUpload, false)
|
||||
assert.equal(publicRoute.automaticActivation, false)
|
||||
})
|
||||
|
||||
test('release activation remains explicitly human controlled', () => {
|
||||
|
|
@ -51,6 +57,12 @@ test('release activation remains explicitly human controlled', () => {
|
|||
assert.equal(foundation.release_broadcast_activation_requires_exact_human_approval, true)
|
||||
assert.equal(foundation.release_broadcast_activation_requires_repeated_expected_facts, true)
|
||||
assert.equal(foundation.release_broadcast_operator_automatic_restart_allowed, false)
|
||||
assert.equal(foundation.release_public_path_prefix, '/hololake/releases')
|
||||
assert.equal(foundation.release_public_route_deployed, false)
|
||||
assert.equal(publicRoute.publicPathPrefix, foundation.release_public_path_prefix)
|
||||
assert.equal(publicRoute.proxyRequestUriPolicy, 'PRESERVE_FULL_PUBLIC_PATH')
|
||||
assert.equal(publicRoute.frontDoorLoopbackPort, null)
|
||||
assert.equal(publicRoute.deployed, false)
|
||||
assert.equal(foundation.release_pipeline_automatic_upload_allowed, false)
|
||||
assert.equal(
|
||||
foundation.release_production_activation_state,
|
||||
|
|
|
|||
|
|
@ -165,3 +165,27 @@ test('symlinked package inputs are rejected instead of followed', () => {
|
|||
fs.rmSync(fixture.fixtureRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('operator refuses a bundle whose packages escape the registered public path', () => {
|
||||
const fixture = buildOperatorFixture()
|
||||
try {
|
||||
const latestPath = path.join(fixture.source, 'latest.json')
|
||||
const latest = JSON.parse(fs.readFileSync(latestPath, 'utf8'))
|
||||
latest.platforms['darwin-aarch64'].url = 'https://guanghulab.com/updates/0.2.0/HoloLake.app.tar.gz'
|
||||
writeJson(latestPath, latest)
|
||||
const broadcastSha256 = sha256(fs.readFileSync(latestPath))
|
||||
const pipelinePath = path.join(fixture.source, 'pipeline-receipt.json')
|
||||
const pipeline = JSON.parse(fs.readFileSync(pipelinePath, 'utf8'))
|
||||
pipeline.broadcastSha256 = broadcastSha256
|
||||
writeJson(pipelinePath, pipeline)
|
||||
const approval = JSON.parse(fs.readFileSync(fixture.approval, 'utf8'))
|
||||
approval.broadcastSha256 = broadcastSha256
|
||||
writeJson(fixture.approval, approval)
|
||||
assert.throws(
|
||||
() => verifyReleaseBundle(fixture.source, fixture.approval),
|
||||
/HOLOLAKE_RELEASE_OPERATOR_PACKAGE_PUBLIC_PREFIX_MISMATCH/,
|
||||
)
|
||||
} finally {
|
||||
fs.rmSync(fixture.fixtureRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ function buildReleaseRoot() {
|
|||
notes: 'Signed release',
|
||||
platforms: {
|
||||
'darwin-aarch64': {
|
||||
url: `https://release.guanghu.test/releases/0.2.0/${packageName}`,
|
||||
url: `https://release.guanghu.test/hololake/releases/0.2.0/${packageName}`,
|
||||
signature: 'trusted-updater-signature',
|
||||
size: packageBytes.length,
|
||||
sha256: sha256(packageBytes),
|
||||
|
|
@ -105,6 +105,10 @@ test('empty release root stays healthy but returns Tauri-compatible 204 no updat
|
|||
assert.equal(health.status, 200)
|
||||
assert.deepEqual((await health.json()).upstreamUpdateSources, [])
|
||||
assert.equal((await fetch(`${base}/latest.json`)).status, 204)
|
||||
assert.equal((await fetch(`${base}/hololake/releases/latest.json`)).status, 204)
|
||||
const publicHealth = await fetch(`${base}/hololake/releases/health`)
|
||||
assert.equal(publicHealth.status, 200)
|
||||
assert.equal((await publicHealth.json()).releaseEndpointPath, '/hololake/releases/latest.json')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -127,7 +131,10 @@ test('only an exact human-approved signed notarized evidence chain becomes reada
|
|||
const latest = await fetch(`${base}/latest.json`)
|
||||
assert.equal(latest.status, 200)
|
||||
assert.equal((await latest.json()).releaseId, 'GH-HOLOLAKE-RELEASE-0.2.0')
|
||||
const updater = await fetch(`${base}/releases/0.2.0/HoloLake.app.tar.gz`)
|
||||
const publicLatest = await fetch(`${base}/hololake/releases/latest.json`)
|
||||
assert.equal(publicLatest.status, 200)
|
||||
assert.equal((await publicLatest.json()).releaseId, 'GH-HOLOLAKE-RELEASE-0.2.0')
|
||||
const updater = await fetch(`${base}/hololake/releases/0.2.0/HoloLake.app.tar.gz`)
|
||||
assert.equal(updater.status, 200)
|
||||
assert.deepEqual(Buffer.from(await updater.arrayBuffer()), fixture.packageBytes)
|
||||
assert.equal((await fetch(`${base}/unknown`)).status, 404)
|
||||
|
|
@ -151,3 +158,27 @@ test('a symlinked immutable current directory is recognized as the intended exec
|
|||
fs.symlinkSync(versionDirectory, current)
|
||||
assert.equal(isMainModule(path.join(current, 'server.mjs'), new URL(`file://${copy}`)), true)
|
||||
})
|
||||
|
||||
test('a package outside the registered public prefix locks the release', () => {
|
||||
const fixture = buildReleaseRoot()
|
||||
const latestPath = path.join(fixture.root, 'releases', '0.2.0', 'latest.json')
|
||||
const latest = JSON.parse(fs.readFileSync(latestPath, 'utf8'))
|
||||
latest.platforms['darwin-aarch64'].url = 'https://release.guanghu.test/releases/0.2.0/HoloLake.app.tar.gz'
|
||||
writeJson(latestPath, latest)
|
||||
const broadcastSha256 = sha256(fs.readFileSync(latestPath))
|
||||
const pipelinePath = path.join(fixture.root, 'releases', '0.2.0', 'pipeline-receipt.json')
|
||||
const pipeline = JSON.parse(fs.readFileSync(pipelinePath, 'utf8'))
|
||||
pipeline.broadcastSha256 = broadcastSha256
|
||||
writeJson(pipelinePath, pipeline)
|
||||
const approvalPath = path.join(fixture.root, 'releases', '0.2.0', 'human-approval.json')
|
||||
const approval = JSON.parse(fs.readFileSync(approvalPath, 'utf8'))
|
||||
approval.broadcastSha256 = broadcastSha256
|
||||
writeJson(approvalPath, approval)
|
||||
const activationPath = path.join(fixture.root, 'ACTIVE.json')
|
||||
const activation = JSON.parse(fs.readFileSync(activationPath, 'utf8'))
|
||||
activation.broadcastSha256 = broadcastSha256
|
||||
writeJson(activationPath, activation)
|
||||
const state = loadRuntimeState(fixture.root)
|
||||
assert.equal(state.state, 'LOCKED_INVALID_RELEASE_EVIDENCE')
|
||||
assert.equal(state.reasonCode, 'HOLOLAKE_RELEASE_PACKAGE_PUBLIC_PREFIX_MISMATCH')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import path from 'node:path'
|
|||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const PUBLIC_RELEASE_ENDPOINT_PATH = '/hololake/releases/latest.json'
|
||||
const PUBLIC_RELEASE_PREFIX = '/hololake/releases'
|
||||
|
||||
const fail = (code) => {
|
||||
throw new Error(code)
|
||||
|
|
@ -28,7 +30,15 @@ export function validateReleaseTrust(trust) {
|
|||
if (!Array.isArray(trust.allowedReleaseHosts) || trust.allowedReleaseHosts.length !== 1) fail('HOLOLAKE_RELEASE_PIPELINE_EXACT_HOST_REQUIRED')
|
||||
const endpoint = new URL(trust.endpoints[0])
|
||||
const host = requireText(trust.allowedReleaseHosts[0], 'HOLOLAKE_RELEASE_PIPELINE_HOST_REQUIRED')
|
||||
if (endpoint.protocol !== 'https:' || endpoint.hostname !== host || endpoint.username || endpoint.password) {
|
||||
if (
|
||||
endpoint.protocol !== 'https:' ||
|
||||
endpoint.hostname !== host ||
|
||||
endpoint.username ||
|
||||
endpoint.password ||
|
||||
endpoint.search ||
|
||||
endpoint.hash ||
|
||||
endpoint.pathname !== PUBLIC_RELEASE_ENDPOINT_PATH
|
||||
) {
|
||||
fail('HOLOLAKE_RELEASE_PIPELINE_ENDPOINT_NOT_HOLOLAKE_HTTPS')
|
||||
}
|
||||
if (requireText(trust.publicKey, 'HOLOLAKE_RELEASE_PIPELINE_UPDATER_PUBLIC_KEY_REQUIRED').length < 32) {
|
||||
|
|
@ -50,7 +60,15 @@ export function validateReleaseInput(input, trustFacts) {
|
|||
if (!/^[a-f0-9]{40}$/.test(input.sourceCommit || '')) fail('HOLOLAKE_RELEASE_PIPELINE_SOURCE_COMMIT_INVALID')
|
||||
if (input.releaseTag !== `v${version}`) fail('HOLOLAKE_RELEASE_PIPELINE_IMMUTABLE_TAG_INVALID')
|
||||
const packageUrl = new URL(requireText(input.packageUrl, 'HOLOLAKE_RELEASE_PIPELINE_PACKAGE_URL_REQUIRED'))
|
||||
if (packageUrl.protocol !== 'https:' || packageUrl.hostname !== trustFacts.host || packageUrl.username || packageUrl.password) {
|
||||
if (
|
||||
packageUrl.protocol !== 'https:' ||
|
||||
packageUrl.hostname !== trustFacts.host ||
|
||||
packageUrl.username ||
|
||||
packageUrl.password ||
|
||||
packageUrl.search ||
|
||||
packageUrl.hash ||
|
||||
!packageUrl.pathname.startsWith(`${PUBLIC_RELEASE_PREFIX}/`)
|
||||
) {
|
||||
fail('HOLOLAKE_RELEASE_PIPELINE_PACKAGE_HOST_NOT_TRUSTED')
|
||||
}
|
||||
if (!Array.isArray(input.features) || input.features.length === 0 || input.features.some((item) => typeof item !== 'string' || !item.trim())) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
const readyTrust = () => ({
|
||||
schema: 'hololake.release-trust/v1',
|
||||
state: 'PROVISIONED',
|
||||
endpoints: ['https://release.guanghu.test/latest.json'],
|
||||
endpoints: ['https://release.guanghu.test/hololake/releases/latest.json'],
|
||||
publicKey: 'A'.repeat(64),
|
||||
allowedReleaseHosts: ['release.guanghu.test'],
|
||||
automaticCheckOnStartup: false,
|
||||
|
|
@ -28,7 +28,7 @@ const readyInput = () => ({
|
|||
releaseTag: 'v0.2.0',
|
||||
sourceCommit: 'a'.repeat(40),
|
||||
platformCode: 'darwin-aarch64',
|
||||
packageUrl: 'https://release.guanghu.test/releases/0.2.0/HoloLake.app.tar.gz',
|
||||
packageUrl: 'https://release.guanghu.test/hololake/releases/0.2.0/HoloLake.app.tar.gz',
|
||||
appleTeamIdentifier: '825A9L3G7Q',
|
||||
notes: 'Signed release',
|
||||
features: ['Persistent direct connection'],
|
||||
|
|
@ -53,6 +53,10 @@ test('release package must use the exact registered HoloLake HTTPS host and immu
|
|||
wrongHost.packageUrl = 'https://github.com/example/HoloLake.app.tar.gz'
|
||||
assert.throws(() => validateReleaseInput(wrongHost, trust), /PACKAGE_HOST_NOT_TRUSTED/)
|
||||
|
||||
const wrongPath = readyInput()
|
||||
wrongPath.packageUrl = 'https://release.guanghu.test/updates/HoloLake.app.tar.gz'
|
||||
assert.throws(() => validateReleaseInput(wrongPath, trust), /PACKAGE_HOST_NOT_TRUSTED/)
|
||||
|
||||
const wrongTag = readyInput()
|
||||
wrongTag.releaseTag = 'latest'
|
||||
assert.throws(() => validateReleaseInput(wrongTag, trust), /IMMUTABLE_TAG_INVALID/)
|
||||
|
|
|
|||
Loading…
Reference in a new issue