feat(hololake): add fail-closed JD release broadcast origin

This commit is contained in:
冰朔 2026-08-13 18:32:38 +08:00
commit f73e11b364
6 changed files with 423 additions and 2 deletions

View file

@ -0,0 +1,16 @@
# HoloLake release broadcast candidate
This is the HoloLake-owned, loopback-only origin for signed update broadcasts.
It has no upstream software feed and never uploads or activates a release.
Without `ACTIVE.json`, `/health` reports `EMPTY_FAIL_CLOSED` and `/latest.json`
returns HTTP 204, the updater protocol's explicit no-update result. An active
release is accepted only when the human activation record, immutable broadcast,
pipeline receipt, package bytes, Developer ID receipt, and Apple notarization
receipt form one exact evidence chain. Invalid evidence locks the whole release
endpoint until an operator fixes the evidence and explicitly restarts the
service.
The service listens only on `127.0.0.1`. Public HTTPS routing, updater trust-key
provisioning, artifact upload, activation, and desktop rollout are independent
deployment gates.

View file

@ -0,0 +1,35 @@
[Unit]
Description=HoloLake signed release broadcast candidate
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=hololake-release
Group=hololake-release
WorkingDirectory=/opt/guanghu/hololake-release-broadcast-candidate/current
Environment=HOLOLAKE_RELEASE_HOST=127.0.0.1
Environment=HOLOLAKE_RELEASE_PORT=3940
Environment=HOLOLAKE_RELEASE_STATE_ROOT=/var/lib/guanghu/hololake-release-broadcast
Environment=HOLOLAKE_RELEASE_ENFORCE_ROOT_OWNER=1
ExecStart=/usr/bin/node /opt/guanghu/hololake-release-broadcast-candidate/current/server.mjs
Restart=on-failure
RestartSec=5
UMask=0027
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
RestrictRealtime=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
IPAddressDeny=any
IPAddressAllow=localhost
ReadOnlyPaths=/opt/guanghu/hololake-release-broadcast-candidate
ReadOnlyPaths=/var/lib/guanghu/hololake-release-broadcast
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,236 @@
#!/usr/bin/env node
import crypto from 'node:crypto'
import fs from 'node:fs'
import http from 'node:http'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const MAX_CONTROL_BYTES = 1024 * 1024
const VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/
const SHA256_PATTERN = /^[a-f0-9]{64}$/
const fail = (code) => {
throw new Error(code)
}
const sha256 = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex')
const requireText = (value, code) => {
if (typeof value !== 'string' || value.trim() === '') fail(code)
return value.trim()
}
function trustedRegularFile(file, { enforceRootOwner, maxBytes = null }) {
const info = fs.lstatSync(file)
if (!info.isFile() || info.isSymbolicLink()) fail('HOLOLAKE_RELEASE_FILE_NOT_REGULAR')
if ((info.mode & 0o022) !== 0) fail('HOLOLAKE_RELEASE_FILE_WRITABLE_BY_NON_OWNER')
if (enforceRootOwner && info.uid !== 0) fail('HOLOLAKE_RELEASE_FILE_NOT_ROOT_OWNED')
if (maxBytes !== null && info.size > maxBytes) fail('HOLOLAKE_RELEASE_CONTROL_FILE_TOO_LARGE')
return info
}
function safeRelative(root, value, code) {
const relative = requireText(value, code)
if (path.isAbsolute(relative) || relative.includes('\0')) fail(code)
const resolved = path.resolve(root, relative)
if (!resolved.startsWith(`${root}${path.sep}`)) fail(code)
return resolved
}
function readTrustedJson(file, options) {
trustedRegularFile(file, { ...options, maxBytes: MAX_CONTROL_BYTES })
return { bytes: fs.readFileSync(file), value: JSON.parse(fs.readFileSync(file, 'utf8')) }
}
function validatePlatform(platform, releaseDirectory, enforceRootOwner) {
const url = new URL(requireText(platform?.url, 'HOLOLAKE_RELEASE_PACKAGE_URL_REQUIRED'))
if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash) {
fail('HOLOLAKE_RELEASE_PACKAGE_URL_INVALID')
}
const packageName = decodeURIComponent(path.posix.basename(url.pathname))
if (!packageName || packageName === '.' || packageName === '..') fail('HOLOLAKE_RELEASE_PACKAGE_NAME_INVALID')
const packagePath = safeRelative(releaseDirectory, packageName, 'HOLOLAKE_RELEASE_PACKAGE_PATH_INVALID')
const packageInfo = trustedRegularFile(packagePath, { enforceRootOwner })
if (!Number.isSafeInteger(platform.size) || platform.size <= 0 || platform.size !== packageInfo.size) {
fail('HOLOLAKE_RELEASE_PACKAGE_SIZE_MISMATCH')
}
if (!SHA256_PATTERN.test(platform.sha256 || '') || sha256(fs.readFileSync(packagePath)) !== platform.sha256) {
fail('HOLOLAKE_RELEASE_PACKAGE_SHA256_MISMATCH')
}
requireText(platform.signature, 'HOLOLAKE_RELEASE_PACKAGE_SIGNATURE_REQUIRED')
const codeReceiptId = requireText(platform.platformCodeSignatureReceipt, 'HOLOLAKE_RELEASE_CODESIGN_RECEIPT_REQUIRED')
const notarizationReceiptId = requireText(platform.notarizationReceipt, 'HOLOLAKE_RELEASE_NOTARIZATION_RECEIPT_REQUIRED')
const codeReceipt = readTrustedJson(
safeRelative(releaseDirectory, `${codeReceiptId}.json`, 'HOLOLAKE_RELEASE_CODESIGN_RECEIPT_PATH_INVALID'),
{ enforceRootOwner },
).value
const notarizationReceipt = readTrustedJson(
safeRelative(releaseDirectory, `${notarizationReceiptId}.json`, 'HOLOLAKE_RELEASE_NOTARIZATION_RECEIPT_PATH_INVALID'),
{ enforceRootOwner },
).value
if (
codeReceipt?.schema !== 'hololake.platform-code-signature-receipt/v1' ||
codeReceipt?.state !== 'DEVELOPER_ID_SIGNATURE_STRICT_AND_GATEKEEPER_ACCEPTED'
) fail('HOLOLAKE_RELEASE_CODESIGN_RECEIPT_INVALID')
if (
notarizationReceipt?.schema !== 'hololake.apple-notarization-receipt/v1' ||
notarizationReceipt?.state !== 'APPLE_NOTARIZATION_ACCEPTED_AND_STAPLED'
) fail('HOLOLAKE_RELEASE_NOTARIZATION_RECEIPT_INVALID')
return { pathname: url.pathname, file: packagePath, size: packageInfo.size }
}
function loadActiveRelease(stateRoot, enforceRootOwner) {
const activationPath = path.join(stateRoot, 'ACTIVE.json')
if (!fs.existsSync(activationPath)) {
return { state: 'EMPTY_FAIL_CLOSED', release: null, reasonCode: null }
}
const activation = readTrustedJson(activationPath, { enforceRootOwner }).value
if (
activation?.schema !== 'hololake.release-broadcast-activation/v1' ||
activation?.state !== 'HUMAN_APPROVED_SIGNED_NOTARIZED_RELEASE_ACTIVE'
) fail('HOLOLAKE_RELEASE_ACTIVATION_INVALID')
const releaseId = requireText(activation.releaseId, 'HOLOLAKE_RELEASE_ID_REQUIRED')
const version = requireText(activation.version, 'HOLOLAKE_RELEASE_VERSION_REQUIRED')
if (!VERSION_PATTERN.test(version)) fail('HOLOLAKE_RELEASE_VERSION_INVALID')
if (!SHA256_PATTERN.test(activation.broadcastSha256 || '')) fail('HOLOLAKE_RELEASE_BROADCAST_SHA256_INVALID')
requireText(activation.humanApprovalReceipt, 'HOLOLAKE_RELEASE_HUMAN_APPROVAL_REQUIRED')
const broadcastPath = safeRelative(stateRoot, activation.broadcastRelativePath, 'HOLOLAKE_RELEASE_BROADCAST_PATH_INVALID')
const pipelinePath = safeRelative(stateRoot, activation.pipelineReceiptRelativePath, 'HOLOLAKE_RELEASE_PIPELINE_PATH_INVALID')
const broadcastDocument = readTrustedJson(broadcastPath, { enforceRootOwner })
const pipelineReceipt = readTrustedJson(pipelinePath, { enforceRootOwner }).value
if (sha256(broadcastDocument.bytes) !== activation.broadcastSha256) fail('HOLOLAKE_RELEASE_BROADCAST_SHA256_MISMATCH')
if (
pipelineReceipt?.schema !== 'hololake.signed-release-pipeline-receipt/v1' ||
pipelineReceipt?.state !== 'SIGNED_NOTARIZED_RELEASE_BROADCAST_READY_FOR_JD_CONTROLLER_UPLOAD' ||
pipelineReceipt?.broadcastSha256 !== activation.broadcastSha256 ||
pipelineReceipt?.automaticUpload !== false ||
pipelineReceipt?.automaticActivation !== false
) fail('HOLOLAKE_RELEASE_PIPELINE_RECEIPT_INVALID')
const broadcast = broadcastDocument.value
if (
broadcast?.schema !== 'hololake.release-broadcast/v1' ||
broadcast?.releaseId !== releaseId ||
broadcast?.version !== version ||
broadcast?.hololake?.restart?.required !== true ||
broadcast?.hololake?.restart?.automaticAllowed !== false ||
broadcast?.hololake?.rollback?.supported !== true ||
broadcast?.hololake?.rollback?.healthReceiptRequired !== true ||
!broadcast.platforms || typeof broadcast.platforms !== 'object' || Array.isArray(broadcast.platforms) ||
Object.keys(broadcast.platforms).length === 0
) fail('HOLOLAKE_RELEASE_BROADCAST_INVALID')
const releaseDirectory = path.dirname(broadcastPath)
const packages = new Map()
for (const platform of Object.values(broadcast.platforms)) {
const accepted = validatePlatform(platform, releaseDirectory, enforceRootOwner)
if (packages.has(accepted.pathname)) fail('HOLOLAKE_RELEASE_PACKAGE_ROUTE_COLLISION')
packages.set(accepted.pathname, accepted)
}
return {
state: 'READY_SIGNED_NOTARIZED_BROADCAST',
release: { releaseId, version, broadcastBytes: broadcastDocument.bytes, packages },
reasonCode: null,
}
}
export function loadRuntimeState(stateRoot, { enforceRootOwner = false } = {}) {
const resolvedRoot = path.resolve(stateRoot)
try {
return loadActiveRelease(resolvedRoot, enforceRootOwner)
} catch (error) {
return {
state: 'LOCKED_INVALID_RELEASE_EVIDENCE',
release: null,
reasonCode: error instanceof Error ? error.message : 'HOLOLAKE_RELEASE_UNKNOWN_FAILURE',
}
}
}
function jsonResponse(response, status, value) {
const body = Buffer.from(`${JSON.stringify(value)}\n`)
response.writeHead(status, {
'Content-Type': 'application/json; charset=utf-8',
'Content-Length': body.length,
'Cache-Control': 'no-store',
'X-Content-Type-Options': 'nosniff',
})
response.end(body)
}
export function createReleaseServer(runtimeState) {
return http.createServer((request, response) => {
response.setHeader('X-Content-Type-Options', 'nosniff')
if (!['GET', 'HEAD'].includes(request.method || '')) {
response.writeHead(405, { Allow: 'GET, HEAD', 'Cache-Control': 'no-store' })
return response.end()
}
const pathname = new URL(request.url || '/', 'http://127.0.0.1').pathname
if (pathname === '/health') {
const status = runtimeState.state === 'LOCKED_INVALID_RELEASE_EVIDENCE' ? 503 : 200
return jsonResponse(response, status, {
schema: 'hololake.release-broadcast-health/v1',
state: runtimeState.state,
updateAvailable: runtimeState.release !== null,
automaticUpload: false,
automaticActivation: false,
upstreamUpdateSources: [],
...(runtimeState.release ? { releaseId: runtimeState.release.releaseId, version: runtimeState.release.version } : {}),
...(runtimeState.reasonCode ? { reasonCode: runtimeState.reasonCode } : {}),
})
}
if (pathname === '/latest.json') {
if (runtimeState.state === 'EMPTY_FAIL_CLOSED') {
response.writeHead(204, { 'Cache-Control': 'no-store' })
return response.end()
}
if (!runtimeState.release) {
response.writeHead(503, { 'Cache-Control': 'no-store' })
return response.end()
}
response.writeHead(200, {
'Content-Type': 'application/json; charset=utf-8',
'Content-Length': runtimeState.release.broadcastBytes.length,
'Cache-Control': 'no-store',
'X-Content-Type-Options': 'nosniff',
})
return response.end(request.method === 'HEAD' ? undefined : runtimeState.release.broadcastBytes)
}
const packageFile = runtimeState.release?.packages.get(pathname)
if (packageFile) {
response.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-Length': packageFile.size,
'Cache-Control': 'public, max-age=31536000, immutable',
'X-Content-Type-Options': 'nosniff',
})
if (request.method === 'HEAD') return response.end()
return fs.createReadStream(packageFile.file).on('error', () => response.destroy()).pipe(response)
}
response.writeHead(404, { 'Cache-Control': 'no-store' })
response.end()
})
}
export function main(env = process.env) {
const host = env.HOLOLAKE_RELEASE_HOST || '127.0.0.1'
if (host !== '127.0.0.1') fail('HOLOLAKE_RELEASE_LOOPBACK_BIND_REQUIRED')
const port = Number(env.HOLOLAKE_RELEASE_PORT || 3940)
if (!Number.isInteger(port) || port < 1024 || port > 65535) fail('HOLOLAKE_RELEASE_PORT_INVALID')
const stateRoot = path.resolve(env.HOLOLAKE_RELEASE_STATE_ROOT || '/var/lib/guanghu/hololake-release-broadcast')
const runtimeState = loadRuntimeState(stateRoot, { enforceRootOwner: env.HOLOLAKE_RELEASE_ENFORCE_ROOT_OWNER === '1' })
const server = createReleaseServer(runtimeState)
server.on('clientError', (_error, socket) => socket.end('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n'))
server.listen(port, host, () => {
process.stdout.write(`${JSON.stringify({ schema: 'hololake.release-broadcast-startup/v1', host, port, state: runtimeState.state })}\n`)
})
return server
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
try {
main()
} catch (error) {
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
process.exitCode = 1
}
}