# scripts/cert-manager

Scripts for the M14-backend PKI (mTLS opt-in per source + internal
gRPC mTLS). These run **before** K8s is involved — the root CA
stays offline, the intermediate is what gets imported into
cert-manager.

## Layout

| File | Purpose | When to run |
|---|---|---|
| `ca-init.sh` | Generate root + intermediate for a new env | Once per env (dev, staging, prod) |
| `ca-rotate-intermediate.sh` | Rotate the intermediate (keep root) | Annually, ~30d before expiry |
| `test-certs.sh` | Generate throwaway test certs for `internal/auth/mtls_test.go` | Whenever tests need refreshing |
| `README.md` | This file | — |
| `testdata/` | Test cert fixtures (gitignored, regenerated) | — |

## Typical flow

### 1. Bootstrap a new environment

```bash
# On an air-gapped machine or encrypted USB
export BA_CA_PASSPHRASE='...'   # use a password manager
scripts/cert-manager/ca-init.sh prod /secure/pki

# Move the root offline, keep the intermediate accessible
mv /secure/pki/prod/root-ca.key /offline-usb/
mv /secure/pki/prod/root-ca.crt /offline-usb/

# Import the intermediate into K8s (sealed-secrets or external-secrets)
# Apply the cert-manager ClusterIssuer manifest (deploy/cert-manager/...)
# Issue serving certs
```

### 2. Annual rotation (30 days before intermediate expires)

```bash
# On the air-gapped machine
export BA_CA_PASSPHRASE='...'
scripts/cert-manager/ca-rotate-intermediate.sh prod /offline-usb /tmp/new-pki

# Re-import the new intermediate into K8s
# Restart cert-manager controller
# Re-issue serving certs (delete+apply, or annotate)
# Verify with the smoke test
```

### 3. Test fixtures

```bash
scripts/cert-manager/test-certs.sh        # generate
scripts/cert-manager/test-certs.sh clean  # remove
```

## Security notes

- **Root key never touches the cluster.** Generate it offline, store
  it offline, only use it to sign the intermediate. If the root key
  is compromised, the entire PKI is compromised (see
  `docs/runbooks/mtls-incident.md` §"CA compromise").
- **Passphrase management.** `BA_CA_PASSPHRASE` is the dev/CI path.
  For prod, prefer interactive prompts or a hardware token. Never
  store the passphrase in the same place as the cert.
- **test-certs.sh is throwaway.** The `testdata/` dir is gitignored
  because it contains private keys. The real PKI is generated by
  `ca-init.sh`, never committed.

## Related docs

- `M14_SECURITY_PLAN.md` — full milestone plan
- `docs/runbooks/mtls-incident.md` — incident response
- `internal/auth/mtls.go` — the verifier (companion to the test certs)
- `internal/auth/mtls_test.go` — table-driven tests that use the test certs
