trstctl /docs GitHub ↗

Issue and trust SSH access at scale

Goal

You will replace the pile of standing SSH keys in everyone's authorized_keys with short-lived SSH certificates from a single authority. The outcome is hosts that trust one SSH certificate authority, users who get certificates that expire on their own (say, valid until 5 p.m.), and a clean inventory of any leftover keys still granting access. This is for an operator who wants central, time-bounded, auditable SSH access instead of per-host key copying. It is honest about which pieces the running binary serves and which are agent/library work today.

Before you start

  • A running control plane and an API token from Getting started (trstctl token create).
  • The CLI/API pointed at your server via TRSTCTL_SERVER and TRSTCTL_TOKEN — see Getting started.
  • An installed agent on the hosts you want to manage, enrolled as in Getting started. For what the agent can see and change on a host, see SSH.

Steps

  1. Turn on the SSH certificate authority and bind it to your tenant. The CA's key lives in the separate signing service under a handle constrained to SSH-cert signing — it never enters the API process. See SSH.

    protocols:
      ssh:
        enabled: true
        tenant_id: "11111111-1111-1111-1111-111111111111"
    

    -> the SSH CA is served at /ssh/..., and its binary key-revocation list at /ssh/krl (the artifact a host's RevokedKeys consumes). The toggle is off by default and startup fails closed if you enable it without a tenant.

  2. Find the SSH access you already have, so you know what the certificates are replacing. Create an ssh discovery source and queue a run; trstctl records host keys and standing-access grants — and flags an authorized_keys grant whose owner is unknown as orphaned. Only fingerprints are stored, never private keys. See Discovery & inventory.

    cat > ssh-source.json <<'JSON'
    {"kind":"ssh","name":"fleet","config":{"targets":["10.0.0.10:22"]}}
    JSON
    trstctl-cli discovery sources create -f ssh-source.json
    echo '{"source_id":"<source-id>"}' | trstctl-cli discovery runs start -f -
    trstctl-cli discovery findings list --run_id <run-id>
    

    -> you get a list of standing-access keys to retire as certificates take over. The SSH discovery control surface (source/schedule/run/findings) is served; the host-key scan executes from the agent/library connector — see Discovery & inventory.

  3. Make your hosts trust the CA's public key. The CA's key goes into a host's TrustedUserCAKeys file, referenced from sshd_config. The agent does this additively and safely, and this trust-rewrite is a high-blast-radius change, so it is off by default and requires an explicit opt-in plus confirmation. See SSH.

    trstctl-agent --enroll-url https://localhost:8443 \
      --bootstrap-token-file ./trstctl-bootstrap-token \
      --server localhost:9443 \
      --name edge-agent-1 \
      --ca-bundle ./trstctl-ca.pem \
      --ssh-trust-add-ca \
      --ssh-trust-confirm \
      --ssh-trust-reload-cmd 'systemctl reload sshd' \
      --ssh-trust-health-cmd 'ssh -o BatchMode=yes localhost true'
    

    What the agent writes for you, additively:

    # /etc/ssh/sshd_config
    TrustedUserCAKeys /etc/ssh/trusted_user_ca_keys
    

    -> the agent backs up the files, validates the new config (sshd -t), reloads, runs your post-reload health command, and auto-rolls-back to the last-known-good on any failure — so a bad rewrite cannot lock you out. It never removes existing trust without an explicit confirmation. This applier is wired into the agent binary behind the opt-in; see Current limitations.

  4. Issue a short-lived user certificate tied to a verified identity, not handed to anyone who asks. The attested issuer runs an attestation check first and only then derives the certificate's principals from the verified result, defaulting to a short TTL. Every issuance is an immutable ssh.cert.issued event. See SSH.

    -> the user connects normally and sshd validates the certificate against the trusted CA with no stored key. Access expires on its own — no standing key left behind. The attestation-gated issuer is library-complete and tested; the served SSH CA mints user/host certs and publishes the binary KRL today — see SSH and Current limitations.

  5. Pull a certificate back before it expires. Revoking it puts its serial on the SSH CA's key-revocation list, served in OpenSSH binary format at /ssh/krl, which a host's sshd consumes via its RevokedKeys directive. See SSH.

    -> a revoked certificate is reported as revoked by stock ssh-keygen; budget for pushing the updated KRL to hosts, since distribution is push-based.

Where next

Journey: J8 Steps through: F43, F44, F45, F42

Rendered live from github.com/ctlplne/trstctl — found a mistake? edit this page.