docs: PKI READY packages — prod CA cutover, AuthZ enroll, CRL/OCSP deploy
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
This commit is contained in:
parent
10307334c5
commit
7e8fc7c1f9
9 changed files with 643 additions and 0 deletions
|
|
@ -232,6 +232,13 @@ staff-iam **never** parses the PEM, checks expiry, or matches CN to username. Bi
|
||||||
|
|
||||||
## Related docs
|
## Related docs
|
||||||
|
|
||||||
|
- [docs/PROD-CA-CUTOVER-RUNBOOK.md](./docs/PROD-CA-CUTOVER-RUNBOOK.md) — **READY** production CA mint/renew/revoke cutover
|
||||||
|
- [docs/AUTHZ-ENROLL-DESIGN.md](./docs/AUTHZ-ENROLL-DESIGN.md) — **READY** AuthZ design before dropping default 501
|
||||||
|
- [docs/CRL-OCSP-DEPLOY-CHECKLIST.md](./docs/CRL-OCSP-DEPLOY-CHECKLIST.md) — **READY** production CRL/OCSP deploy
|
||||||
|
- [docs/PKI-MINT-BACKLOG.md](./docs/PKI-MINT-BACKLOG.md) — live mint backlog; lab vs production
|
||||||
|
- [docs/PRODUCTION-PKI-REFUSE.md](./docs/PRODUCTION-PKI-REFUSE.md) — refuse matrix
|
||||||
|
- [docs/TEST-CA-DESIGN.md](./docs/TEST-CA-DESIGN.md) — lab CA only
|
||||||
|
- [docs/CRL-OCSP-DESIGN.md](./docs/CRL-OCSP-DESIGN.md) — revocation design
|
||||||
- [TOTP.md](./TOTP.md) — authenticator setup, `STAFF_TOTP_REQUIRED`, login gate
|
- [TOTP.md](./TOTP.md) — authenticator setup, `STAFF_TOTP_REQUIRED`, login gate
|
||||||
- [README.md](./README.md) — install, usage, seed users
|
- [README.md](./README.md) — install, usage, seed users
|
||||||
- [SUMMARY.md](./SUMMARY.md) — one-line service summary
|
- [SUMMARY.md](./SUMMARY.md) — one-line service summary
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,11 @@ Open `http://127.0.0.1:3028/login`. Persist store with `STAFF_IAM_PATH`. Login i
|
||||||
| [ENROLL.md](./ENROLL.md) | Device cert (mTLS): status + CSR schema; default `POST /enroll/csr` → **501**; opt-in **LAB ONLY** `LAB_CA_ENABLE=1` → 201 + `X-Lab-CA:1` (never production) |
|
| [ENROLL.md](./ENROLL.md) | Device cert (mTLS): status + CSR schema; default `POST /enroll/csr` → **501**; opt-in **LAB ONLY** `LAB_CA_ENABLE=1` → 201 + `X-Lab-CA:1` (never production) |
|
||||||
| [docs/TEST-CA-DESIGN.md](./docs/TEST-CA-DESIGN.md) | Lab CA script + gated HTTP path; production mint still backlog |
|
| [docs/TEST-CA-DESIGN.md](./docs/TEST-CA-DESIGN.md) | Lab CA script + gated HTTP path; production mint still backlog |
|
||||||
| [docs/PKI-MINT-BACKLOG.md](./docs/PKI-MINT-BACKLOG.md) | Live production PKI checklist |
|
| [docs/PKI-MINT-BACKLOG.md](./docs/PKI-MINT-BACKLOG.md) | Live production PKI checklist |
|
||||||
|
| [docs/PROD-CA-CUTOVER-RUNBOOK.md](./docs/PROD-CA-CUTOVER-RUNBOOK.md) | **READY:** production CA mint/renew/revoke cutover (ops; no IAM CA) |
|
||||||
|
| [docs/AUTHZ-ENROLL-DESIGN.md](./docs/AUTHZ-ENROLL-DESIGN.md) | **READY:** AuthZ design before dropping default 501 |
|
||||||
|
| [docs/CRL-OCSP-DEPLOY-CHECKLIST.md](./docs/CRL-OCSP-DEPLOY-CHECKLIST.md) | **READY:** production CRL/OCSP proxy/CA deploy checklist |
|
||||||
|
| [docs/PRODUCTION-PKI-REFUSE.md](./docs/PRODUCTION-PKI-REFUSE.md) | Refuse matrix: default 501 vs lab 201; prod never lab |
|
||||||
|
| [docs/CRL-OCSP-DESIGN.md](./docs/CRL-OCSP-DESIGN.md) | Revocation architecture (design only) |
|
||||||
| [TOTP.md](./TOTP.md) | Authenticator 2FA + **`STAFF_TOTP_REQUIRED`** enroll path; how login / app UI use flags |
|
| [TOTP.md](./TOTP.md) | Authenticator 2FA + **`STAFF_TOTP_REQUIRED`** enroll path; how login / app UI use flags |
|
||||||
| [NATS.md](./NATS.md) | NATS notes (if used) |
|
| [NATS.md](./NATS.md) | NATS notes (if used) |
|
||||||
| [SUMMARY.md](./SUMMARY.md) | One-line service summary |
|
| [SUMMARY.md](./SUMMARY.md) | One-line service summary |
|
||||||
|
|
|
||||||
241
docs/AUTHZ-ENROLL-DESIGN.md
Normal file
241
docs/AUTHZ-ENROLL-DESIGN.md
Normal file
|
|
@ -0,0 +1,241 @@
|
||||||
|
# AuthZ enroll design (READY package) — drop default 501
|
||||||
|
|
||||||
|
**Status: READY package** — design for replacing default **`POST /enroll/csr` → 501** with a real AuthZ’d mint path.
|
||||||
|
**Not implemented.** Until a production `Signer` exists, the scaffold **must** stay 501. Lab mint (`LAB_CA_ENABLE=1`) is **orthogonal** and remains default-off / production-refused.
|
||||||
|
|
||||||
|
| Companion | Role |
|
||||||
|
|-----------|------|
|
||||||
|
| [PROD-CA-CUTOVER-RUNBOOK.md](./PROD-CA-CUTOVER-RUNBOOK.md) | Ops cutover + Signer go/no-go |
|
||||||
|
| [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md) | Live mint checklist |
|
||||||
|
| [PRODUCTION-PKI-REFUSE.md](./PRODUCTION-PKI-REFUSE.md) | Refuse matrix |
|
||||||
|
| [ENROLL.md](../ENROLL.md) | Current HTTP contract |
|
||||||
|
| [CRL-OCSP-DEPLOY-CHECKLIST.md](./CRL-OCSP-DEPLOY-CHECKLIST.md) | Revoke after issue |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Define **who** may call live mint/renew/revoke, **what** must be validated before sign, and **when** it is safe to remove the default 501 scaffold — without weakening lab refuse rules.
|
||||||
|
|
||||||
|
```
|
||||||
|
Browser / agent
|
||||||
|
│ session cookie or Bearer staff_session
|
||||||
|
▼
|
||||||
|
staff-iam POST /enroll/csr
|
||||||
|
│ 1. AuthN session
|
||||||
|
│ 2. AuthZ role/permission + rate limit
|
||||||
|
│ 3. CSR PEM + ASN.1 + PoP + CN/SAN policy
|
||||||
|
│ 4. Bind subject → IAM user id (server-side)
|
||||||
|
▼
|
||||||
|
Signer.issue (external / HSM) ──► chain PEM + serial
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
IAM metadata persist (serial, fingerprint, notAfter)
|
||||||
|
audit csr_attempt / csr_issued (no PEM body)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current behavior (must keep until go criteria)
|
||||||
|
|
||||||
|
| Condition | Result |
|
||||||
|
|-----------|--------|
|
||||||
|
| Default / production | **501** `not_implemented` after PEM shape gate |
|
||||||
|
| Invalid PEM shape | **400** `invalid_csr_pem` |
|
||||||
|
| `LAB_CA_ENABLE=1` + lab-pki + non-production | **201** + `X-Lab-CA: 1` (LAB ONLY) |
|
||||||
|
| `NODE_ENV=production` + `LAB_CA_ENABLE=1` | Lab **refused** → still **501** |
|
||||||
|
| Audit | `csr_attempt` flags only — **never** store CSR PEM |
|
||||||
|
|
||||||
|
Do **not** partially “soften” 501 with demo PEMs or default-on lab.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## AuthN requirements (live path)
|
||||||
|
|
||||||
|
| Requirement | Detail |
|
||||||
|
|-------------|--------|
|
||||||
|
| Signed-in staff session | Cookie `staff_session` or `Authorization: Bearer` same session token |
|
||||||
|
| No anonymous mint | Live `POST /enroll/csr` **rejects 401** when unauthenticated (unlike today’s discoverability 501) |
|
||||||
|
| TOTP policy orthogonal | If `totpEnabled` / `STAFF_TOTP_REQUIRED`, existing login gates apply before mint |
|
||||||
|
| mTLS optional at mint time | Prefer mint from enrolled session; proxy client-cert may be absent during first enroll |
|
||||||
|
| Service accounts | If automation mints, dedicated IAM principal + permission — not shared admin passwords |
|
||||||
|
|
||||||
|
**Schema discovery** may stay optionally unauthenticated:
|
||||||
|
|
||||||
|
- `GET /enroll/csr/schema` → 200 fields-only (today)
|
||||||
|
- `GET /enroll/status` → 200 (today)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## AuthZ requirements (live path)
|
||||||
|
|
||||||
|
### Permission strings (proposed)
|
||||||
|
|
||||||
|
| Permission | Who | Allows |
|
||||||
|
|------------|-----|--------|
|
||||||
|
| `staff.enroll.self` | any active staff (default grant on role catalog or all named roles) | Mint/renew **own** device cert only |
|
||||||
|
| `staff.enroll.admin` | `owner`, `iam-admin`, `admin` | Mint/renew/revoke **for another** user (break-glass / IT desk) |
|
||||||
|
| `staff.enroll.revoke` | `owner`, `iam-admin` (+ optional security role) | Call Signer.revoke + clear metadata |
|
||||||
|
|
||||||
|
Exact permission ids are **not frozen** — register in `src/roles.js` map when implementing. Edge dual-staff catalog (`starter` / `ender` / `admin`) does **not** by itself grant enroll admin.
|
||||||
|
|
||||||
|
### Binding rules
|
||||||
|
|
||||||
|
1. **Self mint:** path subject = session user id; ignore client-supplied user id fields.
|
||||||
|
2. **Admin mint:** body may include `username` / `userId` target; require `staff.enroll.admin`; audit both actor and target.
|
||||||
|
3. **CN / SAN:** derived server-side from IAM username / email / device record — client `commonName` is a **hint** only (same as lab today) and must match policy or be overwritten.
|
||||||
|
4. **deviceId:** optional stable workstation id; bind in metadata; re-mint same device replaces serial (revoke old if policy says so).
|
||||||
|
|
||||||
|
### Rate limits
|
||||||
|
|
||||||
|
| Scope | Suggested default | On exceed |
|
||||||
|
|-------|-------------------|-----------|
|
||||||
|
| Per user self-mint | 5 / hour | **429** |
|
||||||
|
| Per admin actor | 30 / hour | **429** |
|
||||||
|
| Per IP (unauth noise) | already covered if 401; keep shape-gate cheap | — |
|
||||||
|
|
||||||
|
Persist counters beside existing login rate-limit patterns (memory/SQLite). Audit every deny.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CSR validation ladder (before Signer)
|
||||||
|
|
||||||
|
Today only **PEM armor shape** is checked. Live path must add:
|
||||||
|
|
||||||
|
| Step | Reject |
|
||||||
|
|------|--------|
|
||||||
|
| 1. PEM armor + non-empty body | 400 `invalid_csr_pem` (shipped) |
|
||||||
|
| 2. Parse PKCS#10 ASN.1 | 400 `invalid_csr_asn1` |
|
||||||
|
| 3. Key type/size policy (e.g. RSA ≥ 2048 or P-256) | 400 `csr_key_policy` |
|
||||||
|
| 4. No private-key PEM confusion | 400 (already shape-adjacent) |
|
||||||
|
| 5. Proof-of-possession if separate from CSR signature | 400 `csr_pop_failed` |
|
||||||
|
| 6. Attribute / extension allow-list | 400 `csr_ext_policy` |
|
||||||
|
| 7. AuthZ + bind subject | 401 / 403 |
|
||||||
|
| 8. Signer.issue | 502/503 on signer failure — **fail closed** |
|
||||||
|
|
||||||
|
**Never** fall through to lab CA when Signer fails under production.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## HTTP contract changes (when live)
|
||||||
|
|
||||||
|
### `POST /enroll/csr` (live)
|
||||||
|
|
||||||
|
**Request** (extends schema fields):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"csr": "-----BEGIN CERTIFICATE REQUEST-----...-----END CERTIFICATE REQUEST-----",
|
||||||
|
"deviceId": "optional-workstation-id",
|
||||||
|
"commonName": "optional-hint-only",
|
||||||
|
"sans": ["optional", "dns-or-email-hints"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Success 201** (production — **no** `X-Lab-CA`):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"certificate": "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----",
|
||||||
|
"chain": "-----BEGIN CERTIFICATE-----... optional ...",
|
||||||
|
"serial": "hex",
|
||||||
|
"notAfter": "ISO-8601",
|
||||||
|
"fingerprint": "sha256-hex-of-der"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Errors:** 400 validation · 401 unauth · 403 forbidden · 429 rate · 501 only if Signer **feature flag off** · 502/503 signer down.
|
||||||
|
|
||||||
|
### Lab coexistence
|
||||||
|
|
||||||
|
| Env | Live Signer flag | LAB_CA_ENABLE | Result |
|
||||||
|
|-----|------------------|---------------|--------|
|
||||||
|
| production | off | any | **501** (lab refused) |
|
||||||
|
| production | on | any | Signer path only; lab never |
|
||||||
|
| dev/test | off | 0 | **501** |
|
||||||
|
| dev/test | off | 1 | lab **201** + `X-Lab-CA: 1` |
|
||||||
|
| dev/test | on | 1 | prefer Signer if configured; else lab — document one winner in impl PR |
|
||||||
|
|
||||||
|
**Invariant:** `NODE_ENV=production` ⇒ lab path code returns false (`labCaEnabled()`).
|
||||||
|
|
||||||
|
### Dropping default 501
|
||||||
|
|
||||||
|
Safe only when **all** hold:
|
||||||
|
|
||||||
|
- [ ] **A-01** Signer client integrated + health check
|
||||||
|
- [ ] **A-02** AuthN required on mint
|
||||||
|
- [ ] **A-03** AuthZ permissions enforced (self vs admin)
|
||||||
|
- [ ] **A-04** Full CSR validation ladder (not shape-only)
|
||||||
|
- [ ] **A-05** Subject bind server-side
|
||||||
|
- [ ] **A-06** Rate limits on
|
||||||
|
- [ ] **A-07** Metadata persist + audit without PEM
|
||||||
|
- [ ] **A-08** Tests: unauth 401; forbid cross-user without admin; prod refuses lab; no fake cert fixtures pretending production
|
||||||
|
- [ ] **A-09** Feature flag `STAFF_CSR_MINT=1` (name TBD) default **off** for one release, then default on only after soak
|
||||||
|
- [ ] **A-10** Cutover runbook C-01…C-10 signed off — [PROD-CA-CUTOVER-RUNBOOK.md](./PROD-CA-CUTOVER-RUNBOOK.md)
|
||||||
|
|
||||||
|
Until then keep:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "error": "not_implemented", "nextSteps": [ "… external PKI …" ] }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Renew / revoke AuthZ
|
||||||
|
|
||||||
|
| Action | AuthZ | Notes |
|
||||||
|
|--------|-------|-------|
|
||||||
|
| Renew self | `staff.enroll.self` + owns serial/deviceId | May be same `POST /enroll/csr` with existing metadata |
|
||||||
|
| Renew other | `staff.enroll.admin` | IT desk |
|
||||||
|
| Revoke self | `staff.enroll.self` or force admin-only (product choice) | Always Signer.revoke + clear metadata |
|
||||||
|
| Revoke other / offboard | `staff.enroll.revoke` | Dual-write CA + `mtlsEnrolled: false` |
|
||||||
|
| Bulk offboard | admin + automation principal | All serials for user |
|
||||||
|
|
||||||
|
Revoke API must **not** succeed on IAM flag flip alone — see CRL package.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Audit events (live)
|
||||||
|
|
||||||
|
| action | detail flags (no PEM) |
|
||||||
|
|--------|------------------------|
|
||||||
|
| `csr_attempt` | outcome, pemShape, labCa, signedIn (shipped) |
|
||||||
|
| `csr_issued` | serial, fingerprint, deviceId, targetUser, actor |
|
||||||
|
| `csr_renewed` | oldSerial, newSerial, actor |
|
||||||
|
| `csr_denied` | reason=authz\|rate\|policy, actor, target |
|
||||||
|
| `csr_revoked` | serial, reason, actor |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation order
|
||||||
|
|
||||||
|
1. Keep scaffold 501 + lab refuse (done).
|
||||||
|
2. Land Signer interface + feature flag **off**.
|
||||||
|
3. AuthN/AuthZ + rate limit behind flag (still 501 when flag off).
|
||||||
|
4. CSR ASN.1 validation behind flag.
|
||||||
|
5. Metadata + status fields.
|
||||||
|
6. Soak with flag on in lab/staging against **real** Signer (not lab-test-ca as production stand-in).
|
||||||
|
7. Production cutover per runbook; remove default 501 only when flag on + Signer healthy.
|
||||||
|
8. Docs: ENROLL.md success contract; README gates row.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Do not
|
||||||
|
|
||||||
|
- Do **not** use lab CA as the production Signer
|
||||||
|
- Do **not** grant mint on `GET /roles/catalog` alone
|
||||||
|
- Do **not** trust client CN/SAN without server policy
|
||||||
|
- Do **not** log CSR/cert PEM
|
||||||
|
- Do **not** default `STAFF_CSR_MINT` on before soak
|
||||||
|
- Do **not** return 201 without AuthZ once live path ships
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [PROD-CA-CUTOVER-RUNBOOK.md](./PROD-CA-CUTOVER-RUNBOOK.md)
|
||||||
|
- [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md)
|
||||||
|
- [PRODUCTION-PKI-REFUSE.md](./PRODUCTION-PKI-REFUSE.md)
|
||||||
|
- [CRL-OCSP-DESIGN.md](./CRL-OCSP-DESIGN.md)
|
||||||
|
- [CRL-OCSP-DEPLOY-CHECKLIST.md](./CRL-OCSP-DEPLOY-CHECKLIST.md)
|
||||||
|
- [ENROLL.md](../ENROLL.md)
|
||||||
|
- [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md)
|
||||||
175
docs/CRL-OCSP-DEPLOY-CHECKLIST.md
Normal file
175
docs/CRL-OCSP-DEPLOY-CHECKLIST.md
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
# CRL / OCSP production deploy checklist (READY package)
|
||||||
|
|
||||||
|
**Status: READY package** — operator checklist to deploy **production** revocation (CRL and/or OCSP) for staff device certificates.
|
||||||
|
**Not implemented in staff-iam.** No CRL/OCSP HTTP routes ship from this service. Lab `lab-test-ca.sh crl` emits an **empty lab-signed** CRL for local experiments only — **never** production.
|
||||||
|
|
||||||
|
Design background: [CRL-OCSP-DESIGN.md](./CRL-OCSP-DESIGN.md).
|
||||||
|
Cutover context: [PROD-CA-CUTOVER-RUNBOOK.md](./PROD-CA-CUTOVER-RUNBOOK.md).
|
||||||
|
AuthZ revoke path: [AUTHZ-ENROLL-DESIGN.md](./AUTHZ-ENROLL-DESIGN.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hard rules
|
||||||
|
|
||||||
|
1. **Primary control = short leaf lifetime**; CRL/OCSP are complementary.
|
||||||
|
2. **Proxy / edge** performs chain verify **and** revocation check — not the IAM Node process.
|
||||||
|
3. **staff-iam does not serve** `GET /crl` or OCSP — prefer AIA/CRLDP URLs on the issuing CA hostnames.
|
||||||
|
4. **Lab empty CRL** is not a revoke authority; lab root must not be in production trust stores.
|
||||||
|
5. **No fake revoke** — flipping `mtlsEnrolled: false` without CA revoke is policy only, not crypto revoke.
|
||||||
|
6. **LAB_CA_ENABLE** stays default off; production refuses lab mint.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Choose enforcement option
|
||||||
|
|
||||||
|
| Option | Where revoke is checked | staff-iam role |
|
||||||
|
|--------|-------------------------|----------------|
|
||||||
|
| **A. Proxy-side only (recommended first)** | nginx/Caddy/Envoy/WAF loads CRL or OCSP against client cert | Headers only; metadata later |
|
||||||
|
| **B. CA-adjacent PKI service** | Signer publishes CRL/OCSP; proxy still enforces TLS | IAM calls `Signer.revoke` |
|
||||||
|
| **C. In-process IAM OCSP/CRL** | Discouraged | Do not start here |
|
||||||
|
|
||||||
|
Default deploy path: **A**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pre-flight (CA)
|
||||||
|
|
||||||
|
- [ ] **R-01** Issuing intermediate (or leaf profile) embeds CRL Distribution Points and/or AIA OCSP URL
|
||||||
|
- [ ] **R-02** CRL/OCSP endpoints are **HTTPS** (or org-approved distribution), highly available, independent of staff-iam uptime
|
||||||
|
- [ ] **R-03** CRL sign key / OCSP responder key custody documented (HSM preferred); not the IAM app key
|
||||||
|
- [ ] **R-04** CRL validity / nextUpdate cadence agreed (e.g. ≤ 24h) and OCSP cache skew understood
|
||||||
|
- [ ] **R-05** Reason codes mapped: `keyCompromise`, `cessationOfOperation`, `affiliationChanged` (offboard)
|
||||||
|
- [ ] **R-06** Short-lived leaf policy documented if org skips CRL for leaves shorter than cache TTL
|
||||||
|
- [ ] **R-07** Lab root / `lab-pki` **absent** from production trust + CRL fetch config
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Proxy deploy (Option A)
|
||||||
|
|
||||||
|
- [ ] **R-10** Production trust store = corporate root/intermediate only
|
||||||
|
- [ ] **R-11** Client cert verify **on** for staff doors (fail closed on `FAILED` / absent when enforce intended)
|
||||||
|
- [ ] **R-12** Revocation mode enabled:
|
||||||
|
- CRL file/URL periodic fetch, **or**
|
||||||
|
- OCSP (stapling where applicable; client-cert OCSP leaf check per proxy docs)
|
||||||
|
- [ ] **R-13** Soft-fail vs hard-fail policy explicit:
|
||||||
|
- **Hard-fail** recommended for staff mTLS when OCSP/CRL unreachable (document availability needs)
|
||||||
|
- Soft-fail only with compensating short lifetime + monitoring
|
||||||
|
- [ ] **R-14** Forward verify/DN headers to staff-iam per [ENROLL.md](../ENROLL.md)
|
||||||
|
- [ ] **R-15** Header spoofing blocked (proxy strips inbound client-supplied verify headers)
|
||||||
|
- [ ] **R-16** Document cache TTL and how to **force flush** after emergency revoke
|
||||||
|
|
||||||
|
### nginx sketch (names only — adapt to platform)
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# illustrative — not a copy-paste production profile
|
||||||
|
ssl_client_certificate /etc/ssl/staff-ca-chain.pem;
|
||||||
|
ssl_verify_client on;
|
||||||
|
# ssl_crl /etc/ssl/staff-ca.crl; # if CRL file mode
|
||||||
|
# OCSP: per nginx/OpenSSL build capabilities — configure in org template
|
||||||
|
proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
|
||||||
|
proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
|
||||||
|
```
|
||||||
|
|
||||||
|
Exact header names must match what staff-iam already reads (see ENROLL.md). Do not invent production hostnames here.
|
||||||
|
|
||||||
|
### Caddy / Envoy
|
||||||
|
|
||||||
|
- Use org-standard mTLS + revoke modules.
|
||||||
|
- Same requirements: verify + revoke + forward identity headers + strip spoofed inbound.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CA-adjacent Signer (Option B add-on)
|
||||||
|
|
||||||
|
- [ ] **R-20** `Signer.revoke({ serial, reason })` API live and audited
|
||||||
|
- [ ] **R-21** Revoke publishes to CRL and/or OCSP within agreed SLA (e.g. ≤ 5 minutes)
|
||||||
|
- [ ] **R-22** staff-iam (when live mint ships) dual-writes: Signer.revoke + metadata `revokedAt` + `mtlsEnrolled: false`
|
||||||
|
- [ ] **R-23** Bulk offboard job: all serials for principal → revoke → clear IAM flags
|
||||||
|
- [ ] **R-24** Monitoring: revoke latency, CRL publish failures, OCSP error rate
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## staff-iam deploy attestation
|
||||||
|
|
||||||
|
- [ ] **R-30** No CRL/OCSP listen sockets on staff-iam service
|
||||||
|
- [ ] **R-31** `NODE_ENV=production`, `LAB_CA_ENABLE` unset
|
||||||
|
- [ ] **R-32** `GET /enroll/status` still reflects **proxy** verify — IAM does not claim crypto revoke itself
|
||||||
|
- [ ] **R-33** When metadata exists (future): status may show `serial` / `revokedAt` from store, still subordinate to proxy check
|
||||||
|
- [ ] **R-34** Admin UI never offers “Revoke” that only flips a boolean without CA action (if button ships, it must call Signer)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Offboard / emergency revoke runbook
|
||||||
|
|
||||||
|
1. **Identify** user + device + cert serial (from CA inventory or future IAM metadata).
|
||||||
|
2. **Revoke at CA** (Option A portal/CLI or Option B `Signer.revoke`) with reason.
|
||||||
|
3. **IAM:** set `mtlsEnrolled: false`; optionally invalidate sessions.
|
||||||
|
4. **Flush or wait** proxy CRL/OCSP cache (R-16).
|
||||||
|
5. **Verify:**
|
||||||
|
- Present revoked cert → proxy verify fail / revoke fail
|
||||||
|
- `GET /enroll/status` without good cert → not `verifyOk`
|
||||||
|
- `STAFF_MTLS_ENFORCE=1` login path denies enrolled user without valid cert
|
||||||
|
6. **Ticket** serial, time, reason, actor (no PEM bodies in tickets if avoidable).
|
||||||
|
|
||||||
|
Bulk tenant offboard: revoke all serials, then clear flags; do not rely on flag clear alone.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lab vs production (do not confuse)
|
||||||
|
|
||||||
|
| Item | Lab | Production |
|
||||||
|
|------|-----|------------|
|
||||||
|
| CRL helper | `./scripts/lab-test-ca.sh crl` → empty lab CRL | Real CA CRL with serials |
|
||||||
|
| Trust | dev proxy + lab-root only on lab machines | Corporate chain only |
|
||||||
|
| OCSP | none in staff-iam | CA/responder hosts |
|
||||||
|
| Mint | optional `LAB_CA_ENABLE=1` | external PKI or Signer; lab refused |
|
||||||
|
| Empty CRL | smoke config only | **Insufficient** as revoke proof |
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# LAB ONLY — never production trust
|
||||||
|
./scripts/lab-test-ca.sh init --dir ./lab-pki
|
||||||
|
./scripts/lab-test-ca.sh crl --dir ./lab-pki --out ./lab-pki/lab-empty.crl.pem
|
||||||
|
# point a *local* dev proxy at lab-root + empty CRL for config syntax checks only
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acceptance tests (ops)
|
||||||
|
|
||||||
|
- [ ] **T-01** Valid unrevoked client cert → proxy OK → IAM status shows proxy present
|
||||||
|
- [ ] **T-02** Unknown/untrusted cert → proxy fail → no spoofed verify header reaches IAM
|
||||||
|
- [ ] **T-03** Revoked serial after cache TTL → proxy fail
|
||||||
|
- [ ] **T-04** Emergency flush path works within agreed RTO
|
||||||
|
- [ ] **T-05** staff-iam production still returns **501** on `POST /enroll/csr` until live Signer cutover
|
||||||
|
- [ ] **T-06** `LAB_CA_ENABLE=1` on production binary still does not mint (server refuse)
|
||||||
|
- [ ] **T-07** Monitoring alerts on CRL fetch fail / OCSP unhealthy
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Do not
|
||||||
|
|
||||||
|
- [ ] Do **not** serve CRL/OCSP from staff-iam origin by default
|
||||||
|
- [ ] Do **not** load lab empty CRL in production
|
||||||
|
- [ ] Do **not** soft-fail revoke checks without short-lived certs + explicit risk acceptance
|
||||||
|
- [ ] Do **not** treat IAM enrollment flag as cryptographic revocation
|
||||||
|
- [ ] Do **not** enable lab CA to “work around” missing production CRL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Still left (ops / product)
|
||||||
|
|
||||||
|
- Live CA CRL/OCSP hostnames and certs (out-of-band)
|
||||||
|
- Proxy config in org IaC
|
||||||
|
- Signer.revoke when model B/C mint ships
|
||||||
|
- Optional IAM metadata + status UI revoke state after live mint
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [CRL-OCSP-DESIGN.md](./CRL-OCSP-DESIGN.md) — architecture options A/B/C
|
||||||
|
- [PROD-CA-CUTOVER-RUNBOOK.md](./PROD-CA-CUTOVER-RUNBOOK.md)
|
||||||
|
- [AUTHZ-ENROLL-DESIGN.md](./AUTHZ-ENROLL-DESIGN.md)
|
||||||
|
- [PRODUCTION-PKI-REFUSE.md](./PRODUCTION-PKI-REFUSE.md)
|
||||||
|
- [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md)
|
||||||
|
- [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md)
|
||||||
|
- [ENROLL.md](../ENROLL.md)
|
||||||
|
- [`scripts/lab-test-ca.sh`](../scripts/lab-test-ca.sh) `crl` — LAB ONLY
|
||||||
|
|
@ -154,6 +154,9 @@ Bulk tenant/staff offboard: revoke **all** serials bound to that principal at th
|
||||||
|
|
||||||
## Related docs
|
## Related docs
|
||||||
|
|
||||||
|
- [CRL-OCSP-DEPLOY-CHECKLIST.md](./CRL-OCSP-DEPLOY-CHECKLIST.md) — **READY** production deploy checklist
|
||||||
|
- [PROD-CA-CUTOVER-RUNBOOK.md](./PROD-CA-CUTOVER-RUNBOOK.md) — **READY** CA cutover (includes revoke)
|
||||||
|
- [AUTHZ-ENROLL-DESIGN.md](./AUTHZ-ENROLL-DESIGN.md) — **READY** AuthZ revoke permissions
|
||||||
- [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md) — live mint checklist; CRL/OCSP item
|
- [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md) — live mint checklist; CRL/OCSP item
|
||||||
- [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md) — lab CA init/sign; never production
|
- [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md) — lab CA init/sign; never production
|
||||||
- [ENROLL.md](../ENROLL.md) — mTLS states, proxy headers, CSR scaffold
|
- [ENROLL.md](../ENROLL.md) — mTLS states, proxy headers, CSR scaffold
|
||||||
|
|
|
||||||
|
|
@ -143,8 +143,23 @@ If product later needs in-app production mint/renew, that is a **new** design (r
|
||||||
4. **Never enable lab CA by default** — no boot-time sign, no production image with `LAB_CA_ENABLE=1`.
|
4. **Never enable lab CA by default** — no boot-time sign, no production image with `LAB_CA_ENABLE=1`.
|
||||||
5. TOTP remains orthogonal ([TOTP.md](../TOTP.md)); authenticator enroll is real crypto (RFC 6238) and is unrelated to device-cert mint.
|
5. TOTP remains orthogonal ([TOTP.md](../TOTP.md)); authenticator enroll is real crypto (RFC 6238) and is unrelated to device-cert mint.
|
||||||
|
|
||||||
|
## READY packages (external ops — packaged 2026-10-14)
|
||||||
|
|
||||||
|
In-repo software stays complete; live CA/AuthZ/CRL remain ops/product. These READY packages are the cutover kits:
|
||||||
|
|
||||||
|
| Package | Covers former [ext] |
|
||||||
|
|---------|---------------------|
|
||||||
|
| [PROD-CA-CUTOVER-RUNBOOK.md](./PROD-CA-CUTOVER-RUNBOOK.md) | Production CA mint/renew/revoke |
|
||||||
|
| [AUTHZ-ENROLL-DESIGN.md](./AUTHZ-ENROLL-DESIGN.md) | Drop default 501 for real AuthZ path |
|
||||||
|
| [CRL-OCSP-DEPLOY-CHECKLIST.md](./CRL-OCSP-DEPLOY-CHECKLIST.md) | Production CRL/OCSP |
|
||||||
|
|
||||||
|
**Invariant:** `LAB_CA_ENABLE` default off; `NODE_ENV=production` refuses lab; default `POST /enroll/csr` → **501** until Signer + AuthZ go criteria pass.
|
||||||
|
|
||||||
## Related docs
|
## Related docs
|
||||||
|
|
||||||
|
- [PROD-CA-CUTOVER-RUNBOOK.md](./PROD-CA-CUTOVER-RUNBOOK.md) — **READY** prod CA mint/renew/revoke cutover
|
||||||
|
- [AUTHZ-ENROLL-DESIGN.md](./AUTHZ-ENROLL-DESIGN.md) — **READY** AuthZ before dropping 501
|
||||||
|
- [CRL-OCSP-DEPLOY-CHECKLIST.md](./CRL-OCSP-DEPLOY-CHECKLIST.md) — **READY** CRL/OCSP deploy checklist
|
||||||
- [PRODUCTION-PKI-REFUSE.md](./PRODUCTION-PKI-REFUSE.md) — matrix: what stays **501** vs lab **201**; production refuse rules
|
- [PRODUCTION-PKI-REFUSE.md](./PRODUCTION-PKI-REFUSE.md) — matrix: what stays **501** vs lab **201**; production refuse rules
|
||||||
- [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md) — lab script (offline) + **future** gated test CA design; no HTTP fake mint
|
- [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md) — lab script (offline) + **future** gated test CA design; no HTTP fake mint
|
||||||
- [CRL-OCSP-DESIGN.md](./CRL-OCSP-DESIGN.md) — future real-CA revocation (proxy CRL/OCSP); lab empty CRL only
|
- [CRL-OCSP-DESIGN.md](./CRL-OCSP-DESIGN.md) — future real-CA revocation (proxy CRL/OCSP); lab empty CRL only
|
||||||
|
|
|
||||||
183
docs/PROD-CA-CUTOVER-RUNBOOK.md
Normal file
183
docs/PROD-CA-CUTOVER-RUNBOOK.md
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
# 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](./PKI-MINT-BACKLOG.md) | Shipped surface + live-mint product checklist |
|
||||||
|
| [AUTHZ-ENROLL-DESIGN.md](./AUTHZ-ENROLL-DESIGN.md) | AuthN/AuthZ gates before dropping default 501 |
|
||||||
|
| [CRL-OCSP-DEPLOY-CHECKLIST.md](./CRL-OCSP-DEPLOY-CHECKLIST.md) | Revocation distribution at proxy/CA |
|
||||||
|
| [PRODUCTION-PKI-REFUSE.md](./PRODUCTION-PKI-REFUSE.md) | What stays refused under production defaults |
|
||||||
|
| [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md) | Lab fixture only — never production trust |
|
||||||
|
| [ENROLL.md](../ENROLL.md) | mTLS states, proxy headers, status contract |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hard rules (do not violate on cutover)
|
||||||
|
|
||||||
|
1. **LAB_CA_ENABLE default off** — never set in production images, systemd units, or k8s manifests.
|
||||||
|
2. **`NODE_ENV=production` refuses lab** even if someone sets `LAB_CA_ENABLE=1` (server gate).
|
||||||
|
3. **Do not trust `lab-pki/` roots** on staff fleets or production reverse proxies.
|
||||||
|
4. **Do not drop default 501** until a real `Signer` + AuthZ (see AuthZ package) pass go/no-go.
|
||||||
|
5. **Private keys never** enter the IAM user store, audit log, or enroll response as PKCS#12 passwords / leaf keys.
|
||||||
|
6. **Proxy remains TLS SoT** for “cert present on this request”; IAM `mtlsEnrolled` is 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](../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](./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_ENABLE` unset**, no `lab-pki` volume mounts
|
||||||
|
- [ ] **P-08** Confirm `GET /enroll/status` → `csrPostStatus: 501`, `labCa.enabled: false` on production host
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
1. Operator or MDM enrolls device at corporate CA / EST / SCEP / ACME profile.
|
||||||
|
2. Install leaf + chain in OS/browser client-cert store (org procedure).
|
||||||
|
3. Browser hits staff door through mTLS-terminating proxy.
|
||||||
|
4. Confirm `GET /enroll/status`:
|
||||||
|
- `proxyHeadersSeen` / `mtlsPresent` / `proxy.verifyOk` as configured
|
||||||
|
- `state` progresses `not_enrolled` → `pending_proxy` → ready for admin flag
|
||||||
|
5. IAM admin: `PUT /users/:username` `{ "mtlsEnrolled": true }`
|
||||||
|
6. Only then consider `STAFF_MTLS_ENFORCE=1` on the door.
|
||||||
|
|
||||||
|
### Renew
|
||||||
|
|
||||||
|
1. Renew at CA **before** `notAfter` (org cadence; prefer automated short-lived renew).
|
||||||
|
2. Replace client cert in workstation store.
|
||||||
|
3. No staff-iam API required unless metadata binding ships later (backlog).
|
||||||
|
4. Spot-check `/enroll/status` still shows proxy verify after renew.
|
||||||
|
|
||||||
|
### Revoke / offboard
|
||||||
|
|
||||||
|
1. CA revoke serial (reason `keyCompromise` or `cessationOfOperation`).
|
||||||
|
2. IAM: `mtlsEnrolled: false` (+ kill sessions if policy requires).
|
||||||
|
3. Wait for proxy CRL/OCSP cache TTL or flush — [CRL-OCSP-DEPLOY-CHECKLIST.md](./CRL-OCSP-DEPLOY-CHECKLIST.md).
|
||||||
|
4. 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](./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 when `NODE_ENV=production`.
|
||||||
|
|
||||||
|
### Rollback
|
||||||
|
|
||||||
|
1. Feature-flag or deploy previous staff-iam build that restores default **501**.
|
||||||
|
2. Leave external proxy mTLS + `mtlsEnrolled` path working (path A/B still valid).
|
||||||
|
3. Revoke any leaves issued during failed cutover at the CA.
|
||||||
|
4. **Do not** “fix” with `LAB_CA_ENABLE=1` in 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_ENABLE` on 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: false` alone 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](./CRL-OCSP-DEPLOY-CHECKLIST.md)
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [PRODUCTION-PKI-REFUSE.md](./PRODUCTION-PKI-REFUSE.md)
|
||||||
|
- [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md)
|
||||||
|
- [AUTHZ-ENROLL-DESIGN.md](./AUTHZ-ENROLL-DESIGN.md)
|
||||||
|
- [CRL-OCSP-DEPLOY-CHECKLIST.md](./CRL-OCSP-DEPLOY-CHECKLIST.md)
|
||||||
|
- [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md)
|
||||||
|
- [ENROLL.md](../ENROLL.md)
|
||||||
|
- [`scripts/lab-test-ca.sh`](../scripts/lab-test-ca.sh) — LAB ONLY
|
||||||
|
|
@ -74,6 +74,9 @@ LAB_CA_ENABLE=1 npm start # never with NODE_ENV=production
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
|
- [PROD-CA-CUTOVER-RUNBOOK.md](./PROD-CA-CUTOVER-RUNBOOK.md) — **READY** prod CA cutover
|
||||||
|
- [AUTHZ-ENROLL-DESIGN.md](./AUTHZ-ENROLL-DESIGN.md) — **READY** AuthZ enroll design
|
||||||
|
- [CRL-OCSP-DEPLOY-CHECKLIST.md](./CRL-OCSP-DEPLOY-CHECKLIST.md) — **READY** CRL/OCSP deploy
|
||||||
- [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md) — shipped surface + live checklist
|
- [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md) — shipped surface + live checklist
|
||||||
- [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md) — lab script + gated HTTP
|
- [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md) — lab script + gated HTTP
|
||||||
- [CRL-OCSP-DESIGN.md](./CRL-OCSP-DESIGN.md) — future revocation
|
- [CRL-OCSP-DESIGN.md](./CRL-OCSP-DESIGN.md) — future revocation
|
||||||
|
|
|
||||||
|
|
@ -190,3 +190,14 @@ Exact algorithms should match whatever production CA model is selected in [PKI-M
|
||||||
- [ENROLL.md](../ENROLL.md) — mTLS states, proxy headers, CSR schema/scaffold
|
- [ENROLL.md](../ENROLL.md) — mTLS states, proxy headers, CSR schema/scaffold
|
||||||
- [README.md](../README.md) — service overview and security gates
|
- [README.md](../README.md) — service overview and security gates
|
||||||
- [TOTP.md](../TOTP.md) — orthogonal authenticator 2FA (real crypto; unrelated to device-cert mint)
|
- [TOTP.md](../TOTP.md) — orthogonal authenticator 2FA (real crypto; unrelated to device-cert mint)
|
||||||
|
|
||||||
|
## READY packages (production — not lab)
|
||||||
|
|
||||||
|
Lab stays default-off / production-refused. External ops packages:
|
||||||
|
|
||||||
|
- [PROD-CA-CUTOVER-RUNBOOK.md](./PROD-CA-CUTOVER-RUNBOOK.md) — production CA mint/renew/revoke
|
||||||
|
- [AUTHZ-ENROLL-DESIGN.md](./AUTHZ-ENROLL-DESIGN.md) — AuthZ before dropping 501
|
||||||
|
- [CRL-OCSP-DEPLOY-CHECKLIST.md](./CRL-OCSP-DEPLOY-CHECKLIST.md) — production CRL/OCSP
|
||||||
|
- [PRODUCTION-PKI-REFUSE.md](./PRODUCTION-PKI-REFUSE.md) — refuse matrix
|
||||||
|
- [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md) — live mint checklist
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue