PKI and short-lived certificates
An internal CA with 24-hour leaf certs.
In short. Vault PKI and short-lived certificates: run an internal CA that issues 24-hour leaf certs.
Think of an internal CA as the badge office in a secure building. It doesn't hand out permanent keys to the front door; it prints day passes that expire at midnight and mean nothing tomorrow. Vault's PKI secrets engine works the same way: instead of your team wiring long-lived certificates into config and praying they get rotated before expiry, Vault becomes an on-demand issuer that signs a fresh leaf certificate the moment a workload asks, with a lifetime measured in hours. The security win is the same as the dynamic database credentials in the previous lesson — a stolen 24-hour cert is a problem that solves itself by tomorrow morning. The operational win is that nobody is ever paged at 3am because a wildcard cert nobody remembered quietly expired.
Build a two-tier CA: offline root, online intermediate
Never let Vault sign leaf certificates directly from a root CA. The root is the thing every client trusts; you want it created once, its private key never leaving a controlled mount, and then parked. All day-to-day issuance runs through an intermediate CA that the root signs. If the intermediate is ever compromised, you revoke and reissue one intermediate instead of re-bootstrapping trust across your entire fleet. Mount two PKI engines, generate the root, then have the intermediate produce a CSR that the root signs and hands back. Tune each mount's max_lease_ttl first — it's a hard ceiling that silently caps everything issued beneath it.
# Root CA — long-lived, then leave it alonevault secrets enable -path=pki_root pkivault secrets tune -max-lease-ttl=87600h pki_root # 10y ceilingvault write -field=certificate pki_root/root/generate/internal \common_name="Example Corp Root CA" issuer_name="root-2026" ttl=87600h > root_ca.crt# Intermediate CA — the workhorse Vault signs leaves fromvault secrets enable -path=pki_int pkivault secrets tune -max-lease-ttl=43800h pki_int # 5y ceiling# 1. intermediate generates a key + CSR (private key stays in Vault; no issuer yet)vault write -format=json pki_int/intermediate/generate/internal \common_name="Example Corp Intermediate CA" \| jq -r '.data.csr' > int.csr# 2. root signs the CSR — issuer_ref picks which root issuer does the signingvault write -format=json pki_root/root/sign-intermediate \issuer_ref="root-2026" [email protected] format=pem_bundle ttl=43800h \| jq -r '.data.certificate' > int.crt# 3. hand the signed cert back — this is what creates the intermediate issuervault write pki_int/intermediate/set-signed [email protected]# 4. name the freshly-imported issuer so roles and URLs can reference itvault write pki_int/issuer/default issuer_name="int-2026"
Define a role and issue a 24-hour leaf
A role is the policy that turns "Vault can sign anything" into "Vault will sign a cert for api.svc.example.com, RSA-2048, valid for exactly 24 hours, and nothing else." Roles constrain the allowed domains, whether subdomains and bare domains are permitted, the key type and size, and — critically — the ttl and max_ttl. Set both to 24h so callers can't request a longer-lived cert by passing their own ttl. Also point config/urls at your Vault address so issued certs carry working AIA and CRL locations; clients that fetch the issuer chain or check revocation need those embedded, and they cannot be added after the fact.
# Publish issuer + CRL locations (embedded into every leaf)vault write pki_int/config/urls \issuing_certificates="https://vault.example.com/v1/pki_int/ca" \crl_distribution_points="https://vault.example.com/v1/pki_int/crl"# The role: caps domains, key type, and lifetimevault write pki_int/roles/web-24h \allowed_domains="svc.example.com" \allow_subdomains=true \key_type=rsa key_bits=2048 \ttl=24h max_ttl=24h# A workload requests a leaf — cert + key + issuing CA come back inlinevault write pki_int/issue/web-24h common_name="api.svc.example.com" ttl=24h# .data.certificate .data.private_key .data.issuing_ca .data.ca_chain
The last piece is renewal. Short-lived certs are only viable if something re-issues them automatically before they lapse. In practice you don't script this by hand — Vault Agent runs as a sidecar or systemd unit, authenticates with the workload's own identity (the Kubernetes or OIDC methods from earlier in this course), and templates a fresh cert to disk on a schedule, sending SIGHUP to the server when it rotates. That closes the loop: the app never sees a bearer token, never holds a cert older than a day, and never needs a human in the renewal path.