Convert OPEN-ISSUES #4 external items to READY ops packages without shipping production mint. LAB_CA_ENABLE stays default off; NODE_ENV= production still refuses lab; default POST /enroll/csr remains 501. - docs/PROD-CA-CUTOVER-RUNBOOK.md (mint/renew/revoke cutover) - docs/AUTHZ-ENROLL-DESIGN.md (gates before dropping 501) - docs/CRL-OCSP-DEPLOY-CHECKLIST.md (proxy/CA revoke deploy) - Cross-links from README, ENROLL, PKI-MINT-BACKLOG, refuse/design docs npm test: 19/19 PASS
9.4 KiB
Production CA cutover runbook (READY package)
Status: READY package — operator cutover checklist for live device-cert mint / renew / revoke.
Not shipped in staff-iam code. Default POST /enroll/csr stays 501. Lab CA stays default off and is refused when NODE_ENV=production.
This package tells ops how to cut over to a real production CA without using scripts/lab-test-ca.sh or LAB_CA_ENABLE. It does not implement a CA inside the IAM Node process.
| Companion | Role |
|---|---|
| PKI-MINT-BACKLOG.md | Shipped surface + live-mint product checklist |
| AUTHZ-ENROLL-DESIGN.md | AuthN/AuthZ gates before dropping default 501 |
| CRL-OCSP-DEPLOY-CHECKLIST.md | Revocation distribution at proxy/CA |
| PRODUCTION-PKI-REFUSE.md | What stays refused under production defaults |
| TEST-CA-DESIGN.md | Lab fixture only — never production trust |
| ENROLL.md | mTLS states, proxy headers, status contract |
Hard rules (do not violate on cutover)
- LAB_CA_ENABLE default off — never set in production images, systemd units, or k8s manifests.
NODE_ENV=productionrefuses lab even if someone setsLAB_CA_ENABLE=1(server gate).- Do not trust
lab-pki/roots on staff fleets or production reverse proxies. - Do not drop default 501 until a real
Signer+ AuthZ (see AuthZ package) pass go/no-go. - Private keys never enter the IAM user store, audit log, or enroll response as PKCS#12 passwords / leaf keys.
- Proxy remains TLS SoT for “cert present on this request”; IAM
mtlsEnrolledis enrollment policy only.
Choose production CA model (product decision — pick one)
| Model | When to choose | staff-iam role |
|---|---|---|
| A. External corporate PKI (ADCS, Venafi, smallstep, internal Intermediate) | Org already issues workstation client certs | Stay proxy-only; optional later Signer HTTP client |
| B. EST / SCEP / ACME device profile | Managed fleet enrollment at edge | Edge enrolls; IAM still proxy headers + flag |
| C. HSM-backed delegated Signer service | Product wants in-app POST /enroll/csr live mint |
IAM calls Signer over mTLS; never holds CA key |
| D. Keep external-only forever | Lowest risk; current default | No code change; this runbook stops at proxy cutover |
Recommended first cutover: Model A or B (no IAM crypto). Model C only after AuthZ package + Signer contract land.
Document the choice out-of-band (ticket / security design). Do not invent CA hostnames in git.
Pre-flight (ops-owned CA + proxy)
- P-01 Issuing CA / intermediate chosen (model A–C above); HSM or offline root procedure documented offline
- P-02 Leaf profile: clientAuth EKU, short lifetime (hours–days preferred), CN/SAN policy agreed with security
- P-03 Reverse proxy terminates mTLS and verifies client chain against production trust store only
- P-04 Proxy forwards enroll headers per ENROLL.md (
ssl-client-verify, DN, optional PEM presence — names as deployed) - P-05 CRL/OCSP path ready or short-lifetime-only policy accepted — CRL-OCSP-DEPLOY-CHECKLIST.md
- P-06 Mint / renew / revoke operator procedures exist at the CA (GUI, API, or runbook) outside staff-iam
- P-07 Confirm production staff-iam env:
NODE_ENV=production,LAB_CA_ENABLEunset, nolab-pkivolume mounts - P-08 Confirm
GET /enroll/status→csrPostStatus: 501,labCa.enabled: falseon production host
# production host smoke (expect scaffold — not a cert)
curl -sS https://<staff-iam-host>/enroll/status | jq '{csrPostStatus, labCa}'
# → csrPostStatus: 501, labCa.enabled: false (or equivalent probe false)
curl -sS -o /tmp/csr-body.json -w '%{http_code}\n' -X POST https://<staff-iam-host>/enroll/csr \
-H 'content-type: application/json' \
-d '{"csr":"-----BEGIN CERTIFICATE REQUEST-----\nMIIB\n-----END CERTIFICATE REQUEST-----"}'
# → 501 (or 400 if PEM shape invalid) — never 201 under production defaults
Cutover path A/B — external PKI (no IAM mint)
Use when product keeps mint outside staff-iam (current shipped design).
Mint (issue workstation client cert)
- Operator or MDM enrolls device at corporate CA / EST / SCEP / ACME profile.
- Install leaf + chain in OS/browser client-cert store (org procedure).
- Browser hits staff door through mTLS-terminating proxy.
- Confirm
GET /enroll/status:proxyHeadersSeen/mtlsPresent/proxy.verifyOkas configuredstateprogressesnot_enrolled→pending_proxy→ ready for admin flag
- IAM admin:
PUT /users/:username{ "mtlsEnrolled": true } - Only then consider
STAFF_MTLS_ENFORCE=1on the door.
Renew
- Renew at CA before
notAfter(org cadence; prefer automated short-lived renew). - Replace client cert in workstation store.
- No staff-iam API required unless metadata binding ships later (backlog).
- Spot-check
/enroll/statusstill shows proxy verify after renew.
Revoke / offboard
- CA revoke serial (reason
keyCompromiseorcessationOfOperation). - IAM:
mtlsEnrolled: false(+ kill sessions if policy requires). - Wait for proxy CRL/OCSP cache TTL or flush — CRL-OCSP-DEPLOY-CHECKLIST.md.
- Verify: client no longer yields
proxy.verifyOk; enforce path denies.
Do not call POST /enroll/csr expecting a production cert — it returns 501.
Cutover path C — delegated Signer (future live mint)
Blocked until product implements real Signer + AuthZ. This section is the go/no-go gate, not permission to code.
Go criteria (all required)
- C-01 Signer service deployed (HSM or equivalent); mTLS client identity for staff-iam only
- C-02 AUTHZ-ENROLL-DESIGN.md gates implemented and tested (session + role + rate limit + PoP)
- C-03 Full CSR ASN.1 validation (not header-shape only); CN/SAN bind to IAM user — never trust client CN alone
- C-04 Issue returns cert chain PEM only (no private key / PKCS#12 password in JSON)
- C-05 Persist metadata only: serial, fingerprint, notAfter (no leaf private key)
- C-06 Renew + revoke call Signer; offboard runbook dual-writes CA revoke +
mtlsEnrolled: false - C-07 Default 501 removed only when Signer healthy; failure modes stay fail-closed (502/503), never lab CA
- C-08 Tests: production env still refuses
LAB_CA_ENABLE; no demo PEMs; audit never stores CSR PEM body - C-09 ENROLL.md / README / this runbook updated for the live path in the same release
- C-10 Security sign-off on CA model + key custody
Signer contract sketch (names only)
Signer.issue({ csrPem, subjectUserId, deviceId? }) → { certificatePem, chainPem?, serial, notAfter }
Signer.renew({ serial | csrPem, subjectUserId }) → same as issue
Signer.revoke({ serial, reason }) → { revokedAt }
- Transport: mTLS or SPIFFE between staff-iam and Signer — not lab shell-out.
- staff-iam maps HTTP:
POST /enroll/csr→Signer.issue(AuthZ’d)- future renew/revoke routes →
Signer.renew/Signer.revoke
- Lab path (
lab-test-ca.sh) remains CI/dev fixture only; never selected whenNODE_ENV=production.
Rollback
- Feature-flag or deploy previous staff-iam build that restores default 501.
- Leave external proxy mTLS +
mtlsEnrolledpath working (path A/B still valid). - Revoke any leaves issued during failed cutover at the CA.
- Do not “fix” with
LAB_CA_ENABLE=1in production.
Production env attestation (required every deploy)
| Check | Expected |
|---|---|
NODE_ENV |
production |
LAB_CA_ENABLE |
unset / empty |
LAB_PKI_DIR |
unset; no lab-pki volume |
POST /enroll/csr without live Signer |
501 not_implemented |
GET /enroll/status .labCa |
enabled/ready false |
| Trust store on proxy | production intermediate/root only |
| Image contents | no committed lab-pki/ keys (gitignored) |
Do not
- Do not enable
LAB_CA_ENABLEon production, staging-shared, or customer-adjacent hosts - Do not point production proxies at
lab-pki/lab-root.crt.pem - Do not drop 501 without C-01…C-10
- Do not return PKCS#12 or private keys from staff-iam
- Do not treat
mtlsEnrolled: falsealone as cryptographic revoke (always CA revoke too) - Do not invent CA URLs, RA credentials, or HSM slot IDs in this repository
Still left (ops / product — outside this READY package)
- Deploy real CA or Signer (hostnames, credentials, HSM)
- Execute mint/renew/revoke on live serials
- Wire production proxy trust + headers
- Implement AuthZ + Signer client code (separate engineering change gated by C-01…C-10)
- Production CRL/OCSP endpoints — CRL-OCSP-DEPLOY-CHECKLIST.md