hikyo
Documentation

SSH certificates

Issue short-lived OpenSSH user certificates to people and workloads from an environment-scoped CA, and revoke them with a Key Revocation List.

Hikyo can run an OpenSSH user certificate authority per environment and issue short-lived certificates to the humans and machine principals you name. Hosts trust the CA once (TrustedUserCAKeys) and refuse revoked certificates through a Key Revocation List (RevokedKeys). There are no per-user authorized_keys files to maintain, and access ends on its own when a certificate expires.

Hikyo manages certificates only. It is not a remote shell, a session proxy, a bastion or a host inventory, and it does not issue host certificates.

Concepts

  • CA: an environment-scoped signing authority. Its private key is generated by Hikyo or imported, sealed under the project key, and never returned by any route, verb or audit record. A staging CA can never sign for production.
  • Profile: the signing policy. It fixes which certificate principals (remote user names) may be requested, the force-command and source-address critical options, the allowed extensions, the allowed user key algorithms, the default and maximum lifetime, and who may request.
  • Certificate: one issued certificate. Hikyo keeps its public record (serial, principals, validity, requester) but never a private key.

Key algorithms are a closed set: ed25519, ecdsa-p256 and RSA of at least 3072 bits (rsa-3072). RSA CAs sign with rsa-sha2-512 only.

Set up a CA and a profile

hikyo ssh-ca create prod-users --env production
# or import an existing, unencrypted key without putting it on argv:
hikyo ssh-ca create prod-users --env production --key-file ./ca_key

hikyo ssh-profile create deploy --env production --ca <ca-id> \
  --principal deploy --principal ops \
  --key-algorithm ed25519 --key-algorithm ecdsa-p256 \
  --default-ttl 1h --max-ttl 8h \
  --source-address 10.0.0.0/8 \
  --extension permit-pty --extension permit-port-forwarding \
  --requester <principal-id> --requester <service-account-principal-id>

Managing CAs and profiles needs manage-identities on the project. Listing CAs, profiles and certificates, and fetching the trust bundle and KRL, needs only read on the environment, so a host can use a workload credential.

Configure hosts

Point sshd at the trust bundle and the revocation list:

# /etc/ssh/sshd_config.d/hikyo.conf
TrustedUserCAKeys /etc/ssh/hikyo_user_ca.pub
RevokedKeys /etc/ssh/hikyo_revoked.krl

Refresh both on a timer with a workload credential that holds read on the environment. Write to a temporary file and rename, so sshd never reads a half-written file:

#!/bin/sh
set -eu
TOKEN=/etc/hikyo/host-reader.token
hikyo ssh-ca trusted-keys <ca-id> --env production --token-file "$TOKEN" > /etc/ssh/hikyo_user_ca.pub.new
mv /etc/ssh/hikyo_user_ca.pub.new /etc/ssh/hikyo_user_ca.pub
rm -f /etc/ssh/hikyo_revoked.krl.new
hikyo ssh-ca krl <ca-id> --env production --token-file "$TOKEN" --output-file /etc/ssh/hikyo_revoked.krl.new
mv /etc/ssh/hikyo_revoked.krl.new /etc/ssh/hikyo_revoked.krl

sshd rereads both files on every authentication; no reload is needed. If a refresh fails, keep the last good files: an old KRL still refuses everything it listed. The refresh interval is your revocation latency; a minute is typical.

Request a certificate

A requester must be on the profile’s requester list and hold read on the environment. A human requester also completes the mint reauthentication ceremony first (the same one dynamic-secret leases use).

Certify your own key:

hikyo ssh-cert issue --profile <profile-id> --env production \
  --principal deploy --ttl 2h \
  --public-key-file ~/.ssh/id_ed25519.pub --cert-file ~/.ssh/id_ed25519-cert.pub
ssh deploy@host

Or let Hikyo generate a key pair. The private key is shown exactly once, through the same print rules as other disclosures (a terminal, a fresh 0600 --output-file, or --dangerously-print), and is never stored:

hikyo ssh-cert issue --profile <profile-id> --env production \
  --principal deploy --output-file ~/.ssh/hikyo_deploy
# writes ~/.ssh/hikyo_deploy (0600) and ~/.ssh/hikyo_deploy-cert.pub
ssh -i ~/.ssh/hikyo_deploy deploy@host

A request may only narrow its profile: fewer principals, a narrower source-address, fewer extensions, a shorter lifetime. Anything wider is refused. There is no renewal; request a new certificate when you need one. Each certificate’s key ID is hikyo:<environment>:<certificate>:<principal>, so sshd logs name the Hikyo principal that asked for it.

Revocation, expiry and deletion

These are three different things, and the certificate status keeps them apart:

StatusMeaningHosts
activeIssued, unexpired, signed by a trusted key.Accept it.
revokedCryptographically revoked. in_krl says whether the KRL still has to carry it.Refuse it once they refresh the KRL.
expiredPast its valid_before. Leaves the KRL: there is nothing left to refuse.Refuse it on their own.
untrustedIts signing key was retired, or its CA deleted.Refuse it once they refresh the trust bundle.

Revocation happens when:

  • the requester or a manage-identities holder runs hikyo ssh-cert revoke;
  • a requester is removed from a profile (their live certificates through it are revoked in the same write);
  • a requester loses its grant or is deleted: every node runs a sweeper that re-checks each live certificate’s requester about every 30 seconds and revokes on a definite refusal;
  • a profile is deleted with --revoke-issued.

Deleting is not revoking. Deleting a profile without --revoke-issued stops new issuance and leaves issued certificates valid until they expire. Deleting a CA destroys its signing key, but hosts that still list its keys in TrustedUserCAKeys keep accepting its certificates until they expire: remove the keys from host trust. Disabling a profile stops issuance on every node immediately and revokes nothing. An environment that still has a live SSH CA cannot be deleted.

The KRL is bounded at 65,536 serials per CA. Past that, fetching it fails rather than publishing a truncated list; with short lifetimes this bound is not reachable in practice. hikyo_ssh_krl_entries reports the current size.

Rotate a CA

hikyo ssh-ca rotate <ca-id> --env production --overlap 8h
hikyo ssh-ca retire-key <ca-id> --key <old-key-id> --env production   # optional: end the overlap early

Rotation creates a new signing key at once and moves the old key to retiring: it can no longer sign (its sealed private key is destroyed in the same transaction) but stays in the trust bundle until the overlap ends, so certificates issued just before the rotation keep working. The overlap defaults to the time until the last live certificate signed by the old key expires and is capped at 30 days. Hosts pick up the new key on their next trust-bundle refresh; refresh them before the overlap ends.

Multi-node behavior

Issuance is one transaction: it re-reads the active key, the profile’s enabled flag and the requester list, signs, and records the certificate together. A certificate leaves the server only from a committed transaction, so a node that raced a rotation, a disable or a requester removal on another node cannot hand one out. Nodes hold no cached signing key.

Restore

A restore rolls the certificate records back to the backup’s instant. A certificate issued and revoked after the backup has no record, so it cannot be in the KRL. After a restore, rotate every SSH CA with --overlap 0 and refresh host trust; certificates from before the rotation then stop working.

Monitoring

/metrics exposes label-free gauges: hikyo_ssh_certificates_active, hikyo_ssh_krl_entries, and hikyo_ssh_gauges_known (0 when the scrape could not measure them; the two value gauges are then omitted rather than shown as zero). Every CA, profile, issuance and revocation change is an audit event (ssh.*); no private key ever appears in one.