In enterprise environments, SSH access tends to get pinned between two extremes: either too tight (you can’t do your job) or too loose (long-lived keys, shared bastion accounts). OpenSSH’s built-in certificate support is a practical lever for fixing that balance: the user key becomes “identity,” and the access itself is signed by a CA in a time-bound and auditable way.
Why a CA model?
The two problems I see most often:
authorized_keysfiles on servers swell over time and nobody dares clean them up.- During an incident, “whose key is where?” turns into chaos.
The CA model flips the approach:
- The server knows which CA it trusts (a single file)
- The user keeps their own key, but takes a time-bound certificate for access
- Audit: who connected, when, with which “principal,” is observable
Architecture: 3 components
- CA key: the signing authority (must be very well protected)
- SSH servers: trust the CA and apply principal-based rules
- Signing service/flow: approval + duration + log + certificate issuance
1) Generate the CA key
On the signing host:
ssh-keygen -t ed25519 -f /etc/ssh/ca_user_ed25519 -C "ssh-user-ca"
install -m 0644 /etc/ssh/ca_user_ed25519.pub /etc/ssh/ca_user_ed25519.pub
The private key (/etc/ssh/ca_user_ed25519) should only ever be touched by the signing flow.
2) Configure CA trust on the SSH server
Place the CA public key on the target servers:
install -d -m 0755 /etc/ssh/trust
install -m 0644 ca_user_ed25519.pub /etc/ssh/trust/ca_user_ed25519.pub
Inside /etc/ssh/sshd_config:
TrustedUserCAKeys /etc/ssh/trust/ca_user_ed25519.pub
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
PubkeyAuthentication yes
Then:
sshd -t && systemctl reload sshd
3) Principal model: manage access by name
The model I use in practice:
- The Linux username on the server:
ops - Certificate principals:
prod-readonly,prod-admin,db-maint
For the ops user on the server:
install -d -m 0755 /etc/ssh/auth_principals
printf "prod-readonly\n" > /etc/ssh/auth_principals/ops
chmod 0644 /etc/ssh/auth_principals/ops
That file answers: “with which principals can someone log in as the ops user?“
4) Sign the user certificate (short-lived)
Using the user’s public key, a 60-minute certificate:
ssh-keygen -s /etc/ssh/ca_user_ed25519 \
-I "ticket-INC-14372" \
-n "prod-readonly" \
-V "+60m" \
-z 1001 \
user_ed25519.pub
The output ends up as user_ed25519-cert.pub. The user then connects:
ssh -i user_ed25519 ops@server01
OpenSSH automatically picks up the matching -cert.pub file next to the key passed via -i.
5) Certificate revocation (KRL) and “break glass”
Because certificate lifetimes are short, in many cases revocation isn’t even required. But for the “key got stolen” scenario, a KRL is a practical safety belt:
ssh-keygen -k -f /etc/ssh/revoked.krl user_ed25519.pub
Then in sshd_config:
RevokedKeys /etc/ssh/revoked.krl
For break glass, define a separate principal (for example prod-breakglass) and a separate approval rule.
Automation: how do I wire the signing flow?
Issuing certificates should not be a command a human types by hand. The model that holds up operationally:
- IAM/SSO → identity verification
- Ticket/incident → reason
- Approval → duration (short by default)
- Signing → log + audit trail
- On expiry → automatic shutdown
Once that flow is in place, SSH access stops being a “permanent privilege” and turns into an event-based operation.