Static SSH keys are a recurring audit finding. They live in authorized_keys files scattered across servers, get copied between teammates, and rarely get cleaned up when someone leaves. There’s no expiration, no central record of who has access to what, and no way to revoke a key you didn’t know existed.
Vault’s SSH secrets engine acts as a certificate authority. It signs keys on demand, enforces TTLs, and produces an audit trail of every issuance request. When a certificate expires, access is gone automatically.
Vault installed, running, and authenticated. Linux VMs or cloud instances you control. Administrative access to Vault.
How It Works
Vault signs the key — A user presents their public key. Vault signs it with the CA private key and returns a certificate with an embedded TTL.
Server trusts the CA — Each server is configured with Vault’s CA public key via TrustedUserCAKeys. It accepts any certificate signed by that CA.
Certificate expires — When the TTL runs out, the certificate is rejected automatically. No revocation process needed — the clock does the work.
Step 1 — Enable the SSH Secrets Engine
vault secrets enable -path=ssh ssh
Step 2 — Generate the CA Key
vault write ssh/config/ca generate_signing_key=true
Vault generates an internal SSH CA key pair. The private key stays inside Vault and never leaves — only the public key is distributed to servers.
Step 3 — Distribute the CA Public Key
# Export the CA public key
vault read -field=public_key ssh/config/ca > vault-ca.pub
Copy vault-ca.pub to each server and add one line to /etc/ssh/sshd_config:
TrustedUserCAKeys /etc/ssh/vault-ca.pub
Then restart sshd. The server will now accept any certificate signed by Vault’s CA.
For new VMs, embed the CA key in your cloud-init user-data so every instance is configured at boot without manual steps. This means the control is enforced from the moment the instance exists rather than relying on someone to configure it afterward.
# user-data.yaml
write_files:
- path: /etc/ssh/vault-ca.pub
content: |
ssh-rsa AAAAB3NzaC1... (your vault CA public key)
permissions: '0644'
bootcmd:
- echo "TrustedUserCAKeys /etc/ssh/vault-ca.pub" >> /etc/ssh/sshd_config
runcmd:
- systemctl restart ssh
Step 4 — Create a Role
Roles define what the signed certificate is allowed to do — which usernames are valid, what the TTL is, and whether host or user certificates are issued. Keeping TTLs short and usernames explicit is how you enforce least privilege at the certificate layer.
vault write ssh/roles/dev-role \
key_type=ca \
allow_user_certificates=true \
allowed_users="ansible,darnell" \
default_user="ansible" \
ttl="1h" \
max_ttl="24h"
Step 5 — Sign a Key and Connect
# Generate a new key pair
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_demo
# Sign the public key — Vault returns the certificate
vault write -field=signed_key ssh/sign/dev-role \
public_key=@$HOME/.ssh/id_ed25519_demo.pub \
username=ansible \
> $HOME/.ssh/id_ed25519_demo-cert.pub
chmod 600 ~/.ssh/id_ed25519_demo
chmod 644 ~/.ssh/id_ed25519_demo-cert.pub
# Connect using both the key and the certificate
ssh -i ~/.ssh/id_ed25519_demo \
-i ~/.ssh/id_ed25519_demo-cert.pub \
ansible@your-server
Inspect the Certificate
# Full certificate details
ssh-keygen -L -f ~/.ssh/id_ed25519_demo-cert.pub
# Check validity window
ssh-keygen -L -f ~/.ssh/id_ed25519_demo-cert.pub | grep "Valid:"
Control Mapping
Replacing static SSH keys with Vault-issued certificates isn’t just a security improvement — it directly addresses access control requirements that show up consistently in NIST-based assessments and SOC 2 audits.
AC-2 (Account Management) requires that access is revoked when no longer needed. Static SSH keys fail this requirement because revocation depends on someone manually removing the key from every authorized_keys file on every server that user could access. TTL-bound certificates fix this at the architecture level — the certificate expires automatically, and no server-side cleanup is required.
AC-17 (Remote Access) requires that remote access sessions are authorized, monitored, and controlled. Vault’s SSH role configuration is the authorization mechanism — access to a role requires a Vault policy, and the role itself defines which usernames and TTLs are permitted. Every certificate request goes through that gate.
IA-5 (Authenticator Management) covers the lifecycle of authentication credentials. Static SSH keys are long-lived credentials with no built-in expiration and no rotation mechanism. Short-TTL certificates replace them with credentials that are time-limited by design, eliminating an entire class of stale credential findings.
AU-2 and AU-12 (Audit Events and Audit Record Generation) require that authentication and access events are logged. Vault’s audit log captures every certificate signing request — who requested it, which role was used, what username was embedded, and when it was issued. That’s a complete access trail that static authorized_keys files cannot provide.
CM-6 (Configuration Settings) requires establishing and maintaining secure configuration settings. Authorized_keys files scattered across servers are a configuration drift problem — keys accumulate, ownership becomes unclear, and the actual state of access diverges from what any documentation says. Centralizing issuance through Vault eliminates that entire vector.
Key Points
- The CA private key never leaves Vault — only the public key is distributed to servers
- Deploying the CA public key via cloud-init means every new instance is enrolled in the certificate model automatically, with no manual configuration step that could be skipped
- Set
ttlbased on your access model — 1h for interactive sessions, longer for automation accounts — and treat the TTL as a control parameter, not just a convenience setting - Roles control which usernames are embeddable in certificates — a certificate for
ansiblewon’t work asrootunlessrootis inallowed_users, which is how you enforce least privilege at the certificate layer - Pair certificate issuance with Vault’s LDAP or OIDC auth methods so every certificate request is tied to an authenticated identity — this is what gives you a complete, attributable access trail and turns the Vault audit log into genuine evidence for access reviews
