SSH — replace standing SSH keys with short-lived certificates
What it is
Most SSH access works by copying a user's public key into a server's authorized_keys
file. That scales badly and ages dangerously: keys pile up, nobody remembers whose they
are, and removing access means hunting them down across every host. An
SSH certificate replaces that model. You trust one SSH
certificate authority, and it signs short-lived certificates that say
"this user may log in as alice until 5 p.m." No per-host key copying, automatic
expiry, central control.
This page covers trstctl's three SSH pieces: the SSH CA that signs host and user certificates (F43), the agent that safely configures hosts to trust that CA (F44), and attestation-gated short-lived user certificates that tie SSH access to verified identity (F45).
Why it exists
Standing SSH keys are one of the most common audit findings and breach vectors: orphaned keys grant access nobody is tracking, and offboarding rarely removes every key. SSH certificates fix the structural problem — access expires on its own, trust is centralized in the CA, and you can grant exactly the principals and time window each session needs. The hard parts are doing the host trust change without locking yourself out, and making sure only the right identity can get a certificate — which is what F44 and F45 address.
How it works
The SSH certificate authority (F43)
trstctl's SSH CA signs two kinds of OpenSSH certificate: host certificates (so clients can verify a server without trust-on-first-use prompts) and user certificates (so servers can authorize a login without a stored key). Each certificate carries principals (which usernames it's valid for), a validity window, and optional critical options and extensions.
All signing goes through one function in the crypto boundary,
crypto.SignSSHCertificate (AN-3), which takes an opaque signer handle — so the CA
key can live in an HSM and never appears in the clear (AN-4). An
issuance profile bounds the maximum TTL and which certificate types are allowed; serial
numbers increment safely under a lock; every issuance is audited (ssh.cert.issued,
AN-2) and runs on a bulkhead (AN-7). The CA also maintains a
key revocation list (KRL) — it can revoke by serial or key ID and produce a snapshot
to distribute to hosts, which is how you pull back a certificate before it expires.
Code: internal/protocols/ssh (CA, IssueUserCert, IssueHostCert, KRL,
AuthorityKey), internal/crypto/ssh.go (SignSSHCertificate).
SSH deployment & trust configuration (F44)
For a host to accept the CA's certificates, it must trust the CA's public key — written
into TrustedUserCAKeys and referenced from sshd_config. Editing sshd_config on a
live fleet is exactly where people lock themselves out, so trstctl's agent does it with
extreme care, and this is a hard project rule: trust is only ever added additively,
validated before it takes effect, and rolled back automatically on any failure.
The agent (1) backs up both files; (2) is idempotent — if the CA line is already present
it does nothing; (3) writes changes atomically (write-temp-then-rename); (4) runs a
three-step gauntlet — validate the new config (sshd -t), reload, then health-check that
sshd still accepts connections; and (5) if any step fails, restores both files from
backup and reloads the known-good config. Removing trust is never an implicit side
effect: RemoveCATrust refuses to run without an explicit confirmation flag. Every
action is audited (ssh.trust.added, ssh.trust.removed, ssh.trust.rolled_back).
Code: internal/agent/sshtrust (Applier, AddCATrust, RemoveCATrust).
Attestation-gated short-lived user certificates (F45)
The most powerful pattern: don't issue an SSH user certificate to anyone who asks —
issue it only to a caller who proves their identity first. This issuer runs an
attestation check (the same chain used for workload identity), and
only on success derives the certificate's principals from the verified attestation and
calls the SSH CA. It fails closed if attestation fails, defaults to a 15-minute TTL
(capped by the profile), and binds the attestation to the issued certificate in the audit
trail (ssh.attested_cert.issued, AN-2). The result: SSH access that is short-lived
and provably tied to, say, a specific CI job or a specific cloud instance — no standing
keys at all.
Code: internal/protocols/ssh/attested.go (AttestedUserCertIssuer).
Use it
Conceptually, the flow is: stand up the SSH CA, distribute its public key to hosts via the agent, then issue short-lived user certificates. The CA's public key goes into a host's trust config like this (what the agent writes for you, additively):
# /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/trusted_user_ca_keys
# /etc/ssh/trusted_user_ca_keys (the CA public key in authorized_keys form)
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... trstctl-ssh-ca
A user certificate is then issued with explicit principals and a short TTL (e.g. 15
minutes for alice), and the user connects normally — sshd validates the certificate
against the trusted CA without any stored key.
Pitfalls & limits
- Never hand-edit trust on a live host. Use the agent so the validate-reload-
health-check-rollback safety net applies; a bad manual
sshd_configedit can lock you out. trstctl will not remove existing trust without an explicit confirmation. - Serving status: the SSH CA is served by the running control plane
(
EXC-WIRE-02,protocols.ssh.enabled, default off): cert issuance at/ssh/...and the OpenSSH binary KRL at/ssh/krl(sshd'sRevokedKeysconsumes it). The CA key lives in the signer under its own SSH-cert-constrained handle (AN-4), and issuance is tenant-scoped and audited (AN-1/AN-2). The trust agent and the attested issuer are library-complete and tested but not yet wired into the agent binary — see Current limitations. - Short TTLs require renewal. That's the security benefit, but plan the renewal path for long-running sessions.
- KRL distribution is push-based. Revoking a certificate means distributing the updated KRL to hosts; budget for that propagation.
Reference
- CA operations:
IssueUserCert,IssueHostCert,AuthorityKey(forTrustedUserCAKeys/@cert-authority),KRL.RevokeSerial,KRL.Distribute. - Agent config:
SSHDConfigPath,TrustedUserCAKeysPath,RequireConfirmationToRemoveTrust(default true). - Attested issuance:
AttestedUserCertIssuer.Issue(method + payload → attested cert). - Events:
ssh.cert.issued,ssh.attested_cert.issued,ssh.trust.added,ssh.trust.removed,ssh.trust.rolled_back. - Standard: OpenSSH certificate format (
PROTOCOL.certkeys). - Design deep-dive: SSH trust-rewrite design.
See also
Workload identity (the attestation chain F45 reuses) · Issuance & certificate authorities · SSH trust-rewrite design · Discovery & inventory (finding existing SSH keys) · glossary: SSH certificate, attestation, HSM/KMS
Covers: F43, F44, F45