Vault PKI — Internal CA
STATUS: BUILT (first slice), 2026-10-25. The PKI mount, root CA, and one issuing role are live in the mini’s Vault, and CRC’s cert-manager issues real certificates through a Vault ClusterIssuer. The first consumer is the service-health test suite, whose deliberately short-lived certificate is re-issued continuously. Scope is deliberately narrow: one role, one internal domain (
health.lab). The per-plane intermediates from the original design are not built yet.
What exists
- Mount + root. PKI secrets engine at
pki/in Vault (vault.rhels.com, on the mini). Root: RHELS Lab Root CA (RSA 2048, organization “RHELS Lab”), generated in Vault and never exported — no CA private key exists anywhere else. - Issuing role.
lab-health-issuer: allowed domainhealth.labwith subdomains, EC leaf keys required, default TTL 1h, max TTL 30 days. - Policy.
cert-manager-vaultgrants only issue/sign on that role plus read of the CA chain — nothing else in Vault.
How it’s set up (behind the scenes)
The consumer path is cert-manager on CRC (Argo app
crc-cert-manager, cert-manager chart 1.21.2 — installed for this;
the cluster previously had no cert-manager) with a ClusterIssuer
named vault-lab (in rhels/local-k8s-gitops,
clusters/crc/security/cert-manager/):
server: https://vault.rhels.compath: pki/sign/lab-health-issuer— the FULL sign path. cert-manager POSTs the CSR to<server>/v1/<path>; a barepkiposts to the mount root and Vault answers 403.- Auth is Vault’s Kubernetes auth method, role
crc-cert-manager(bound to thecert-managerservice account in thecert-managernamespace, TTL 10 minutes). cert-manager presents a TokenRequest-minted token for that SA.
Two non-obvious halves make that login work at all; both were found live on 2026-10-25 and both are required:
- The auth mount carries a reviewer JWT. Without one, Vault
validates each presented token by TokenReview using the token
itself, which only passes tokens carrying the API-server audience.
The reviewer credential is service account
vault-reviewer(namespacecert-manager, bound tosystem:auth-delegator); its legacy token lives in the server-populated Secretvault-reviewer-token, andvault-access/scripts/reconcile.pywrites it into the mount config on every reconcile (actionset-reviewerre-asserts it alone; recover.sh runs that after each rebuild’s convergence). - The issuer must request the
vaultaudience. The Vault role pins audiencevault; the default TokenRequest audiences do not include it, so the ClusterIssuer setsserviceAccountRef.audiences: [vault]. Without it the login fails 403 “invalid audience (aud) claim” even with the reviewer in place.
Unseal step. Vault signs nothing while sealed, so every Vault restart needs the unseal before PKI (and secret delivery) works: the mini’s home holds the unseal key file (mode 600); the unseal runs in-process on the mini — the key is read by the unseal call and never printed, passed as an argument, copied, or logged. recover.sh’s preflight performs it (the older Keychain bootstrap is the fallback). Proven in the 2026-10-25 drill: Vault restart -> sealed -> unseal -> sealed=false -> VaultStaticSecrets resync green.
How users use it
Declare a cert-manager Certificate against the ClusterIssuer — no Vault commands in the issuance path:
issuerRef: {name: vault-lab, kind: ClusterIssuer}privateKey: {algorithm: ECDSA}(the role refuses RSA leaves: “role requires keys of type ec”)dnsNamesunderhealth.lab- Set
durationto what you actually want; Vault honors the requested TTL up to the role max, with ~30s of clock skew — the suite’s checker asserts |actual validity - requested| <= 600s.
The worked example is the suite’s own Certificate
(suite.health.lab, requested duration 2h) in the
lab-service-health chart: first issued 2026-10-25 with measured
validity 2h 0m 30s against the 2h request.
What does not change
- No front-door name moves off Let’s Encrypt; this CA serves lab-internal names only.
- Vault’s own listener certificate remains the Let’s Encrypt one.
- The seal model is untouched: issuance is a provisioning-time act, and leaf lifetimes must outlast any realistic sealed window — or, like the suite’s 2h cert, be continuously renewed by cert-manager once Vault is unsealed.