HashiCorp Vault — PKI Exercises
Practice PKI certificate management with these hands-on exercises.
Exercise 1 — Build a CA Hierarchy
Task: Set up a complete root CA → intermediate CA hierarchy.
- Enable PKI at
pki/(root) andpki_int/(intermediate) - Generate a root CA
- Generate an intermediate CSR
- Sign the intermediate with the root
- Import the signed intermediate
- Verify the chain with
openssl verify
Show Answer
# Enable and tune
vault secrets enable pki
vault secrets tune -max-lease-ttl=87600h pki
vault secrets enable -path=pki_int pki
vault secrets tune -max-lease-ttl=26280h pki_int
# Root CA
vault write pki/root/generate/internal \
common_name="Test Root CA" issuer_name="root" ttl=87600h
vault write pki/config/urls \
issuing_certificates="$VAULT_ADDR/v1/pki/ca" \
crl_distribution_points="$VAULT_ADDR/v1/pki/crl"
# Intermediate CSR
vault write -format=json pki_int/intermediate/generate/internal \
common_name="Test Intermediate CA" issuer_name="intermediate" \
| jq -r '.data.csr' > /tmp/int.csr
# Sign with root
vault write -format=json pki/root/sign-intermediate \
issuer_ref="root" csr=@/tmp/int.csr format=pem_bundle ttl=26280h \
| jq -r '.data.certificate' > /tmp/int.pem
# Import
vault write pki_int/intermediate/set-signed certificate=@/tmp/int.pem
# Verify
vault read -field=certificate pki/ca/pem > /tmp/root-ca.pem
vault read -field=certificate pki_int/ca/pem > /tmp/int-ca.pem
openssl verify -CAfile /tmp/root-ca.pem /tmp/int-ca.pem
# /tmp/int-ca.pem: OK ✅
Exercise 2 — Issue and Inspect Certificates
Task: Create a role and issue a certificate, then inspect it.
- Create a role
webfor*.example.comwith 24h max TTL - Issue a certificate for
app.example.com - Extract and inspect the certificate with
openssl - Verify the certificate is signed by the intermediate CA
Show Answer
# Create role
vault write pki_int/roles/web \
allowed_domains="example.com" \
allow_subdomains=true \
max_ttl=24h
# Issue certificate
vault write -format=json pki_int/issue/web \
common_name="app.example.com" ttl=24h > /tmp/cert.json
# Extract
jq -r '.data.certificate' /tmp/cert.json > /tmp/app.crt
jq -r '.data.private_key' /tmp/cert.json > /tmp/app.key
# Inspect
openssl x509 -in /tmp/app.crt -text -noout | head -30
# Subject: CN = app.example.com
# Issuer: CN = Test Intermediate CA
# Validity: 24 hours from now
# Verify against CA chain
openssl verify -CAfile /tmp/root-ca.pem -untrusted /tmp/int-ca.pem /tmp/app.crt
# /tmp/app.crt: OK ✅
Exercise 3 — Certificate Revocation
Task: Issue a certificate, revoke it, and verify it appears on the CRL.
- Issue a certificate and note the serial number
- Revoke the certificate
- Download the CRL and verify the serial is listed
Show Answer
# Issue
CERT_DATA=$(vault write -format=json pki_int/issue/web \
common_name="revoke-me.example.com" ttl=24h)
SERIAL=$(echo $CERT_DATA | jq -r '.data.serial_number')
echo "Serial: $SERIAL"
# Revoke
vault write pki_int/revoke serial_number="$SERIAL"
# Check CRL
curl -s "$VAULT_ADDR/v1/pki_int/crl/pem" > /tmp/crl.pem
openssl crl -in /tmp/crl.pem -text -noout | grep -A1 "Serial"
# Should show the revoked serial number
Exercise 4 — Short-Lived Certificates
Task: Create a role for 1-hour certificates suitable for microservices.
- Create an
internal-svcrole with 1h max TTL for*.svc.internal - Issue a certificate
- Verify the TTL is approximately 1 hour
- Discuss: why is this better than long-lived certs?
Show Answer
# Create short-lived role
vault write pki_int/roles/internal-svc \
allowed_domains="svc.internal" \
allow_subdomains=true \
max_ttl=1h \
key_type=ec \
key_bits=256 \
no_store=true
# Issue
vault write -format=json pki_int/issue/internal-svc \
common_name="api.svc.internal" ttl=1h > /tmp/short-cert.json
# Check TTL
jq -r '.data.certificate' /tmp/short-cert.json > /tmp/short.crt
openssl x509 -in /tmp/short.crt -text -noout | grep -A2 "Validity"
# Not Before: (now)
# Not After: (1 hour from now)
Why short-lived is better:
- No CRL/OCSP needed — cert expires before you’d detect compromise
- Compromised cert has minimal window of abuse
- Forces automation — no manual cert management
no_store=truereduces Vault storage overhead
Exercise 5 — PKI Policy
Task: Write a policy that allows a service to issue certificates but not manage the CA.
Show Answer
# pki-issue-policy.hcl
# Issue certificates from the "internal-svc" role only
path "pki_int/issue/internal-svc" {
capabilities = ["create", "update"]
}
# Read CA chain (needed to verify peers)
path "pki_int/ca/pem" {
capabilities = ["read"]
}
path "pki_int/ca_chain" {
capabilities = ["read"]
}
# Deny CA management
path "pki_int/root/*" {
capabilities = ["deny"]
}
path "pki_int/intermediate/*" {
capabilities = ["deny"]
}
path "pki_int/roles/*" {
capabilities = ["deny"]
}
vault policy write pki-issue pki-issue-policy.hcl
TOKEN=$(vault token create -policy=pki-issue -ttl=1h -field=token)
# Can issue
VAULT_TOKEN=$TOKEN vault write pki_int/issue/internal-svc \
common_name="test.svc.internal" ttl=1h
# Success ✅
# Cannot manage roles
VAULT_TOKEN=$TOKEN vault write pki_int/roles/new-role allowed_domains="hack.com"
# Error: permission denied ❌
What’s Next?
With PKI covered, let’s automate secret delivery with Vault Agent — a client-side daemon that handles authentication, caching, and templating.
Next Chapter: Vault Agent