Skip to content
Revoking SSH certificates: the KRL

Revoking SSH certificates: the KRL

Revoking the identity provider's SSH certificates with a signed, dated OpenSSH KRL that is checked at login and at every SFTP operation.

A certificate the identity provider’s SSH CA signed — see SFTP for people the provider vouches for — is checked at login. Disabling somebody in go-authn/bridge revokes their tokens and deletes their application passwords, which a directory reload applies here; but without a list, a certificate already issued would still open SFTP until it expires, and an SFTP session already open would stay open.

Since v0.12.0 a list closes that:

oidc {
  issuer          = "https://login.example.org"
  audience        = "fileshare"
  ssh_ca_file     = "/etc/fileshare/bridge-ca.pub"
  ssh_krl_url        = "https://bridge.example.org/ssh/krl"   # or ssh_krl_file
  ssh_krl_state_file = "/var/lib/fileshare/ssh.krl.state"    # the order, across a restart
  ssh_krl_max_age    = "1h"                                  # default; ssh_krl_refresh = "1m"
}

go-authn/bridge publishes the SSH certificates it has revoked — a person disabled, an IdP disabled — as an OpenSSH KRL (PROTOCOL.krl, the list sshd’s RevokedKeys reads), and fileshare keeps a copy of it, read with go-authn/krl.

Refused at login, and in the middle of a session

A revoked certificate is refused at login, and a session it already opened stops being served. SSH checks a certificate once, at the handshake; so every operation of a federated SFTP session asks the KRL again, open files included. The first operation after this server’s copy of the list changes is refused.

Fields

fielddefault
ssh_krl_urlwhere the list is fetched, HTTPS only
ssh_krl_fileor a file, by absolute path — one or the other, never both
ssh_krl_ca_filethe system’spins the authorities the list’s HTTPS server is checked against
ssh_krl_refresh1mhow often it is fetched, at least a second
ssh_krl_max_age1hhow old the last good copy may be before the certificates it governs are refused
ssh_krl_state_filewhere the last list that verified is kept, so a restart remembers the order

A max_age shorter than refresh is refused — every copy would be stale before the next fetch. A KRL with no ssh_ca_file is refused too: it revokes the provider’s certificates, and without that CA there are none.

It fails closed

While the KRL cannot be fetched, the provider’s certificates are refused

While the list cannot be fetched, or its last good copy is older than ssh_krl_max_age, every certificate the provider’s CA signed is refused: a revocation check that lets everything through when the list is unreachable is the one an attacker who can block the list defeats. The list must then be served as reliably as the logins it governs. fileshare_revocation_list_age_seconds{list="ssh_krl"} is the metric to alert on before max_age is reached; see health and metrics.

Signed and dated, from ssh_krl_url

Since v0.15.0, a KRL from ssh_krl_url must be signed and say when it expires, as go-authn/revocation describes and go-authn/bridge serves from v0.10.0:

  • The signature: <url>.sig is a detached SSHSIG signature by the provider’s SSH CA (ssh_ca_file, which ssh_krl_url therefore requires), in the namespace krl@go-authn.github.io. It is fetched with If-Match on the list’s ETag, and fetched again at once if the list was re-issued in between.
  • The expiry: the expires@go-authn.github.io extension. Past it, the list is not current, however recently a 304 confirmed it.

HTTPS (ssh_krl_ca_file pins its authorities) authenticates the server that answered, not the list: a mirror, a cache or a compromised web server could serve an empty one, and nothing in a KRL alone says it is stale. A list that does not verify is refused, and the last good copy is kept. A list placed by hand with ssh_krl_file is trusted as the filesystem that holds it, and needs neither signature nor expiry; one it carries is honoured.

Upgrading to v0.15.0

An ssh_krl_url serving an unsigned KRL, or one with no expiry, is no longer taken: the provider’s certificates are then refused, as for any list that cannot be had. Run go-authn/bridge v0.10.0 or later first.

Never backwards, across a restart too

Since v0.16.0, a list is refused if it is older than the one held: a lower version (the KRL’s version, a CRL’s number), or the same version issued earlier. With ssh_krl_state_file (crl_state_file for NFS), the last list that verified is kept on disk, verified again at start, and orders the next one. On its own it does not count as current until a list is fetched. Without it, a restart forgets the order, and fileshare says so at start: an older list, still signed and unexpired, would be taken.

These files, like ssh_ca_file, the CA files, the list files and (since v0.17.0) authorized_keys_file, dsn_file, bind_password_file and the ssf ca_file, may not lie inside a share: whoever writes there would decide who gets in.

A plain sshd next to fileshare

The same verification is available without fileshare: go-authn/revocation’s authn-revokd fetches the lists, keeps only those that verify, keeps their order on disk, writes RevokedKeys for sshd, and, when a list lapses, writes one that revokes the CA itself, so sshd fails closed too.

What it does not cover

OpenPubkey (opkssh) certificates are not in any KRL — nothing issued them but the person’s own key. They are bounded by opkssh_max_age, and taken back by shared signals. So is an access token over WebDAV.

The same mechanism — fetched, kept, failing closed — carries the X.509 CRL of NFS identities from certificates.

Created on by tannevaled

Modified on by tannevaled