OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / test / unit / fleet-shell-safety.test.js

287 lines · javascript ref:
import test from 'brittle'
import { readFileSync } from 'node:fs'

const updater = readFileSync('fleet/updater.sh', 'utf8')
const updaterLauncher = readFileSync('fleet/updater-launcher.sh', 'utf8')
const updaterInstaller = readFileSync('fleet/install-updater.sh', 'utf8')
const updaterService = readFileSync('fleet/hiverelay-updater.service', 'utf8')
const gatewayQuarantine = readFileSync('fleet/quarantine-public-gateway.sh', 'utf8')
const fleetStatus = readFileSync('fleet/fleet-status.sh', 'utf8')
const deployVps = readFileSync('scripts/deploy-vps.sh', 'utf8')
const rolloutCheck = readFileSync('scripts/check-fleet-rollout.mjs', 'utf8')
const relayJanitor = readFileSync('scripts/relay-janitor.js', 'utf8')
const cli = readFileSync('packages/core/cli/index.js', 'utf8')

test('fleet updater installation executes only an exact signed-checkout updater', (t) => {
  const blobGate = 'fleet/updater.sh differs from the signed checkout'
  const tagGate = 'is not an allowed-signer-verified release tag'
  const execUpdater = 'exec /bin/bash --noprofile --norc "$UPDATER" "$@"'

  t.ok(updaterInstaller.includes('install -m 0755 "$SRC/updater-launcher.sh" "$BIN_DIR/hiverelay-updater"'))
  t.absent(updaterInstaller.includes('install -m 0755 "$SRC/updater.sh"'),
    'the mutable installed copy is gone')
  t.ok(updaterLauncher.includes("rev-parse --verify 'HEAD:fleet/updater.sh'"))
  t.ok(updaterLauncher.includes('hash-object --no-filters "$UPDATER"'))
  t.ok(updaterLauncher.includes('verify-tag --raw "$tag"'))
  t.ok(updaterLauncher.includes('gpg.ssh.program=/usr/bin/ssh-keygen'))
  t.ok(updaterLauncher.includes(blobGate))
  t.ok(updaterLauncher.includes(tagGate))
  t.ok(updaterLauncher.indexOf(blobGate) < updaterLauncher.indexOf(execUpdater))
  t.ok(updaterLauncher.indexOf(tagGate) < updaterLauncher.indexOf(execUpdater))
  t.absent(updaterLauncher.includes('source "$CONF"'))
  t.absent(updaterLauncher.includes('. "$CONF"'))
})

test('fleet updater service requires the preserved root-only runtime environment', (t) => {
  t.ok(updaterService.includes('ExecStart=/usr/bin/env -i HOME=/root PATH=/usr/sbin:/usr/bin:/sbin:/bin /bin/bash --noprofile --norc /usr/local/bin/hiverelay-updater'))
  t.absent(updaterService.includes('EnvironmentFile='),
    'systemd never imports unchecked updater data into the root process environment')
  t.ok(updaterInstaller.includes('ENV_FILE="$ENV_DIR/hiverelay-updater.env"'))
  t.ok(updaterInstaller.includes('chmod 0600 "$ENV_TMP"'))
  t.ok(updaterService.includes('StateDirectory=hiverelay-updater'))
  t.ok(updaterService.includes('StateDirectoryMode=0700'))
  t.ok(updaterInstaller.includes('quarantined unsafe updater environment path'))
  t.ok(updaterInstaller.includes("printf 'CHANNEL=%s\\nRELAY_NAME=%s\\nREPO_DIR=%s\\n'"))
  t.ok(updaterInstaller.includes('install -m 0755 "$SRC/quarantine-public-gateway.sh"'))
  t.absent(updaterInstaller.includes('printf \'HIVERELAY_PUBLIC_GATEWAY_'),
    'installer never regenerates operator gateway settings')
})

test('fleet updater accepts channel authority only from signed monotonic Git control', (t) => {
  t.ok(updater.includes('scripts/resolve-signed-fleet-channel.mjs'))
  t.ok(updater.includes('--channel "$CHANNEL"'))
  t.ok(updater.includes('--state "$CONTROL_STATE"'))
  t.ok(updater.includes('--installed-head "$CUR_SHA"'))
  t.absent(updater.includes('HIVERELAY_CHANNELS_URL'))
  t.absent(updater.includes('raw.githubusercontent.com'))
  t.absent(updater.includes('cannot fetch channels.json'))
  t.ok(fleetStatus.includes('CHANNEL="$channel" python3 -c'))
  t.ok(fleetStatus.includes('os.environ["CHANNEL"]'))
  t.absent(fleetStatus.includes(".get('$channel'"))
  t.absent(fleetStatus.includes('python3 -c "import sys,json;print(json.load(sys.stdin).get('))
})

test('fleet updater routes dependency-install failures through rollback', (t) => {
  t.ok(updater.includes('rollback_to_previous'))
  t.ok(updater.includes('deps_if_changed "$CUR_SHA" "$TARGET_SHA" || rollback_to_previous'))
  t.ok(updater.includes('if ! git checkout --quiet "$CUR_SHA"; then'))
  t.ok(updater.includes('CRITICAL could not checkout previous SHA'))
  t.ok(updater.includes('if ! deps_if_changed "$TARGET_SHA" "$CUR_SHA"; then'))
  t.ok(updater.includes('npm ci --omit=dev --no-audit --no-fund'))
  t.absent(updater.includes('npm install --omit=dev'), 'npm ci failure cannot fall back to a non-lockfile install')
  t.ok(updater.includes('verify_raw_tracked_tree || rollback_to_previous "dependency install changed tracked release bytes on $TARGET"'))
  t.ok(updater.includes('if ! verify_raw_tracked_tree; then'))
  t.absent(updater.includes('git checkout --quiet "$CUR_SHA" || log "CRITICAL could not checkout previous SHA"'))
})

test('fleet updater health-gates target and rollback runtime versions', (t) => {
  const expectedVersionArg = 'expected_version="' + '$' + '{1:-}"'
  const targetHealthGate = 'if healthy "' + '$' + '{TARGET#v}"; then'
  const rollbackHealthGate = 'if healthy "' + '$' + '{CUR_VER#v}"; then'

  t.ok(updater.includes(expectedVersionArg))
  t.ok(updater.includes('"version"[[:space:]]*:[[:space:]]*"'))
  t.ok(updater.includes('[ "$version" = "$expected_version" ]'))
  t.ok(updater.includes(targetHealthGate))
  t.ok(updater.includes(rollbackHealthGate))
  t.absent(updater.includes('if healthy; then'))
})

test('signed manifest cohort forces exact preflight and evidence bindings', (t) => {
  const dollar = '$'
  const probeInvocation = 'HIVERELAY_API_KEY="$key" timeout "$PUBLIC_GATEWAY_PROBE_TIMEOUT" "' + dollar + '{probe_args[@]}"'
  const verifyIndex = updater.indexOf('verify_tag "$TARGET"')
  const manifestIndex = updater.indexOf('resolve_public_gateway_contract', verifyIndex)

  t.ok(updater.includes('RELAY_NAME is required in $CONF'))
  t.ok(updater.includes('PUBLIC_GATEWAY_REQUIRED=0'))
  t.ok(updater.includes('git ls-tree --name-only "$TARGET_SHA" -- "$PUBLIC_GATEWAY_MANIFEST_PATH"'))
  t.ok(updater.includes('git show "$TARGET_SHA:$PUBLIC_GATEWAY_MANIFEST_PATH"'))
  t.ok(updater.includes('scripts/resolve-public-hive-gateway-node.mjs'))
  t.ok(verifyIndex !== -1 && manifestIndex > verifyIndex,
    'target trust is established before signed policy is interpreted')
  t.ok(updater.includes('[ "$PUBLIC_GATEWAY_REQUIRED" = 1 ] || return 0'))
  t.ok(updater.includes('scripts/preflight-public-hive-gateway.mjs'))
  t.ok(updater.includes('scripts/verify-public-hive-gateway-evidence.mjs'))
  t.ok(updater.includes('PUBLIC_GATEWAY_DEPLOYMENT_PROFILE="legacy"'))
  t.ok(updater.includes('PUBLIC_GATEWAY_OPERATOR_CONTRACT_SHA256="-"'))
  t.ok(updater.includes('public-t1-gateway)'))
  t.ok(updater.includes('PUBLIC_GATEWAY_OPS_REQUIRED=1'))
  t.ok(updater.includes('--require-public-t1'))
  t.ok(updater.includes('enabled public gateway updater cohort must use public-t1-gateway'))
  t.ok(updater.includes('scripts/preflight-public-hive-gateway-ops.mjs'))
  t.ok(updater.includes('scripts/verify-public-hive-gateway-ops-evidence.mjs'))
  t.ok(updater.includes('[[ "$PUBLIC_GATEWAY_PROBE_EVIDENCE" != /* ]]'))
  t.ok(updater.includes('[ "$PUBLIC_GATEWAY_PROBE_PUBLIC_SUFFIX_READY" != "0" ]'))
  t.ok(updater.includes('--mode fleet'))
  t.ok(updater.includes('--probe-origin "$PUBLIC_GATEWAY_EXPECTED_ORIGIN"'))
  t.ok(updater.includes('--connect-address "$PUBLIC_GATEWAY_EXPECTED_CONNECT_ADDRESS"'))
  t.ok(updater.includes('--app-key "$PUBLIC_GATEWAY_EXPECTED_APP_KEY"'))
  t.ok(updater.includes('--path "$PUBLIC_GATEWAY_EXPECTED_PATH"'))
  t.ok(updater.includes('--nginx-config "$PUBLIC_GATEWAY_PROBE_NGINX_CONFIG"'))
  t.ok(updater.includes('--nginx-binary "$PUBLIC_GATEWAY_PROBE_NGINX_BINARY"'))
  t.ok(updater.includes('--expected-sha256 "$PUBLIC_GATEWAY_EXPECTED_SHA256"'))
  t.ok(updater.includes('--evidence "$PUBLIC_GATEWAY_PROBE_EVIDENCE"'))
  t.ok(updater.includes('[ "$PUBLIC_GATEWAY_PROBE_PUBLIC_SUFFIX_READY" != "1" ] || probe_args+=(--public-suffix-ready)'))
  t.ok(updater.includes('--release-target "$TARGET"'))
  t.ok(updater.includes('--release-sha "$TARGET_SHA"'))
  t.ok(updater.includes('--require-admission-profile "$PUBLIC_GATEWAY_ADMISSION_PROFILE"'))
  t.ok(updater.includes('--expected-drive-version "$PUBLIC_GATEWAY_EXPECTED_DRIVE_VERSION"'))
  t.ok(updater.includes('--expected-peer-fingerprint256 "$PUBLIC_GATEWAY_EXPECTED_PEER_FINGERPRINT256"'))
  t.ok(updater.includes('--expected-nginx-sha256 "$PUBLIC_GATEWAY_EXPECTED_NGINX_SHA256"'))
  t.ok(updater.includes('--release-manifest "$release_manifest"'))
  t.ok(updater.includes('--expected-contract-sha256 "$PUBLIC_GATEWAY_OPERATOR_CONTRACT_SHA256"'))
  t.ok(updater.includes('--dns-live'))
  t.ok(updater.includes('--ss-binary "$PUBLIC_GATEWAY_OPS_SS_BINARY"'))
  t.ok(updater.includes(probeInvocation))
  t.absent(updater.includes('env HIVERELAY_API_KEY='), 'API key is not exposed in child argv')
  for (const retired of [
    'HIVERELAY_PUBLIC_GATEWAY_PROBE_ORIGIN',
    'HIVERELAY_PUBLIC_GATEWAY_PROBE_CONNECT_ADDRESS',
    'HIVERELAY_PUBLIC_GATEWAY_PROBE_APP_KEY',
    'HIVERELAY_PUBLIC_GATEWAY_PROBE_PATH',
    'HIVERELAY_PUBLIC_GATEWAY_PROBE_SHA256',
    'HIVERELAY_PUBLIC_GATEWAY_PROBE_MODE'
  ]) t.absent(updater.includes(retired), `${retired} cannot weaken or redirect signed policy`)
})

test('fleet updater contains both same-release and failed public-t1 transition edges', (t) => {
  const targetHealthGate = 'if healthy "' + '$' + '{TARGET#v}"; then'
  const gatewayGate = 'public_gateway_healthy || rollback_to_previous "public HTTPS gateway probe failed on ' + '$' + 'TARGET"'
  const successLog = 'log "OK updated ' + '$' + 'CUR_VER -> ' + '$' + 'TARGET — health green"'
  const rollbackHealthGate = 'if healthy "' + '$' + '{CUR_VER#v}"; then'
  const noOpBranch = 'if [ "$UP_TO_DATE" = 1 ]; then'
  const containCall = 'if contain_up_to_date_gateway_failure; then'

  const noOpIndex = updater.lastIndexOf(noOpBranch)
  const noOpProbeIndex = updater.indexOf('if ! public_gateway_healthy; then', noOpIndex)
  const containmentIndex = updater.indexOf(containCall, noOpIndex)
  const targetHealthIndex = updater.indexOf(targetHealthGate)
  const gatewayGateIndex = updater.indexOf(gatewayGate)
  const successIndex = updater.indexOf(successLog)
  const rollbackFunctionIndex = updater.indexOf('rollback_to_previous()')
  const rollbackHealthIndex = updater.indexOf(rollbackHealthGate, rollbackFunctionIndex)
  const updateSectionIndex = updater.indexOf('# ── update')

  t.ok(noOpIndex > rollbackFunctionIndex, 'same-release decision runs after the probe helper exists')
  t.ok(noOpProbeIndex > noOpIndex && noOpProbeIndex < updateSectionIndex,
    'same-release tick refreshes gateway evidence before exiting')
  t.ok(containmentIndex > noOpProbeIndex && containmentIndex < updateSectionIndex,
    'same-release probe failure enters narrow containment')
  t.absent(updater.slice(noOpIndex, updateSectionIndex).includes('rollback_to_previous'),
    'same-release probe failure cannot enter rollback')
  t.absent(updater.slice(noOpIndex, updateSectionIndex).includes('systemctl'),
    'same-release containment does not stop or restart management service')
  t.ok(updater.includes('hiverelay-public-gateway-evidence-invalid-v1'))
  t.ok(updater.includes('hiverelay-public-gateway-operator-readiness-invalid-v1'))
  t.ok(updater.indexOf('invalidate_public_gateway_evidence ||') < updater.indexOf('quarantine_public_gateway_edge ||'))
  t.ok(updater.indexOf('invalidate_public_gateway_evidence ||') <
    updater.indexOf('invalidate_public_gateway_ops_evidence ||'))
  t.ok(updater.indexOf('invalidate_public_gateway_ops_evidence ||') <
    updater.indexOf('quarantine_public_gateway_edge ||'))
  t.ok(updater.includes('management API left running'))
  t.absent(gatewayQuarantine.includes('systemctl'))
  t.absent(gatewayQuarantine.includes('sudo'))
  t.ok(gatewayQuarantine.includes('return 421;'))
  t.ok(gatewayQuarantine.includes('"$NGINX_BINARY" "$@"'))
  t.ok(gatewayQuarantine.includes('run_trusted_nginx -t'))
  t.ok(gatewayQuarantine.includes('run_trusted_nginx -s reload'))
  t.ok(targetHealthIndex > updateSectionIndex, 'target health gate is in the update path')
  t.ok(gatewayGateIndex > targetHealthIndex, 'gateway probe follows target API health')
  t.ok(successIndex > gatewayGateIndex, 'success is declared only after the gateway probe')
  t.ok(rollbackHealthIndex > rollbackFunctionIndex && rollbackHealthIndex < updateSectionIndex,
    'rollback retains its independent API-only health gate')
  t.absent(updater.slice(rollbackFunctionIndex, noOpIndex).includes('public_gateway_healthy'),
    'rollback function never invokes the public gateway probe')
  t.ok(updater.slice(rollbackFunctionIndex, noOpIndex).includes('contain_up_to_date_gateway_failure'),
    'failed public-t1 transitions contain the public edge before code rollback')
  t.ok(updater.includes('public edge remains quarantined pending refreshed previous-release manifest/config/nginx/DNS/TLS/SPKI/socket/content evidence'))
})

test('deploy script keeps API keys out of world-readable systemd units', (t) => {
  t.ok(deployVps.includes('validate_api_key "$EFFECTIVE_KEY"'))
  t.ok(deployVps.includes("API_KEY_B64='" + '$' + "{API_KEY_B64}'"))
  t.ok(deployVps.includes('base64 -d'))
  t.ok(deployVps.includes('EnvironmentFile=/etc/hiverelay/hiverelay.env'))
  t.ok(deployVps.includes('chmod 0600 /etc/hiverelay/hiverelay.env'))
  t.absent(deployVps.includes('Environment=HIVERELAY_API_KEY=API_KEY_PLACEHOLDER'))
  t.absent(deployVps.includes('sed -i "s/API_KEY_PLACEHOLDER'))
})

test('deploy script computes MemoryHigh correctly for gigabyte relay limits', (t) => {
  t.ok(deployVps.includes('MEMORY_HIGH="$(memory_high_limit "$MAX_MEM")"'))
  t.ok(deployVps.includes('memory_high_limit()'))
  t.ok(deployVps.includes('NUM * 1024 * 80 / 100'))
  t.ok(deployVps.includes('sed -i "s/MEMHIGH_PLACEHOLDER/' + '$' + '{MEMORY_HIGH}/"'))
  t.absent(deployVps.includes('MEM_HIGH=$(( MEM_NUM * 80 / 100 ))'))
})

test('fleet health tooling discovers API keys from root-only env files', (t) => {
  for (const source of [updater, fleetStatus, rolloutCheck, relayJanitor]) {
    t.ok(source.includes('/etc/hiverelay/hiverelay.env'))
    t.ok(source.includes('awk -F='))
    t.absent(source.includes("grep -o 'HIVERELAY_API_KEY=[^ ]*'"))
    t.absent(source.includes("grep -oE 'HIVERELAY_API_KEY=[A-Za-z0-9._-]+'"))
  }
  t.ok(updater.includes('ENV_FILE='))
  t.ok(fleetStatus.includes('read_api_key()'))
  t.ok(rolloutCheck.includes('env_file='))
  t.ok(relayJanitor.includes('root-only env file first'))
})

test('relay janitor keeps remote API keys out of local ssh argv', (t) => {
  t.ok(relayJanitor.includes('remoteHasApiKey'))
  t.ok(relayJanitor.includes('remoteUnseed'))
  t.ok(relayJanitor.includes("spawn('ssh', [...sshBase, 'bash', '-s', '--', ...args]"))
  t.ok(relayJanitor.includes('Never print or return it'))
  t.ok(relayJanitor.includes('-H "@$header_file"'))
  t.absent(relayJanitor.includes('const key = await readApiKey'))
  t.absent(relayJanitor.includes('authorization: Bearer ' + '$' + '{key}'))
  t.absent(relayJanitor.includes("printf '%s\\n' \"$key\""))
})

test('fleet health probes keep relay API keys out of curl argv', (t) => {
  for (const source of [updater, fleetStatus, rolloutCheck]) {
    t.ok(source.includes('curl_with_optional_key()'))
    t.ok(source.includes('header_file="$(mktemp)"'))
    t.ok(source.includes('chmod 600 "$header_file"'))
    t.ok(source.includes('curl -H "@$header_file" "$@"'))
    t.absent(source.includes('-H "Authorization: Bearer $key"'))
    t.absent(source.includes('-H "Authorization: Bearer $K"'))
  }
  t.ok(relayJanitor.includes('header_file="$(mktemp)"'))
  t.ok(relayJanitor.includes('chmod 600 "$header_file"'))
  t.ok(relayJanitor.includes("printf 'authorization: Bearer %s\\n' \"$key\""))
  t.ok(relayJanitor.includes('-H "@$header_file"'))
  t.absent(relayJanitor.includes('-H "authorization: Bearer $key"'))
})

test('fleet status sanitizes terminal output and validates inventory before ssh', (t) => {
  t.ok(fleetStatus.includes('clean_field()'))
  t.ok(fleetStatus.includes("tr -c '[:print:]' '?'"))
  t.ok(fleetStatus.includes("tr '|' '?'"))
  t.ok(fleetStatus.includes('valid_host()'))
  t.ok(fleetStatus.includes('valid_channel()'))
  t.ok(fleetStatus.includes('valid_key_path()'))
  t.ok(fleetStatus.includes('BADHOST'))
  t.ok(fleetStatus.includes('BADCHAN'))
  t.ok(fleetStatus.includes('BADKEY'))
  t.ok(fleetStatus.includes('safe_name="$(clean_field "$name")"'))
  t.ok(fleetStatus.includes('safe_target="$(clean_field "$target")"'))
  t.ok(fleetStatus.includes('out="$(printf \'%s\\n\' "$out" | tail -n 1)"'))
  t.absent(fleetStatus.includes('"$name" UNREACH'))
  t.absent(fleetStatus.includes(
    '"$' + '{ver:-?}" "$' + '{run:-?}" "$' + '{apps:-?}" "$' + '{conns:-?}" "$' + '{disk:-?}" "$target"'
  ))
})

test('relay CLI keeps high-frequency status output off service logs', (t) => {
  t.ok(cli.includes('const interactiveStatus = process.stdout.isTTY === true'))
  t.ok(cli.includes('const statusIntervalMs = interactiveStatus ? 5000 : 60000'))
  t.ok(cli.includes('if (interactiveStatus)'))
  t.ok(cli.includes("log.info(status, 'relay status')"))
  t.ok(cli.includes('function statusSnapshot (node)'))
  t.ok(cli.includes('stats.served ? stats.served.totalBytesServed'))
  t.absent(cli.includes('if (!args.quiet) {\n    statusInterval = setInterval(() =>'))
})