verae-staff-iam/docs/PRODUCTION-PKI-REFUSE.md
George Lambert 64da073787 docs: PRODUCTION-PKI-REFUSE matrix 501 vs lab 201
Operator-facing refuse matrix for enroll/csr paths.
2026-10-14 01:30:52 -04:00

80 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Production PKI refuse matrix (501 vs lab 201)
**Purpose:** single place for what **stays honest 501** in production vs what may return **lab-only 201**. Live production mint is **not shipped**. Lab path is never production.
Canonical detail: [ENROLL.md](../ENROLL.md) · backlog: [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md) · lab design: [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md) · revoke design: [CRL-OCSP-DESIGN.md](./CRL-OCSP-DESIGN.md).
## Gate summary
| Gate | Production / default | Lab opt-in |
|------|----------------------|------------|
| `LAB_CA_ENABLE` | unset / off | `=1` |
| `NODE_ENV=production` | **refuses lab** even if flag set | must **not** be production |
| Lab PKI dir (`LAB_PKI_DIR` or `./lab-pki`) | ignored | must exist + `init`’d (`ca.crt` + key) |
| Signer | none | `scripts/lab-test-ca.sh sign` (openssl) |
| Response markers | no cert body | `X-Lab-CA: 1`, `X-Lab-CA-Warning: NOT-FOR-PRODUCTION`, body `lab: true` |
Fail closed: missing flag, missing lab-pki, missing script, production env, or sign failure → **no silent success**.
## HTTP matrix — stays 501 vs lab 201
| Surface | Condition | HTTP | Body / headers | Production? |
|---------|-----------|------|----------------|-------------|
| `GET /enroll/csr/schema` | always | **200** | fields only; `mint: false` | OK (no crypto) |
| `GET /enroll/status` | default | **200** | `csrPostStatus: **501**`; `labCa.enabled: false` | OK |
| `GET /enroll/status` | lab ready | **200** | `csrPostStatus: **201**` (probe only); `labCa.enabled/ready` | Lab probe only — **not** a mint |
| `POST /enroll/csr` | missing / empty `csr` | **501** | `error: not_implemented` + `nextSteps` | **Stays 501** |
| `POST /enroll/csr` | invalid PEM shape | **400** | `error: invalid_csr_pem` | Shape gate (no mint) |
| `POST /enroll/csr` | valid-looking PEM, lab **off** | **501** | `not_implemented` | **Stays 501** |
| `POST /enroll/csr` | valid PEM, lab on, pki missing / not ready | **501** | `not_implemented` | **Stays 501** |
| `POST /enroll/csr` | valid PEM, lab on, `NODE_ENV=production` | **501** | lab gated off → scaffold | **Stays 501** (refuse) |
| `POST /enroll/csr` | valid PEM, lab on + pki ready + non-prod | **201** | `certificate` (+ optional `chain`); `X-Lab-CA: 1` | **LAB ONLY** |
| `POST /enroll/csr` | lab path, openssl sign fails | **502** | `lab_ca_sign_failed` + `X-Lab-CA: 1` | Lab failure (not production mint) |
| Login / My security UI | any | — | status chips / checklist only | No download-cert / mint button |
| `PUT /users/:username` `{ mtlsEnrolled }` | admin | **200** | flag only | OK (no cert issue) |
| Proxy mTLS headers | edge | — | presence flags on status | External PKI SoT |
## What always stays refused in production (no 201)
These must **never** return success cert material under production defaults:
| Capability | Status |
|------------|--------|
| Production CA / HSM / EST / ACME / SCEP mint | **Not shipped** → default POST **501** |
| Silent or default-on lab mint | **Forbidden** (`LAB_CA_ENABLE` default off; production env refuses) |
| Demo / stub / self-signed “real” staff cert from IAM | **Forbidden** |
| PKCS#12 / private key return | **Never** |
| Production CRL or OCSP from staff-iam | **Not shipped** (design only; lab empty `crl` offline) |
| Dropping default 501 without real `Signer` | **Forbidden** until live PKI checklist done |
## Operator path (production)
1. Issue workstation client certs in **external** reverse-proxy / internal PKI — not this app.
2. Do not call `POST /enroll/csr` expecting a production cert — it returns **501**.
3. Terminate mTLS at the proxy; forward verify/DN headers.
4. Confirm `GET /enroll/status` → `proxyHeadersSeen` / `mtlsPresent`.
5. Admin `mtlsEnrolled: true` → optional `STAFF_MTLS_ENFORCE=1`.
## Lab path (explicit only)
```sh
./scripts/lab-test-ca.sh init --dir ./lab-pki
LAB_CA_ENABLE=1 npm start # never with NODE_ENV=production
# POST /enroll/csr { "csr": "-----BEGIN CERTIFICATE REQUEST-----..." }
# → 201 + X-Lab-CA: 1 OR 502 on sign failure OR 501 if pki not ready
```
## Still left (live production PKI)
- Choose real CA model (external ACME/EST/HSM/delegated signer)
- AuthZ + full CSR ASN.1 / PoP / CN-SAN policy
- Bind cert → IAM user; metadata persist; renew/revoke
- Production CRL/OCSP (not lab empty CRL)
- Remove default 501 only after real `Signer`; keep no-fake-crypto tests
## Related
- [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md) — shipped surface + live checklist
- [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md) — lab script + gated HTTP
- [CRL-OCSP-DESIGN.md](./CRL-OCSP-DESIGN.md) — future revocation
- [ENROLL.md](../ENROLL.md) — states, headers, nextSteps