feat: lab-test-ca.sh openssl lab CA (not wired to enroll)
LAB ONLY self-signed root + sign CSR; POST /enroll/csr still 501.
This commit is contained in:
parent
566b29f4ac
commit
7a211356a7
4 changed files with 358 additions and 24 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1 +1,3 @@
|
|||
node_modules/
|
||||
# Offline lab CA material from scripts/lab-test-ca.sh (never commit keys)
|
||||
lab-pki/
|
||||
|
|
|
|||
|
|
@ -75,6 +75,24 @@ Shipped this pass (still **no mint**):
|
|||
|
||||
Still left for live PKI: full checklist under “Left for live PKI” below.
|
||||
|
||||
### Lab offline CA script (P0 Issue 4 advance — still **no HTTP mint**)
|
||||
|
||||
Shipped for **lab/dev disk fixtures only**:
|
||||
|
||||
- [x] [`scripts/lab-test-ca.sh`](../scripts/lab-test-ca.sh) — OpenSSL self-signed lab root (`init`) + sign CSR → leaf PEM/chain (`sign`); loud **NOT FOR PRODUCTION** warnings; default output `lab-pki/` (gitignored)
|
||||
- [x] **Not** wired into `POST /enroll/csr`, server boot, or any default env flag — HTTP path stays **501**
|
||||
- [x] Documented relationship to future live mint in this file + [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md)
|
||||
|
||||
**How the lab script relates to future live mint**
|
||||
|
||||
| Layer | Role of `lab-test-ca.sh` | Live mint (backlog below) |
|
||||
|-------|--------------------------|---------------------------|
|
||||
| Today | Manual offline OpenSSL CA for local experiments / future fixture prep | Not shipped; CSR route is 501 |
|
||||
| When live mint ships | May generate throwaway leaves for **dev proxy** trust tests and unit fixtures | Production `Signer` (HSM/EST/ACME/delegated) issues real certs via AuthZ’d `POST /enroll/csr` |
|
||||
| Never | Become the default server mint path, production trust anchor, or silent 200 on `/enroll/csr` | Must not call the lab script from production `NODE_ENV` |
|
||||
|
||||
Operators continue to issue workstation certs at external reverse-proxy / internal PKI. Developers who need a PEM on disk run the script themselves; they still must not expect staff-iam HTTP to return cert material.
|
||||
|
||||
### Operator path (current)
|
||||
|
||||
1. Issue workstation client certs in your **external** reverse-proxy / internal PKI (not this app). Do not call `POST /enroll/csr` expecting a cert — it returns 501.
|
||||
|
|
@ -112,21 +130,23 @@ Checklist for a real in-app mint (product + security design required before codi
|
|||
- [ ] `GET /enroll/status` (or successor) surfaces real cert metadata when present
|
||||
- [ ] Renew / re-key endpoint and expiry warnings; revoke on offboard
|
||||
- [ ] CRL/OCSP or proxy-side revocation story documented
|
||||
- [ ] Remove 501 scaffold; keep “no fake crypto” rule for tests (fixtures may use a test CA only — see [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md))
|
||||
- [ ] Remove 501 scaffold; keep “no fake crypto” rule for tests (fixtures may use offline [`scripts/lab-test-ca.sh`](../scripts/lab-test-ca.sh) or a future gated test CA — see [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md); **never** default-on in server)
|
||||
- [ ] Operator runbooks + ENROLL.md / README updated for the live path
|
||||
|
||||
If product later needs in-app mint/renew, that is a **new** design (real CA integration or delegated signer), not an extension of the current status strings or a softened 501. Until then, keep mint/renew copy pointing at the proxy/PKI, keep `/enroll/status` read-only with respect to crypto, keep `GET /enroll/csr/schema` fields-only, and keep `POST /enroll/csr` as 501-only.
|
||||
|
||||
## Hard rules
|
||||
|
||||
1. **No fake crypto** — do not add endpoints, fixtures, or UI that return made-up certs, keys, or signed CSRs. The CSR scaffold’s only success criteria is an honest **501**.
|
||||
1. **No fake crypto** — do not add endpoints, fixtures, or UI that return made-up certs, keys, or signed CSRs over HTTP. The CSR scaffold’s only success criteria is an honest **501**. Offline lab PEMs from `scripts/lab-test-ca.sh` stay on disk and must not be served as mint responses.
|
||||
2. **Proxy is source of truth** for “cert present on this request”; IAM flag is source of truth for “user is enrolled for enforce.”
|
||||
3. Do not turn on `STAFF_MTLS_ENFORCE` until the proxy actually verifies clients (see [ENROLL.md](../ENROLL.md) “Do not”).
|
||||
4. TOTP remains orthogonal ([TOTP.md](../TOTP.md)); authenticator enroll is real crypto (RFC 6238) and is unrelated to device-cert mint.
|
||||
4. **Never enable lab CA in the default server path** — no import of `lab-test-ca.sh`, no boot-time sign, no implicit trust of `lab-pki/`.
|
||||
5. TOTP remains orthogonal ([TOTP.md](../TOTP.md)); authenticator enroll is real crypto (RFC 6238) and is unrelated to device-cert mint.
|
||||
|
||||
## Related docs
|
||||
|
||||
- [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md) — **future only** design for a dev/CI test CA (not implemented; no fake mint)
|
||||
- [TEST-CA-DESIGN.md](./TEST-CA-DESIGN.md) — lab script (offline) + **future** gated test CA design; no HTTP fake mint
|
||||
- [`scripts/lab-test-ca.sh`](../scripts/lab-test-ca.sh) — OpenSSL lab CA init/sign (NOT production; not wired to `/enroll/csr`)
|
||||
- [ENROLL.md](../ENROLL.md) — mTLS states, `nextSteps`, `proxyHeadersSeen`, proxy header contract, admin workflow
|
||||
- [README.md](../README.md) — install, usage, staff security gates (mTLS row: proxy headers only, no device-cert mint)
|
||||
- [TOTP.md](../TOTP.md) — authenticator 2FA (orthogonal gate)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,32 @@
|
|||
# Test CA design (future only)
|
||||
# Test CA design (future only + lab offline script)
|
||||
|
||||
**Status: design only — not implemented.**
|
||||
`verae-staff-iam` does **not** run a CA, sign CSRs, or return cert material today. `POST /enroll/csr` remains **501**. This document describes how a **future** offline/dev **test CA** *could* support automated tests once live mint is approved. It is not a license to add mint endpoints, demo certs, or softened 501 responses now.
|
||||
**Status: design for in-process/CI test CA — not implemented in the server.**
|
||||
Offline lab helper **shipped:** [`scripts/lab-test-ca.sh`](../scripts/lab-test-ca.sh) (OpenSSL self-signed root + CSR→PEM sign on disk only).
|
||||
|
||||
`verae-staff-iam` does **not** run a CA, sign CSRs, or return cert material over HTTP today. `POST /enroll/csr` remains **501**. This document describes (1) the **lab script** (manual/offline) and (2) how a **future** gated test CA *could* support automated tests once live mint is approved. It is not a license to add mint endpoints, demo certs, or softened 501 responses now.
|
||||
|
||||
Live mint product backlog and hard “no fake crypto” rules: [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md). Current proxy-only enroll contract: [ENROLL.md](../ENROLL.md).
|
||||
|
||||
## Lab script (shipped — offline only)
|
||||
|
||||
| Item | Detail |
|
||||
|------|--------|
|
||||
| Path | [`scripts/lab-test-ca.sh`](../scripts/lab-test-ca.sh) |
|
||||
| What it does | `init` → self-signed lab root under `lab-pki/` (gitignored); `sign --csr` → short-lived leaf PEM + chain on disk |
|
||||
| What it does **not** | Call staff-iam HTTP; change `POST /enroll/csr`; set any server env; trust itself in production |
|
||||
| Warnings | Prints a loud **LAB ONLY / NOT FOR PRODUCTION** banner on every run |
|
||||
| Relation to live mint | Fixture toolchain only. When product ships a real `Signer` + live `POST /enroll/csr`, this script (or a successor) may feed **dev proxy** trust stores and unit fixtures — it must **never** become the default server mint path |
|
||||
|
||||
```sh
|
||||
# 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"
|
||||
./scripts/lab-test-ca.sh sign --dir ./lab-pki --csr dev.csr --out ./lab-pki/leaf.crt.pem
|
||||
# Still: curl -X POST …/enroll/csr → 501 (server untouched)
|
||||
```
|
||||
|
||||
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:
|
||||
|
|
@ -29,21 +51,37 @@ Until live mint ships, the only honest test of enroll CSR is: schema **200** fie
|
|||
|
||||
## Relationship to live PKI backlog
|
||||
|
||||
[PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md) lists what production mint needs (CA model, AuthZ, CSR policy, bind to user, renew, revoke). This design covers **only** the test-side companion:
|
||||
[PKI-MINT-BACKLOG.md](./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) Test CA (future, this doc)
|
||||
Product mint (future) Lab / test CA
|
||||
───────────────────── ──────────────────────────
|
||||
External/HSM/EST signer ←→ Local OpenSSL/step-ca fixture
|
||||
POST /enroll/csr succeeds Same handler, TEST_CA_* env only
|
||||
Proxy trusts corp roots Dev proxy trusts test root
|
||||
External/HSM/EST signer ←→ scripts/lab-test-ca.sh (offline NOW)
|
||||
POST /enroll/csr succeeds Future: same handler, TEST_CA_* env only
|
||||
(never default; never production NODE_ENV)
|
||||
Proxy trusts corp roots Dev proxy may trust lab-pki root (lab only)
|
||||
```
|
||||
|
||||
Checklist items that mention “fixtures may use a test CA only” refer here. Do not implement the test CA ahead of choosing the production CA model.
|
||||
| Stage | Lab script | HTTP `POST /enroll/csr` |
|
||||
|-------|------------|-------------------------|
|
||||
| **Today** | Offline init/sign on disk | **501** scaffold (unchanged) |
|
||||
| **Live mint ships** | Still offline fixture / CI helper | Real production `Signer` |
|
||||
| **Gated test signer (future)** | May share profile defaults with script | Only with `STAFF_TEST_CA=1` and non-production |
|
||||
|
||||
## Proposed layout (when implemented)
|
||||
Checklist items that mention “fixtures may use a test CA only” refer here. **Do not** wire `lab-test-ca.sh` into the default server path or soften 501 because the script exists.
|
||||
|
||||
Suggested paths under the repo (names indicative; nothing exists yet):
|
||||
## Layout
|
||||
|
||||
**Shipped now (offline script only):**
|
||||
|
||||
```
|
||||
platform/verae-staff-iam/
|
||||
scripts/
|
||||
lab-test-ca.sh # LAB ONLY: init root + sign CSR → 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/
|
||||
|
|
@ -52,13 +90,15 @@ platform/verae-staff-iam/
|
|||
root-ca.crt # test root (public)
|
||||
intermediate-ca.crt # optional
|
||||
scripts/
|
||||
test-ca-init.sh # generate root + intermediate into a local dir
|
||||
test-ca-sign-csr.sh # sign one PEM CSR → client cert + chain
|
||||
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 test CA stay on the developer machine or CI secret store (`TEST_CA_KEY_DIR`), never in the IAM JSON user store, never in Docker images tagged for production, never logged.
|
||||
**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
|
||||
|
||||
|
|
@ -106,8 +146,9 @@ Exact algorithms should match whatever production CA model is selected in [PKI-M
|
|||
|
||||
## Operator vs developer story
|
||||
|
||||
- **Operators (production):** unchanged — issue workstation certs in corporate PKI; proxy terminates mTLS; IAM flag + enforce. See [ENROLL.md](../ENROLL.md).
|
||||
- **Developers (future):** run `scripts/test-ca-init.sh`, point dev proxy at `test/pki/root-ca.crt`, enable `STAFF_TEST_CA` only on localhost, run integration suite.
|
||||
- **Operators (production):** unchanged — issue workstation certs in corporate PKI; proxy terminates mTLS; IAM flag + enforce. See [ENROLL.md](../ENROLL.md). Never run `lab-test-ca.sh` for real staff devices.
|
||||
- **Developers (lab now):** run `scripts/lab-test-ca.sh init|sign`, keep material under gitignored `lab-pki/`, optionally point a *local* dev proxy at `lab-root.crt.pem`. HTTP mint still 501.
|
||||
- **Developers (future mint):** same offline script or gated `STAFF_TEST_CA` signer; never enable lab CA in default server boot.
|
||||
- **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)
|
||||
|
|
@ -120,10 +161,11 @@ Exact algorithms should match whatever production CA model is selected in [PKI-M
|
|||
|
||||
## Hard rules
|
||||
|
||||
1. **This file is not an implementation.** No code path should claim test-CA support until the backlog mint work is intentionally scheduled.
|
||||
2. **No fake crypto in production API** — 501 stays until real sign; tests that need certs use offline scripts or a gated signer, not a lying success response in default config.
|
||||
3. **Test roots never trust-anchor production.**
|
||||
4. Cross-link changes: any future mint PR must update [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md), this doc’s status line, and [ENROLL.md](../ENROLL.md) together.
|
||||
1. **Server does not implement mint.** `lab-test-ca.sh` is offline only; no HTTP code path should claim test-CA or lab-CA support until live mint is intentionally scheduled and gated.
|
||||
2. **No fake crypto in production API** — 501 stays until real sign; tests that need certs use the offline lab script or a future gated signer, not a lying success response in default config.
|
||||
3. **Lab/test roots never trust-anchor production.**
|
||||
4. **Never enable lab CA in the default server path** — no boot-time hook, no implicit `LAB_PKI_DIR` read, no `STAFF_TEST_CA` default-on.
|
||||
5. Cross-link changes: any future mint PR must update [PKI-MINT-BACKLOG.md](./PKI-MINT-BACKLOG.md), this doc’s status line, and [ENROLL.md](../ENROLL.md) together.
|
||||
|
||||
## Related docs
|
||||
|
||||
|
|
|
|||
270
scripts/lab-test-ca.sh
Executable file
270
scripts/lab-test-ca.sh
Executable file
|
|
@ -0,0 +1,270 @@
|
|||
#!/usr/bin/env bash
|
||||
# lab-test-ca.sh — offline OpenSSL lab CA for local/dev experiments ONLY.
|
||||
#
|
||||
# Creates a self-signed lab root CA and signs a PKCS#10 CSR → leaf PEM.
|
||||
# Material stays on disk under a lab directory. This script is NEVER imported
|
||||
# by the Node server and is NOT wired into POST /enroll/csr (still 501).
|
||||
#
|
||||
# NOT FOR PRODUCTION. NOT A STAFF-IAM RUNTIME FEATURE.
|
||||
# Do not trust this root on real workstations, staging staff fleets, or prod.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/lab-test-ca.sh init [--dir DIR]
|
||||
# scripts/lab-test-ca.sh sign --csr PATH [--dir DIR] [--out PATH] [--days N] [--cn CN]
|
||||
# scripts/lab-test-ca.sh warn
|
||||
#
|
||||
# Defaults: DIR=./lab-pki (gitignored). Requires openssl on PATH.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DEFAULT_DIR="${ROOT_DIR}/lab-pki"
|
||||
DAYS_DEFAULT=7
|
||||
|
||||
warn_banner() {
|
||||
cat >&2 <<'EOF'
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
LAB ONLY — verae-staff-iam scripts/lab-test-ca.sh
|
||||
Self-signed throwaway CA. NOT for production, staging staff, or
|
||||
any reverse-proxy trust store that faces real operators.
|
||||
Does NOT call or enable POST /enroll/csr (remains 501 scaffold).
|
||||
Do NOT ship lab-pki/ keys in images or commit private keys.
|
||||
Live mint stays on the product backlog (docs/PKI-MINT-BACKLOG.md).
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
EOF
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage:
|
||||
$SCRIPT_NAME init [--dir DIR]
|
||||
Create lab root CA key + self-signed cert under DIR (default: lab-pki/).
|
||||
|
||||
$SCRIPT_NAME sign --csr PATH [--dir DIR] [--out PATH] [--days N] [--cn CN]
|
||||
Sign a PEM CSR with the lab CA → leaf certificate PEM.
|
||||
--out defaults to DIR/leaf-<timestamp>.crt.pem
|
||||
--days leaf notAfter (default ${DAYS_DEFAULT}, max 30 for lab)
|
||||
--cn optional subject CN override in the issued cert (lab only)
|
||||
|
||||
$SCRIPT_NAME warn
|
||||
Print the lab-only warning and exit 0.
|
||||
|
||||
Environment:
|
||||
LAB_PKI_DIR Override default DIR when --dir omitted.
|
||||
|
||||
Exit codes: 0 ok · 1 usage/input · 2 missing openssl/CA · 3 sign failure
|
||||
EOF
|
||||
}
|
||||
|
||||
need_openssl() {
|
||||
if ! command -v openssl >/dev/null 2>&1; then
|
||||
echo "error: openssl not found on PATH" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_dir() {
|
||||
local d="${1:-}"
|
||||
if [[ -z "$d" ]]; then
|
||||
d="${LAB_PKI_DIR:-$DEFAULT_DIR}"
|
||||
fi
|
||||
# Absolute path
|
||||
if [[ "$d" != /* ]]; then
|
||||
d="$(pwd)/$d"
|
||||
fi
|
||||
echo "$d"
|
||||
}
|
||||
|
||||
cmd_init() {
|
||||
local dir=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) dir="${2:-}"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "error: unknown init arg: $1" >&2; usage >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
need_openssl
|
||||
warn_banner
|
||||
dir="$(resolve_dir "$dir")"
|
||||
mkdir -p "$dir"
|
||||
chmod 700 "$dir" 2>/dev/null || true
|
||||
|
||||
local key="$dir/lab-root.key.pem"
|
||||
local crt="$dir/lab-root.crt.pem"
|
||||
local conf="$dir/lab-root.cnf"
|
||||
local serial="$dir/lab-root.srl"
|
||||
|
||||
if [[ -f "$key" || -f "$crt" ]]; then
|
||||
echo "error: lab CA already exists under $dir (refusing to overwrite)" >&2
|
||||
echo " remove lab-root.key.pem / lab-root.crt.pem to re-init" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat >"$conf" <<'EOF'
|
||||
[req]
|
||||
distinguished_name = dn
|
||||
x509_extensions = v3_ca
|
||||
prompt = no
|
||||
|
||||
[dn]
|
||||
CN = Verae staff-iam LAB root (NOT FOR PRODUCTION)
|
||||
O = Verae Lab Only
|
||||
OU = throwaway-test-ca
|
||||
|
||||
[v3_ca]
|
||||
basicConstraints = critical,CA:true,pathlen:0
|
||||
keyUsage = critical,keyCertSign,cRLSign
|
||||
subjectKeyIdentifier = hash
|
||||
EOF
|
||||
|
||||
openssl genrsa -out "$key" 4096 2>/dev/null
|
||||
chmod 600 "$key"
|
||||
openssl req -new -x509 -days 3650 -key "$key" -out "$crt" -config "$conf"
|
||||
# Fresh serial file for leaf issuance
|
||||
openssl rand -hex 8 | tr '[:lower:]' '[:upper:]' >"$serial"
|
||||
chmod 600 "$serial" 2>/dev/null || true
|
||||
|
||||
cat >"$dir/README.txt" <<EOF
|
||||
LAB PKI material — NOT FOR PRODUCTION
|
||||
Generated by scripts/lab-test-ca.sh on $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
Root cert: lab-root.crt.pem (public)
|
||||
Root key: lab-root.key.pem (PRIVATE — never commit, never production trust)
|
||||
staff-iam server does not read this directory. POST /enroll/csr stays 501.
|
||||
See docs/TEST-CA-DESIGN.md and docs/PKI-MINT-BACKLOG.md.
|
||||
EOF
|
||||
|
||||
echo "lab CA created:"
|
||||
echo " dir: $dir"
|
||||
echo " cert: $crt"
|
||||
echo " key: $key (keep private)"
|
||||
openssl x509 -in "$crt" -noout -subject -dates 2>/dev/null || true
|
||||
warn_banner
|
||||
}
|
||||
|
||||
cmd_sign() {
|
||||
local dir="" csr="" out="" days="$DAYS_DEFAULT" cn=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) dir="${2:-}"; shift 2 ;;
|
||||
--csr) csr="${2:-}"; shift 2 ;;
|
||||
--out) out="${2:-}"; shift 2 ;;
|
||||
--days) days="${2:-}"; shift 2 ;;
|
||||
--cn) cn="${2:-}"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "error: unknown sign arg: $1" >&2; usage >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$csr" ]]; then
|
||||
echo "error: --csr PATH is required" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$csr" ]]; then
|
||||
echo "error: CSR not found: $csr" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! [[ "$days" =~ ^[0-9]+$ ]] || [[ "$days" -lt 1 || "$days" -gt 30 ]]; then
|
||||
echo "error: --days must be 1..30 (lab short-lived leaves)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
need_openssl
|
||||
warn_banner
|
||||
dir="$(resolve_dir "$dir")"
|
||||
local key="$dir/lab-root.key.pem"
|
||||
local ca="$dir/lab-root.crt.pem"
|
||||
local serial="$dir/lab-root.srl"
|
||||
local ext="$dir/lab-leaf-ext.cnf"
|
||||
|
||||
if [[ ! -f "$key" || ! -f "$ca" ]]; then
|
||||
echo "error: lab CA missing under $dir — run: $SCRIPT_NAME init --dir $dir" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ ! -f "$serial" ]]; then
|
||||
openssl rand -hex 8 | tr '[:lower:]' '[:upper:]' >"$serial"
|
||||
fi
|
||||
|
||||
# Light shape check (headers only); openssl does the real parse on sign.
|
||||
if ! grep -q "BEGIN CERTIFICATE REQUEST" "$csr" || ! grep -q "END CERTIFICATE REQUEST" "$csr"; then
|
||||
echo "error: CSR must be PEM PKCS#10 (BEGIN/END CERTIFICATE REQUEST)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$out" ]]; then
|
||||
out="$dir/leaf-$(date -u +%Y%m%dT%H%M%SZ).crt.pem"
|
||||
fi
|
||||
|
||||
cat >"$ext" <<EOF
|
||||
basicConstraints = critical,CA:false
|
||||
keyUsage = critical,digitalSignature,keyEncipherment
|
||||
extendedKeyUsage = clientAuth
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid,issuer
|
||||
EOF
|
||||
# Optional CN override via -subj would replace whole subject; keep CSR subject
|
||||
# unless --cn set, then use openssl -subj for lab convenience only.
|
||||
local subj_args=()
|
||||
if [[ -n "$cn" ]]; then
|
||||
subj_args=(-subj "/CN=${cn}/O=Verae Lab Only/OU=lab-leaf")
|
||||
fi
|
||||
|
||||
if ! openssl x509 -req \
|
||||
-in "$csr" \
|
||||
-CA "$ca" \
|
||||
-CAkey "$key" \
|
||||
-CAserial "$serial" \
|
||||
-out "$out" \
|
||||
-days "$days" \
|
||||
-sha256 \
|
||||
-extfile "$ext" \
|
||||
"${subj_args[@]+"${subj_args[@]}"}" 2>"$dir/last-sign.err"
|
||||
then
|
||||
echo "error: openssl sign failed:" >&2
|
||||
cat "$dir/last-sign.err" >&2 || true
|
||||
exit 3
|
||||
fi
|
||||
rm -f "$dir/last-sign.err"
|
||||
|
||||
# Chain file (leaf + lab root) for convenience — still lab only.
|
||||
local chain="${out%.crt.pem}.chain.pem"
|
||||
if [[ "$chain" == "$out" ]]; then
|
||||
chain="${out}.chain.pem"
|
||||
fi
|
||||
cat "$out" "$ca" >"$chain"
|
||||
|
||||
echo "lab leaf signed (NOT FOR PRODUCTION):"
|
||||
echo " leaf: $out"
|
||||
echo " chain: $chain"
|
||||
openssl x509 -in "$out" -noout -subject -issuer -dates -serial 2>/dev/null || true
|
||||
echo
|
||||
echo "Next (lab/dev only): point a *dev* reverse proxy trust store at:"
|
||||
echo " $ca"
|
||||
echo "staff-iam still does not mint; POST /enroll/csr remains 501."
|
||||
echo "See docs/TEST-CA-DESIGN.md — this script is offline fixture only."
|
||||
warn_banner
|
||||
}
|
||||
|
||||
main() {
|
||||
local cmd="${1:-}"
|
||||
if [[ -z "$cmd" ]]; then
|
||||
warn_banner
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
shift || true
|
||||
case "$cmd" in
|
||||
init) cmd_init "$@" ;;
|
||||
sign) cmd_sign "$@" ;;
|
||||
warn) warn_banner; exit 0 ;;
|
||||
-h|--help|help) usage; exit 0 ;;
|
||||
*)
|
||||
echo "error: unknown command: $cmd" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Reference in a new issue