Building a PKI hierarchy using OpenBao

Public Key Infrastructure (PKI) is the backbone of secure communications. At the heart of any PKI are Certificate Authorities (CAs): long-lived root CAs and shorter-lived intermediate CAs that issue the day-to-day certificates we actually use.

In this post, we’ll walk through setting up a simple PKI hierarchy using OpenBao (a community fork of HashiCorp Vault). We’ll create a 50-year root CA, generate a 10-year intermediate CA signed by that root, configure certificate URLs for proper validation and issue short-lived leaf certificates for services. This is a great way to get a realistic PKI setup for testing or lab environments.

Why use a hierarchy?

Best practice is to never issue certificates directly from your root CA. The root should live as long as possible, ideally offline, and only sign intermediates. Intermediates can then be rotated every few years while keeping the root intact. Leaf certificates, in turn, should be short-lived (days or months).

This gives you:

Longevity: Root CA is stable, rarely touched

Flexibility: Intermediates can be renewed or revoked

Security: Compromised intermediates don’t affect the root

Step 1. Create the Root CA

We’ll start by enabling a PKI engine for the root and tuning its lifetime to 50 years:

bao secrets enable -path=pki_root pki
bao secrets tune -max-lease-ttl=438000h pki_root

Now generate a self-signed root certificate:

bao write pki_root/root/generate/internal \
    common_name="Area536 Root CA" \
    ttl=438000h

You’ll see a warning about missing AIA/CRL URLs. We’ll fix that shortly.

Step 2. Create the Intermediate CA

Enable a new PKI engine for the intermediate, tuned for a 10-year lifetime:

bao secrets enable -path=pki_int pki
bao secrets tune -max-lease-ttl=87600h pki_int

Generate a CSR for the intermediate:

bao write -format=json pki_int/intermediate/generate/internal \
    common_name="Area536 Intermediate CA" \
    | jq -r '.data.csr' > pki_intermediate.csr

Have the root sign the CSR:

bao write -format=json pki_root/root/sign-intermediate \
    csr=@pki_intermediate.csr \
    format=pem_bundle ttl=87600h \
    | jq -r '.data.certificate' > pki_intermediate_cert.pem

Finally, import the signed certificate back into the intermediate:

bao write pki_int/intermediate/set-signed certificate=@pki_intermediate_cert.pem

Now your intermediate CA is fully trusted by the root.

Step 3. Configure Certificate URLs

To make certificate validation work smoothly, configure the issuing and revocation endpoints:

bao write pki_int/config/urls \
    issuing_certificates="http://127.0.0.1:8200/v1/pki_int/ca" \
    crl_distribution_points="http://127.0.0.1:8200/v1/pki_int/crl"

(Do the same for pki_root if you want the root’s CRL/AIA published.)

Step 4. Define Roles

Roles define what certificates the intermediate can issue. For example, to issue certs for example.com:

bao write pki_int/roles/web-server \
    allowed_domains="example.com" \
    allow_subdomains=true \
    max_ttl="720h"

This role allows issuing certificates for example.com and any subdomain, with a max lifetime of 30 days.

Step 5. Issue a Leaf Certificate

Finally, request a certificate for a service:

bao write pki_int/issue/web-server common_name="app.example.com"

This returns a PEM bundle including the private key, signed certificate and the CA chain.

Putting It All Together

With this setup you now have:

This hierarchy mimics how production PKI is deployed, giving you realistic and secure testing in your OpenBao environment.