12 KiB
Test CA design (lab opt-in HTTP + future production)
Status:
- Offline lab helper shipped:
scripts/lab-test-ca.sh - Opt-in lab HTTP shipped (LAB ONLY):
LAB_CA_ENABLE=1+ initialized lab-pki →POST /enroll/csrmay shell-out tolab-test-ca.sh sign→ 201 cert PEM + headersX-Lab-CA: 1/X-Lab-CA-Warning: NOT-FOR-PRODUCTION - Default (flag off):
POST /enroll/csrremains 501 - Production mint: still not shipped — never enable lab CA in production (
NODE_ENV=productionrefuses it)
This document describes (1) the lab script, (2) the gated lab HTTP path, and (3) how a future production Signer would replace the default 501. Never production for lab material.
Live mint product backlog and hard “no fake crypto” rules: PKI-MINT-BACKLOG.md. Current proxy-only enroll contract: ENROLL.md.
Lab script + opt-in HTTP (LAB ONLY — never production)
| Item | Detail |
|---|---|
| Path | scripts/lab-test-ca.sh |
| Offline | init → self-signed lab root under lab-pki/ (gitignored); sign --csr → short-lived leaf PEM + chain on disk; optional crl → empty lab CRL |
| Opt-in HTTP | LAB_CA_ENABLE=1 + lab-pki ready → staff-iam shells out to sign on POST /enroll/csr → 201 + X-Lab-CA: 1 |
| Default | Flag off → POST /enroll/csr 501 (unchanged production scaffold) |
| Production refuse | NODE_ENV=production → lab path off even if LAB_CA_ENABLE=1 |
| Warnings | Script banner + response warning field + X-Lab-CA-Warning: NOT-FOR-PRODUCTION |
| Relation to live mint | Lab/dev only. Production mint needs a real Signer (see backlog) — lab CA must never be the default or production path |
# example (lab machine only)
./scripts/lab-test-ca.sh init --dir ./lab-pki
openssl req -new -newkey rsa:2048 -nodes -keyout dev.key -out dev.csr -subj "/CN=lab-user@staff.local"
# offline sign still works:
./scripts/lab-test-ca.sh sign --dir ./lab-pki --csr dev.csr --out ./lab-pki/leaf.crt.pem
# opt-in HTTP (LAB ONLY):
LAB_CA_ENABLE=1 LAB_PKI_DIR=./lab-pki npm start
curl -sS -D- -X POST http://127.0.0.1:3028/enroll/csr \
-H 'content-type: application/json' \
--data-binary @<(python3 -c 'import json,sys; print(json.dumps({"csr":open("dev.csr").read()}))')
# expect: HTTP 201, X-Lab-CA: 1, body.certificate PEM
# default without LAB_CA_ENABLE → 501
Do not point production or shared staging reverse proxies at lab-pki/lab-root.crt.pem.
Purpose
When (and only when) product ships real CSR intake + signing, CI and local dev need a throwaway certificate authority that:
- Issues short-lived client certs against known test keys.
- Never appears in production builds or default operator paths.
- Proves the mint/renew/status code paths without calling a corporate HSM or public ACME.
The test CA is a fixture toolchain, not a staff-iam runtime feature and not a substitute for external PKI in operator docs.
Non-goals (now and later)
| Non-goal | Why |
|---|---|
Return success PEM when LAB_CA_ENABLE is off |
Default must stay honest 501 |
Enable lab CA under NODE_ENV=production |
Hard-refused in server |
| Ship lab-pki private keys in production images | Keys stay gitignored / CI secret only |
| Soften production mint with static demo PEMs | Violates “no fake crypto”; lab path is explicit + headed X-Lab-CA: 1 only |
| Trust lab roots on real staff fleets | Production mint must use a real CA model (see backlog) |
| Browser “Download test cert” in login / My security | Operator path stays proxy/PKI until live mint is real |
| Reuse test CA roots in staging that faces real staff | Test roots are untrusted junk; isolate to CI/dev |
| Parse or store private keys in IAM user records | Metadata only (serial, fingerprint, notAfter) if mint ever lands |
Default honest contract: schema 200 fields-only, POST 501, status csrPostStatus: 501. Lab tests may set LAB_CA_ENABLE=1 + temp lab-pki and assert 201 + X-Lab-CA: 1 (skip if no openssl).
Relationship to live PKI backlog
PKI-MINT-BACKLOG.md lists what production mint needs (CA model, AuthZ, CSR policy, bind to user, renew, revoke). Lab/test tooling is a companion, not a substitute:
Product mint (future) Lab CA (today, opt-in)
───────────────────── ──────────────────────────
External/HSM/EST signer ←→ scripts/lab-test-ca.sh
POST /enroll/csr succeeds LAB_CA_ENABLE=1 + lab-pki → 201 + X-Lab-CA:1
(never default lab) Default off → 501; production NODE_ENV refuses
Proxy trusts corp roots Dev proxy may trust lab-pki root (lab only)
| Stage | Lab script | HTTP POST /enroll/csr |
|---|---|---|
| Default today | Offline init/sign on disk | 501 scaffold |
| Lab opt-in today | Same script, server shell-out | 201 + X-Lab-CA: 1 when flag + pki ready |
| Live production mint | Still offline fixture / CI helper | Real production Signer (drop default 501) |
Do not enable LAB_CA_ENABLE by default or in production images.
Layout
Shipped now (offline script only):
platform/verae-staff-iam/
scripts/
lab-test-ca.sh # LAB ONLY: init root + sign CSR + empty crl → PEM on disk
lab-pki/ # default output dir (gitignored; keys never committed)
Proposed later (when live mint + gated test signer are scheduled):
platform/verae-staff-iam/
test/pki/ # gitignored material OR committed *public* only
README.md # how to regenerate; no private keys in git if policy says so
root-ca.crt # test root (public)
intermediate-ca.crt # optional
scripts/
lab-test-ca.sh # keep as offline fallback / pre-HTTP fixture
test-ca-init.sh # optional rename/split once CI profile solidifies
test-ca-sign-csr.sh
src/pki/ # only if in-process signer is chosen later
testCa.js # gated by STAFF_TEST_CA=1; absent from prod image
# MUST NOT call lab-test-ca.sh from default server boot
Private keys for the lab/test CA stay on the developer machine or CI secret store (LAB_PKI_DIR / future TEST_CA_KEY_DIR), never in the IAM JSON user store, never in Docker images tagged for production, never logged. The Node process must not read lab-pki/ unless a future explicit non-production gate is designed — default server path stays 501.
Trust and environment gates
| Variable | Effect |
|---|---|
LAB_CA_ENABLE=1 |
Shipped. Allows shell-out lab sign on POST /enroll/csr when lab-pki ready |
LAB_PKI_DIR |
Lab CA directory (default ./lab-pki) |
NODE_ENV=production |
Refuses lab CA even if LAB_CA_ENABLE=1 |
STAFF_MTLS_ENFORCE |
Unchanged: still requires real proxy verify headers |
Fail closed: missing key dir, production env, or unset flag → 501 (no mint).
Signing flow (future test only)
- Test generates a keypair + PKCS#10 CSR (OpenSSL,
step, or node-forge in the test process). - CSR PEM posted to the future live
POST /enroll/csror passed totest-ca-sign-csr.shfor pure unit tests that do not boot HTTP. - Test CA validates: PEM parse, key type (e.g. P-256 or RSA-2048+), CN/SAN allow-list for fixtures (
test-user@staff.local,deviceIdprefixtest-). - Sign with intermediate; return cert chain PEM (and optional PKCS#12 with a test-only passphrase).
- Test asserts response shape, then feeds the client cert to a dev reverse proxy that forwards the same verify/DN/PEM headers ENROLL.md already documents.
GET /enroll/statusshould showmtlsPresent/ proxy flags from headers — still no PEM bodies in status JSON.
In-process mint (if chosen) would call the same validation + sign helpers the production CA client interface uses; the test CA is one implementation of Signer, not a parallel fake route.
What tests should prove
| Layer | Assertion |
|---|---|
| Contract (default) | GET /enroll/csr/schema → 200, mint: false; POST /enroll/csr → 501; no cert fields |
| Lab integration (today) | LAB_CA_ENABLE=1 + lab-pki + openssl → 201 + X-Lab-CA: 1 + certificate PEM; skip if no openssl |
| Unit (future production) | CSR policy rejects bad PEM / wrong key size / disallowed CN |
| Integration (future production) | Real Signer → 201 without lab headers; serial stored as metadata |
| Proxy (future) | Client presents test cert; proxy headers flip proxyHeadersSeen.*; enforce path works |
| Negative | NODE_ENV=production + test flag → no sign; anonymous rate limit; revoke on offboard |
Do not add “golden” static client certs that the HTTP API returns as if newly minted. Fixtures may pre-place cert files on disk for proxy tests without going through mint.
CA profile (suggested defaults)
- Root: RSA-4096 or P-384, pathlen 1, 10-year notAfter (local only).
- Intermediate: pathlen 0, EKU clientAuth (+ optional digitalSignature).
- Leaf: notAfter ≤ 7 days for CI; SAN URI or DNS under
.test/ email bound to fixture username. - Hash: SHA-256.
- Serial: random 64-bit+; persist fingerprint in test DB only.
Exact algorithms should match whatever production CA model is selected in PKI-MINT-BACKLOG.md so tests exercise the real policy engine.
Operator vs developer story
- Operators (production): unchanged — issue workstation certs in corporate PKI; proxy terminates mTLS; IAM flag + enforce. See ENROLL.md. Never run
lab-test-ca.shfor real staff devices. - Developers (lab now): run
scripts/lab-test-ca.sh init, setLAB_CA_ENABLE=1, keep material under gitignoredlab-pki/, optionally point a local dev proxy atlab-root.crt.pem. ExpectX-Lab-CA: 1on 201 — never treat as production. - Developers (default): leave
LAB_CA_ENABLEunset → HTTP mint stays 501. - CI (future): generate or restore test CA into a job-local volume; tear down after; never publish leaves as release artifacts.
Implementation order (blocked on product)
- Finish production CA model decision in PKI-MINT-BACKLOG.md (“Left for live PKI”).
- Define
Signerinterface + AuthN/AuthZ forPOST /enroll/csr. - Add this test CA as the first
Signerimplementation behind env gates. - Wire integration tests; keep production images free of test keys and
STAFF_TEST_CAdefaults. - Update ENROLL.md / README only when mint is real — not before.
Hard rules
- Default server path does not mint —
LAB_CA_ENABLEoff → 501. Lab HTTP is explicit opt-in only. - No fake crypto in production API — production stays 501 until real
Signer; lab 201 always carriesX-Lab-CA: 1+ warning body. - Lab/test roots never trust-anchor production.
- Never enable lab CA by default or under
NODE_ENV=production. - Cross-link changes: production mint PR must update PKI-MINT-BACKLOG.md, this doc, and ENROLL.md together.
Related docs
- PKI-MINT-BACKLOG.md — shipped proxy/schema/501 surface; live mint checklist; no-fake-crypto rules
- CRL-OCSP-DESIGN.md — future production CRL/OCSP; lab empty CRL helper
- ENROLL.md — mTLS states, proxy headers, CSR schema/scaffold
- README.md — service overview and security gates
- TOTP.md — orthogonal authenticator 2FA (real crypto; unrelated to device-cert mint)