import test from 'brittle'
import { spawnSync } from 'child_process'
import { createHash } from 'crypto'
import { chmod, lstat, mkdtemp, readFile, readdir, rm, symlink, writeFile } from 'fs/promises'
import { tmpdir } from 'os'
import path from 'path'
import {
inspectPublicHiveGatewayAdmissionReadiness,
inspectPublicHiveGatewayConfig,
inspectPublicHiveGatewayNginx,
renderPublicHiveGatewayNginx
} from '../../scripts/lib/public-hive-gateway-preflight.mjs'
const KEY = 'a'.repeat(64)
const KEY_LABEL = 'ikikikikikikikikikikikikikikikikikikikikikikikikikiy'
const CONFIG = {
mode: 'public',
productProfile: 'relay-core',
requirePhysicalEnforcement: true,
enableAPI: true,
enableSeeding: true,
apiHost: '127.0.0.1',
apiPort: 9100,
gatewayHost: '127.0.0.1',
gatewayPort: 9200,
gatewayTrustProxy: true,
gatewayTrustedProxyAddresses: ['127.0.0.1', '::1'],
gatewayRequireForwardedSNI: true,
gatewayCompatibilityHosts: ['127.0.0.1', 'localhost', '[::1]'],
gatewayMaxInFlight: 256,
gatewayMaxInFlightPerApp: 32,
gatewayMaxResponseBytes: 67108864,
gatewayMaxTransformBytes: 4194304,
gatewayEgressBytesPerWindow: 268435456,
gatewayEgressWindowMs: 60000,
gatewayMaxResponseLifetimeMs: 900000,
custody: { enabled: false },
hiveAppHostSuffix: 'hive-canary.operator.example',
hiveAppPublicKeys: [KEY],
hiveAppPublicVersions: { [KEY]: 7 }
}
test('public gateway preflight - hardened single-app canary passes with explicit warnings', (t) => {
const result = inspectPublicHiveGatewayConfig(CONFIG, {
mode: 'canary',
apiKeyPresent: true,
publicSuffixReady: false
})
t.ok(result.ok)
t.alike(result.errors, [])
t.is(result.normalized.suffix, CONFIG.hiveAppHostSuffix)
t.alike(result.normalized.appKeys, [KEY])
t.alike(result.normalized.appVersions, { [KEY]: 7 })
t.is(result.normalized.physicalEnforcementRequired, true)
t.ok(result.warnings.some(value => value.includes('transitional-operator-allowlist-v1')))
t.ok(result.warnings.some(value => value.includes('Public Suffix')))
})
test('public gateway preflight - physical enforcement must be explicitly required by raw config', (t) => {
const missing = { ...CONFIG }
delete missing.requirePhysicalEnforcement
const absent = inspectPublicHiveGatewayConfig(missing, {
mode: 'canary',
apiKeyPresent: true,
publicSuffixReady: false
})
t.absent(absent.ok)
t.ok(absent.errors.some(value => value.includes('requirePhysicalEnforcement must be explicitly true')))
const disabled = inspectPublicHiveGatewayConfig({ ...CONFIG, requirePhysicalEnforcement: false }, {
mode: 'canary',
apiKeyPresent: true,
publicSuffixReady: false
})
t.absent(disabled.ok)
t.ok(disabled.errors.some(value => value.includes('requirePhysicalEnforcement must be explicitly true')))
const inheritedOnly = inspectPublicHiveGatewayConfig(CONFIG, {
mode: 'canary',
apiKeyPresent: true,
publicSuffixReady: false,
explicitConfig: missing
})
t.absent(inheritedOnly.ok)
t.ok(inheritedOnly.errors.some(value => value.includes('raw public-t1 gateway config')))
})
test('public gateway preflight - one-app fleet posture waits for substrate but not PSL registration', (t) => {
const result = inspectPublicHiveGatewayConfig(CONFIG, {
mode: 'fleet',
apiKeyPresent: true,
publicSuffixReady: false
})
t.absent(result.ok)
t.ok(result.errors.some(value => value.includes('transitional-operator-allowlist-v1')))
t.absent(result.errors.some(value => value.includes('Public Suffix')),
'one admitted production canary has no sibling app origin to isolate')
t.ok(result.warnings.some(value => value.includes('Public Suffix')))
})
test('public gateway preflight - a production-looking profile name cannot open fleet admission', (t) => {
const result = inspectPublicHiveGatewayAdmissionReadiness(Object.freeze({
kind: 'public-hive-gateway-admission-capability',
version: 1,
profile: 'frozen-t1-production-v99',
authority: 'local-operator-allowlist',
fleetReady: false
}), 'fleet')
t.absent(result.ok)
t.ok(result.errors.some(value => value.includes('not fleet-ready')))
const forgedReady = inspectPublicHiveGatewayAdmissionReadiness({
kind: 'public-hive-gateway-admission-capability',
version: 1,
profile: 'frozen-t1-production-v99',
authority: 'local-operator-allowlist',
fleetReady: true
}, 'fleet')
t.absent(forgedReady.ok)
})
test('public gateway preflight - multi-app Phase 1 remains closed even with a Public Suffix assertion', (t) => {
for (const publicSuffixReady of [false, true]) {
const result = inspectPublicHiveGatewayConfig({
...CONFIG,
hiveAppPublicKeys: [KEY, 'b'.repeat(64)]
}, {
mode: 'fleet',
apiKeyPresent: true,
publicSuffixReady
})
t.absent(result.ok)
t.ok(result.errors.some(value => value.includes('exactly one manifest-bound trusted app')),
`multi-app fleet is closed when publicSuffixReady=${publicSuffixReady}`)
}
})
test('public gateway preflight - fleet requires one matching immutable version pin while canary warns', (t) => {
const withoutPin = { ...CONFIG, hiveAppPublicVersions: {} }
const canary = inspectPublicHiveGatewayConfig(withoutPin, {
mode: 'canary',
apiKeyPresent: true,
publicSuffixReady: false
})
t.ok(canary.ok)
t.ok(canary.warnings.some(value => value.includes('exactly one immutable hiveAppPublicVersions pin')))
const fleet = inspectPublicHiveGatewayConfig(withoutPin, {
mode: 'fleet',
apiKeyPresent: true,
publicSuffixReady: false
})
t.absent(fleet.ok)
t.ok(fleet.errors.some(value => value.includes('exactly one immutable hiveAppPublicVersions pin')))
const wrongKey = inspectPublicHiveGatewayConfig({
...CONFIG,
hiveAppPublicVersions: { ['b'.repeat(64)]: 7 }
}, {
mode: 'fleet',
apiKeyPresent: true,
publicSuffixReady: true
})
t.absent(wrongKey.ok)
t.ok(wrongKey.errors.some(value => value.includes('outside hiveAppPublicKeys')))
})
test('public gateway preflight - unsafe topology and multi-app canary fail closed', (t) => {
const result = inspectPublicHiveGatewayConfig({
...CONFIG,
apiHost: '0.0.0.0',
gatewayHost: '0.0.0.0',
gatewayTrustProxy: false,
gatewayRequireForwardedSNI: false,
gatewayTrustedProxyAddresses: ['0.0.0.0'],
gatewayCompatibilityHosts: ['relay.example'],
custody: { enabled: true },
hiveAppPublicKeys: [KEY, 'b'.repeat(64)]
}, {
mode: 'canary',
apiKeyPresent: false
})
t.absent(result.ok)
for (const term of [
'gatewayHost to bind loopback',
'exactly one manifest-bound trusted app',
'apiHost must bind loopback',
'gatewayTrustProxy must be true',
'gatewayRequireForwardedSNI must be true',
'gatewayTrustedProxyAddresses',
'gatewayCompatibilityHosts',
'custody.enabled must be false',
'HIVERELAY_API_KEY'
]) {
t.ok(result.errors.some(value => value.includes(term)), `failure names ${term}`)
}
})
test('public gateway preflight - strict nginx template renders and validates', async (t) => {
const template = await readFile('deploy/public-hive-gateway/nginx.conf.template', 'utf8')
const rendered = renderPublicHiveGatewayNginx(template, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort,
certificate: '/etc/letsencrypt/live/hive/fullchain.pem',
certificateKey: '/etc/letsencrypt/live/hive/privkey.pem'
})
const result = inspectPublicHiveGatewayNginx(rendered, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort
})
t.ok(result.ok)
t.alike(result.errors, [])
t.ok(rendered.includes(`127.0.0.1:${CONFIG.gatewayPort}`))
t.ok(rendered.includes('server_name "~^'), 'nginx regex with a {52} quantifier stays quoted')
t.ok(rendered.includes('proxy_set_header Accept-Encoding "";'), 'browser encodings are stripped before the loopback upstream')
t.absent(rendered.includes('__HIVE_'), 'all deployment placeholders resolved')
t.exception(() => renderPublicHiveGatewayNginx(template, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort,
certificate: '/safe/cert.pem; include /tmp/evil',
certificateKey: '/safe/key.pem'
}), /safe absolute path/)
t.exception(() => renderPublicHiveGatewayNginx(template, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort,
certificate: '/safe/{dynamic}.pem',
certificateKey: '/safe/key.pem'
}), /safe absolute path/)
})
test('public gateway preflight - permissive forwarding or caching proxy is rejected', (t) => {
const unsafe = `
server_name app.${CONFIG.hiveAppHostSuffix};
proxy_pass http://127.0.0.1:${CONFIG.gatewayPort};
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache shared;
gzip on;
`
const result = inspectPublicHiveGatewayNginx(unsafe, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort
})
t.absent(result.ok)
t.ok(result.errors.some(value => value.includes('must not append')))
t.ok(result.errors.some(value => value.includes('cache must be disabled')))
t.ok(result.errors.some(value => value.includes('must not transform')))
t.ok(result.errors.some(value => value.includes('attest the TLS SNI')))
t.ok(result.errors.some(value => value.includes('default 421 reject vhost')))
t.ok(result.errors.some(value => value.includes('discard request bodies')))
t.ok(result.errors.some(value => value.includes('request-rate throttling must return 429')))
t.ok(result.errors.some(value => value.includes('connection throttling must return 429')))
})
test('public gateway preflight - commented safety directives are not evidence', async (t) => {
const template = await readFile('deploy/public-hive-gateway/nginx.conf.template', 'utf8')
const rendered = renderPublicHiveGatewayNginx(template, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort,
certificate: '/etc/hiverelay/public-gateway/fullchain.pem',
certificateKey: '/etc/hiverelay/public-gateway/privkey.pem'
})
const commentsOnly = rendered.split('\n').map(line => `# ${line}`).join('\n')
const result = inspectPublicHiveGatewayNginx(commentsOnly, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort
})
t.absent(result.ok)
t.ok(result.errors.some(value => value.includes('default 421 reject vhost')))
t.ok(result.errors.some(value => value.includes('loopback gateway port')))
t.ok(result.errors.some(value => value.includes('attest the TLS SNI')))
const duplicated = inspectPublicHiveGatewayNginx(`${rendered}\n${rendered}`, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort
})
t.absent(duplicated.ok)
t.ok(duplicated.errors.some(value => value.includes('no competing default')))
t.ok(duplicated.errors.some(value => value.includes('exactly one active quoted app-key server block')))
})
test('public gateway preflight - quoted claims and reordered competing listeners are not evidence', async (t) => {
const template = await readFile('deploy/public-hive-gateway/nginx.conf.template', 'utf8')
const rendered = renderPublicHiveGatewayNginx(template, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort,
certificate: '/etc/hiverelay/public-gateway/fullchain.pem',
certificateKey: '/etc/hiverelay/public-gateway/privkey.pem'
})
const quotedClaim = rendered.replace('proxy_cache off;', 'set $claim "proxy_cache off;";\n proxy_cache on;')
const quotedResult = inspectPublicHiveGatewayNginx(quotedClaim, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort
})
t.absent(quotedResult.ok)
t.ok(quotedResult.errors.some(value => value.includes('cache must be disabled')))
const competing = `${rendered}\nserver {
listen 127.0.0.2:443 default_server;
server_name competing.example;
return 421;
}\n`
const competingResult = inspectPublicHiveGatewayNginx(competing, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort
})
t.absent(competingResult.ok)
t.ok(competingResult.errors.some(value => value.includes('no competing default')))
const duplicateHost = rendered.replace(
'proxy_set_header Host $host;',
'proxy_set_header Host $host;\n proxy_set_header Host $http_host;'
)
const duplicateHostResult = inspectPublicHiveGatewayNginx(duplicateHost, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort
})
t.absent(duplicateHostResult.ok)
t.ok(duplicateHostResult.errors.some(value => value.includes('duplicate or unreviewed overrides')))
const defaultRedirect = rendered.replace(
' access_log off;\n error_log stderr crit;\n return 421;',
' access_log off;\n error_log stderr crit;\n error_page 421 = https://api.example;\n return 421;'
)
const defaultRedirectResult = inspectPublicHiveGatewayNginx(defaultRedirect, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort
})
t.absent(defaultRedirectResult.ok)
t.ok(defaultRedirectResult.errors.some(value => value.includes('default 421 reject vhost')))
})
test('public gateway preflight - every sibling TLS vhost shares the reviewed HTTP socket topology', async (t) => {
const template = await readFile('deploy/public-hive-gateway/nginx.conf.template', 'utf8')
const rendered = renderPublicHiveGatewayNginx(template, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort,
certificate: '/etc/hiverelay/public-gateway/fullchain.pem',
certificateKey: '/etc/hiverelay/public-gateway/privkey.pem'
})
const inspect = extra => inspectPublicHiveGatewayNginx(`${rendered}\n${extra}`, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort
})
const management = inspect(`server {
listen 443 ssl;
listen [::]:443 ssl;
server_name management.example;
return 421;
}`)
t.ok(management.ok, 'an exact unrelated management name may share the reviewed wildcard sockets')
for (const [name, fixture, expected] of [
['address-specific', `server {
listen 192.0.2.10:443 ssl;
server_name management.example;
return 421;
}`, 'shared wildcard'],
['socket-option', `server {
listen 443 ssl reuseport;
listen [::]:443 ssl;
server_name management.example;
return 421;
}`, 'shared wildcard'],
['unnamed', `server {
listen 443 ssl;
listen [::]:443 ssl;
return 421;
}`, 'explicit server_name'],
['stream', `stream {
server {
listen 443;
proxy_pass 127.0.0.1:9443;
}
}`, 'HTTP context'],
['stream-include-dump', `# configuration file /etc/nginx/nginx.conf:
stream { include /etc/nginx/stream-enabled/*.conf; }
# configuration file /etc/nginx/stream-enabled/tls.conf:
server { listen 443; proxy_pass 127.0.0.1:9443; }
`, 'stream context'],
['exact-app-host', `server {
listen 443 ssl;
listen [::]:443 ssl;
server_name ${'y'.repeat(52)}.${CONFIG.hiveAppHostSuffix};
return 421;
}`, 'shadow'],
['wildcard-app-host', `server {
listen 443 ssl;
listen [::]:443 ssl;
server_name *.${CONFIG.hiveAppHostSuffix};
return 421;
}`, 'shadow'],
['regex', `server {
listen 443 ssl;
listen [::]:443 ssl;
server_name ~^management\\.;
return 421;
}`, 'shadow']
]) {
const result = inspect(fixture)
t.absent(result.ok, `${name} sibling is rejected`)
t.ok(result.errors.some(value => value.includes(expected)), `${name} failure names ${expected}`)
}
})
test('public gateway preflight - inherited and module directives use a closed posture', async (t) => {
const template = await readFile('deploy/public-hive-gateway/nginx.conf.template', 'utf8')
const rendered = renderPublicHiveGatewayNginx(template, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort,
certificate: '/etc/hiverelay/public-gateway/fullchain.pem',
certificateKey: '/etc/hiverelay/public-gateway/privkey.pem'
})
for (const directive of [
'brotli on;',
'zstd on;',
'gzip_static on;',
'ssi on;',
'sub_filter before after;',
'subs_filter before after;',
'body_filter_by_lua_file /etc/nginx/filter.lua;',
'pagespeed on;',
'add_before_body /prefix;',
'image_filter resize 100 100;',
'xslt_stylesheet /etc/nginx/transform.xslt;',
'charset utf-8;',
'load_module modules/ngx_http_hostile_filter_module.so;'
]) {
const result = inspectPublicHiveGatewayNginx(`${directive}\n${rendered}`, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort
})
t.absent(result.ok, directive)
t.ok(result.errors.some(value => value.includes('unreviewed inherited HTTP/module')), directive)
}
const inherited = inspectPublicHiveGatewayNginx(`http {
subs_filter before after;
}
${rendered}`, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort
})
t.absent(inherited.ok)
t.ok(inherited.errors.some(value => value.includes('subs_filter')))
const unprobedPath = rendered.replace(' location / {', ` location = /not-probed {
body_filter_by_lua_file /etc/nginx/filter.lua;
return 200;
}
location / {`)
const unprobed = inspectPublicHiveGatewayNginx(unprobedPath, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort
})
t.absent(unprobed.ok)
t.ok(unprobed.errors.some(value => value.includes('outside the reviewed Phase 1 policy')),
'a transform on a path outside the live probe is still rejected structurally')
const missingIdentity = inspectPublicHiveGatewayNginx(
rendered.replace(' proxy_set_header Accept-Encoding "";\n', ''),
{ suffix: CONFIG.hiveAppHostSuffix, gatewayPort: CONFIG.gatewayPort }
)
t.absent(missingIdentity.ok)
t.ok(missingIdentity.errors.some(value => value.includes('identity transfer encoding upstream')))
const duplicateIdentity = inspectPublicHiveGatewayNginx(
rendered.replace(
' proxy_set_header Accept-Encoding "";',
' proxy_set_header Accept-Encoding "";\n proxy_set_header Accept-Encoding gzip;'
),
{ suffix: CONFIG.hiveAppHostSuffix, gatewayPort: CONFIG.gatewayPort }
)
t.absent(duplicateIdentity.ok)
t.ok(duplicateIdentity.errors.some(value => value.includes('duplicate or unreviewed overrides')))
const inheritedEncoding = inspectPublicHiveGatewayNginx(
`proxy_set_header Accept-Encoding gzip;\n${rendered}`,
{ suffix: CONFIG.hiveAppHostSuffix, gatewayPort: CONFIG.gatewayPort }
)
t.absent(inheritedEncoding.ok)
t.ok(inheritedEncoding.errors.some(value => value.includes('unreviewed inherited HTTP/module')))
const accessLogging = inspectPublicHiveGatewayNginx(
rendered.replace(' access_log off;\n error_log stderr crit;\n server_tokens off;',
' access_log /var/log/nginx/hive-app-access.log;\n error_log stderr crit;\n server_tokens off;'),
{ suffix: CONFIG.hiveAppHostSuffix, gatewayPort: CONFIG.gatewayPort }
)
t.absent(accessLogging.ok)
t.ok(accessLogging.errors.some(value => value.includes('disable durable request access logging')))
const fileErrorLogging = inspectPublicHiveGatewayNginx(
rendered.replace(' error_log stderr crit;\n server_tokens off;',
' error_log /var/log/nginx/hive-app-error.log warn;\n server_tokens off;'),
{ suffix: CONFIG.hiveAppHostSuffix, gatewayPort: CONFIG.gatewayPort }
)
t.absent(fileErrorLogging.ok)
t.ok(fileErrorLogging.errors.some(value => value.includes('critical stderr events')))
})
test('public gateway preflight - stock-shaped parent gzip remains isolated by explicit server overrides', async (t) => {
const template = await readFile('deploy/public-hive-gateway/nginx.conf.template', 'utf8')
const rendered = renderPublicHiveGatewayNginx(template, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort,
certificate: '/etc/hiverelay/public-gateway/fullchain.pem',
certificateKey: '/etc/hiverelay/public-gateway/privkey.pem'
})
const active = `
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;
events { worker_connections 768; }
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
types_hash_max_size 2048;
default_type application/octet-stream;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/access.log;
keepalive_timeout 65;
gzip on;
gunzip off;
include /etc/nginx/mime.types;
include /etc/nginx/conf.d/*.conf;
}
types {
text/html html htm;
application/javascript js;
application/octet-stream bin exe dll;
}
${rendered}
`
const result = inspectPublicHiveGatewayNginx(active, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort
})
t.ok(result.ok)
t.alike(result.errors, [])
const inheritedDefault = inspectPublicHiveGatewayNginx(
rendered.replace(
' gzip off;\n gunzip off;\n access_log off;\n error_log stderr crit;\n return 421;',
' access_log off;\n error_log stderr crit;\n return 421;'
),
{ suffix: CONFIG.hiveAppHostSuffix, gatewayPort: CONFIG.gatewayPort }
)
t.absent(inheritedDefault.ok)
t.ok(inheritedDefault.errors.some(value => value.includes('default 421 reject vhost')))
const defaultFileLogging = inspectPublicHiveGatewayNginx(
rendered.replace(
' access_log off;\n error_log stderr crit;\n return 421;',
' access_log off;\n error_log /var/log/nginx/default-error.log warn;\n return 421;'
),
{ suffix: CONFIG.hiveAppHostSuffix, gatewayPort: CONFIG.gatewayPort }
)
t.absent(defaultFileLogging.ok)
t.ok(defaultFileLogging.errors.some(value => value.includes('default 421 reject vhost')))
})
test('public gateway preflight CLI - installed nginx input is inspected and hashed exactly', async (t) => {
const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-installed-nginx-'))
t.teardown(() => rm(dir, { recursive: true, force: true }))
const template = await readFile('deploy/public-hive-gateway/nginx.conf.template', 'utf8')
const installed = renderPublicHiveGatewayNginx(template, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort,
certificate: '/etc/hiverelay/public-gateway/fullchain.pem',
certificateKey: '/etc/hiverelay/public-gateway/privkey.pem'
})
const installedPath = path.join(dir, 'public-gateway.conf')
await writeFile(installedPath, installed)
const result = spawnSync(process.execPath, [
'scripts/preflight-public-hive-gateway.mjs',
'--config', 'deploy/public-hive-gateway/hiverelay-config.example.json',
'--nginx-config', installedPath
], {
encoding: 'utf8',
env: { ...process.env, HIVERELAY_API_KEY: 'unit-test-present' }
})
t.is(result.status, 0, result.stderr)
const evidence = JSON.parse(result.stdout)
t.is(evidence.schema, 'hiverelay-public-gateway-preflight-v2')
t.is(evidence.config.physicalEnforcementRequired, true)
t.is(evidence.nginx.source, 'installed')
t.is(evidence.nginx.sha256, createHash('sha256').update(installed).digest('hex'))
t.ok(evidence.nginx.ok)
})
test('public gateway preflight CLI - active nginx parser output is the attested configuration', async (t) => {
const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-active-nginx-'))
t.teardown(() => rm(dir, { recursive: true, force: true }))
const template = await readFile('deploy/public-hive-gateway/nginx.conf.template', 'utf8')
const installed = renderPublicHiveGatewayNginx(template, {
suffix: CONFIG.hiveAppHostSuffix,
gatewayPort: CONFIG.gatewayPort,
certificate: '/etc/hiverelay/public-gateway/fullchain.pem',
certificateKey: '/etc/hiverelay/public-gateway/privkey.pem'
})
const installedPath = path.join(dir, 'public-gateway.conf')
const activePath = path.join(dir, 'active.conf')
const fakeNginx = path.join(dir, 'nginx')
const active = `${'# bounded filler from another parsed file\n'.repeat(9000)}` +
'# configuration file /etc/nginx/nginx.conf:\nevents {}\nhttp {}\n\n' +
`# configuration file ${installedPath}:\n${installed}`
await writeFile(installedPath, installed)
await writeFile(activePath, active)
await writeFile(fakeNginx, '#!/bin/sh\n[ "$1" = "-T" ] || exit 2\nexec /bin/cat "$HIVERELAY_TEST_ACTIVE_NGINX"\n')
await chmod(fakeNginx, 0o755)
const result = spawnSync(process.execPath, [
'scripts/preflight-public-hive-gateway.mjs',
'--config', 'deploy/public-hive-gateway/hiverelay-config.example.json',
'--nginx-config', installedPath,
'--nginx-binary', fakeNginx
], {
encoding: 'utf8',
env: {
...process.env,
HIVERELAY_API_KEY: 'unit-test-present',
HIVERELAY_TEST_ACTIVE_NGINX: activePath
}
})
t.is(result.status, 0, result.stderr)
const evidence = JSON.parse(result.stdout)
t.is(evidence.nginx.source, 'active')
t.is(evidence.nginx.sha256, createHash('sha256').update(active).digest('hex'))
t.ok(evidence.nginx.ok)
await writeFile(activePath, `# configuration file /etc/nginx/not-installed.conf:\n${installed}`)
const missing = spawnSync(process.execPath, [
'scripts/preflight-public-hive-gateway.mjs',
'--config', 'deploy/public-hive-gateway/hiverelay-config.example.json',
'--nginx-config', installedPath,
'--nginx-binary', fakeNginx
], {
encoding: 'utf8',
env: {
...process.env,
HIVERELAY_API_KEY: 'unit-test-present',
HIVERELAY_TEST_ACTIVE_NGINX: activePath
}
})
t.not(missing.status, 0)
t.ok(JSON.parse(missing.stdout).nginx.errors.some(value => value.includes('identify the installed')))
})
test('public gateway preflight CLI - no-follow input rejects a config symlink', async (t) => {
const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-preflight-symlink-'))
t.teardown(() => rm(dir, { recursive: true, force: true }))
const config = path.join(dir, 'config.json')
const linkedConfig = path.join(dir, 'linked-config.json')
await writeFile(config, JSON.stringify(CONFIG))
await symlink(config, linkedConfig)
const result = spawnSync(process.execPath, [
'scripts/preflight-public-hive-gateway.mjs',
'--config', linkedConfig
], {
encoding: 'utf8',
env: { ...process.env, HIVERELAY_API_KEY: 'unit-test-present' }
})
t.not(result.status, 0)
t.ok(result.stderr.includes('gateway config must be a bounded regular file'))
})
test('public gateway preflight CLI - atomic output replaces but never follows a destination symlink', async (t) => {
const dir = await mkdtemp(path.join(tmpdir(), 'hiverelay-preflight-atomic-'))
t.teardown(() => rm(dir, { recursive: true, force: true }))
const sentinel = path.join(dir, 'sentinel.txt')
const evidencePath = path.join(dir, 'evidence.json')
await writeFile(sentinel, 'do-not-overwrite\n')
await symlink(sentinel, evidencePath)
const result = spawnSync(process.execPath, [
'scripts/preflight-public-hive-gateway.mjs',
'--config', 'deploy/public-hive-gateway/hiverelay-config.example.json',
'--evidence', evidencePath
], {
encoding: 'utf8',
env: { ...process.env, HIVERELAY_API_KEY: 'unit-test-present' }
})
t.is(result.status, 0, result.stderr)
t.is(await readFile(sentinel, 'utf8'), 'do-not-overwrite\n')
const evidenceInfo = await lstat(evidencePath)
t.ok(evidenceInfo.isFile())
t.absent(evidenceInfo.isSymbolicLink())
t.is(evidenceInfo.mode & 0o077, 0, 'atomic output is not group/world accessible')
t.alike(JSON.parse(await readFile(evidencePath, 'utf8')), JSON.parse(result.stdout))
t.alike((await readdir(dir)).sort(), ['evidence.json', 'sentinel.txt'])
})
test('public gateway preflight CLI - deployment option mistakes fail closed', (t) => {
const script = 'scripts/preflight-public-hive-gateway.mjs'
const config = 'deploy/public-hive-gateway/hiverelay-config.example.json'
const run = (...args) => spawnSync(process.execPath, [script, '--config', config, ...args], {
encoding: 'utf8',
env: { ...process.env, HIVERELAY_API_KEY: 'unit-test-present' }
})
const unknown = run('--probe-orign', 'https://example.test/')
t.not(unknown.status, 0)
t.ok(unknown.stderr.includes('Unknown option: --probe-orign'))
const outputWithoutTemplate = run('--nginx-output', '/tmp/should-not-exist.conf')
t.not(outputWithoutTemplate.status, 0)
t.ok(outputWithoutTemplate.stderr.includes('--nginx-output requires --nginx-template'))
const pathWithoutProbe = run('--path', '/index.html')
t.not(pathWithoutProbe.status, 0)
t.ok(pathWithoutProbe.stderr.includes('--path') && pathWithoutProbe.stderr.includes('--probe-origin'))
const connectWithoutProbe = run('--connect-address', '127.0.0.1')
t.not(connectWithoutProbe.status, 0)
t.ok(connectWithoutProbe.stderr.includes('--connect-address') && connectWithoutProbe.stderr.includes('--probe-origin'))
const invalidConnectAddress = run(
'--probe-origin', `https://${KEY_LABEL}.${CONFIG.hiveAppHostSuffix}`,
'--connect-address', 'localhost'
)
t.not(invalidConnectAddress.status, 0)
t.ok(invalidConnectAddress.stderr.includes('--connect-address must be an explicit IP address'))
const offlineFleetEvidence = run('--mode', 'fleet')
t.not(offlineFleetEvidence.status, 0)
t.ok(offlineFleetEvidence.stderr.includes('--mode fleet evidence requires --probe-origin'))
const unattributedFleetProbe = run(
'--mode', 'fleet',
'--probe-origin', `https://${KEY_LABEL}.${CONFIG.hiveAppHostSuffix}`
)
t.not(unattributedFleetProbe.status, 0)
t.ok(unattributedFleetProbe.stderr.includes('--mode fleet live evidence requires --connect-address'))
const ambiguousNginx = run(
'--nginx-template', '/tmp/template.conf',
'--nginx-config', '/tmp/installed.conf',
'--certificate', '/tmp/cert.pem',
'--certificate-key', '/tmp/key.pem'
)
t.not(ambiguousNginx.status, 0)
t.ok(ambiguousNginx.stderr.includes('mutually exclusive'))
})