feat(control-plane): production native-CA wiring for on-disk P-256 CAs (A2-prep)

Wire the prod boot to issue frp-client + device leaves from the existing on-disk
P-256 CAs: NATIVE_* env vars, a file-backed KmsResolver (loads the PEM key,
validates prime256v1, never logs key material), buildFileBackedNativeCas threaded
into buildControlPlane via a nativeCas override. Fail-closed on partial NATIVE_*.
281 tests pass.
This commit is contained in:
Yaojia Wang
2026-07-18 15:04:04 +02:00
parent 10688b0dd1
commit af630143de
8 changed files with 597 additions and 10 deletions

View File

@@ -16,7 +16,8 @@ import { runMigrations } from './db/migrate.js'
import { createRedisRevocationBus } from './routing/bus.js'
import { createCapabilityVerifier, configureCapabilityVerifyKey } from './boot/verifier.js'
import { createRedisClient, createRedisPublisher } from './boot/redis.js'
import { buildControlPlane } from './main.js'
import { buildControlPlane, type ControlPlaneOverrides } from './main.js'
import { buildFileBackedNativeCas } from './boot/native-ca.js'
/** Admin API binds to loopback by default — never expose the provisioning API on a public interface. */
const DEFAULT_CP_BIND_HOST = '127.0.0.1'
@@ -57,7 +58,21 @@ async function main(): Promise<void> {
await configureCapabilityVerifyKey(env.capabilitySignPubkey)
const verifier = createCapabilityVerifier()
const { app } = await buildControlPlane(env, { stores, bus, verifier })
// A2-prep: when NATIVE_* on-disk CA material is configured, load the two REAL P-256 CAs (frp-client
// + device) from disk and thread them into the app so /enroll + /device/enroll issue leaves from the
// production CAs. `buildFileBackedNativeCas` fails fast (INV9) on unreadable/non-P-256 material. When
// unset, `buildControlPlane` keeps its existing behaviour (dev self-signed CAs, or fail-closed if
// NODE_ENV=production) — the dev/test injection path is untouched.
const nativeOverrides: Pick<ControlPlaneOverrides, 'production' | 'nativeCas' | 'nativeDnsZone'> =
env.nativeCa !== undefined
? {
production: true,
nativeCas: await buildFileBackedNativeCas(env, env.nativeCa),
nativeDnsZone: env.nativeCa.dnsZone,
}
: {}
const { app } = await buildControlPlane(env, { stores, bus, verifier, ...nativeOverrides })
const host = resolveBindHost(process.env)
const port = resolveBindPort(process.env)