← All posts
Series BeaconSSH Series — Part 5 of 6

Trust Distribution and Host Security in BeaconSSH

·
beaconsshsshtrustoperations

Most discussions about SSH access focus on the client side — keys, certificates, agents. But the security of BeaconSSH is ultimately enforced on the host, by OpenSSH, using a single file: TrustedUserCAKeys. This post covers how that file gets populated and why the mechanism is more nuanced than “download a public key.”

How OpenSSH Uses TrustedUserCAKeys

When a user connects with an SSH certificate, sshd reads TrustedUserCAKeys and checks whether the certificate’s signing CA matches any key in the file. If it matches and the certificate is valid (time, principals, constraints), access is granted.

No authorized_keys entry needed. No callback to any server. The certificate is self-contained proof of identity and authorization.

This means: adding a CA key trusts every valid certificate from that CA. Removing it revokes trust for all of them. The trust anchor is a file on disk, not a network service.

The Host CLI

The host CLI is a cron job. Not a daemon — a job that runs, does its work, and exits. Each run:

  1. Fetches the CA public key bundle from the certificate server over HTTPS
  2. Verifies the GPG signature on the bundle
  3. Checks that the bundle version is monotonically increasing
  4. Writes the verified keys atomically to TrustedUserCAKeys

Why GPG Signatures?

TLS protects the transport, but not the payload. If the certificate server is compromised and an attacker controls the HTTPS endpoint, they could serve a bundle containing their own CA public key — instantly gaining the ability to forge certificates that hosts will accept.

GPG signatures using a key distributed out-of-band during initial host provisioning ensure that only bundles signed by the CA operator are accepted. A compromised server alone isn’t enough.

Why Monotonic Versioning?

Without version checking, an attacker who captured a previously valid bundle could replay it. This matters during CA rotation — replaying an old bundle could re-trust a key that was intentionally rotated out.

The host CLI stores the current version in a state file. Each new bundle must have a strictly greater version number. Rollback is rejected.

Why Atomic Writes?

sshd reads TrustedUserCAKeys on every connection. A partial write — process killed or disk full mid-write — could produce a corrupted file that rejects all certificates or, worse, accepts none (locking out legitimate users).

The host CLI writes to a temporary file, then uses rename() to atomically replace the target. sshd always sees a complete file.

Failure Modes

Certificate server unreachable: The host CLI fails to fetch. Current TrustedUserCAKeys remains intact. Existing certificates continue to work. Failing closed — no trust changes during outages.

Invalid signature: The host CLI rejects the bundle. Current config untouched. Could indicate server compromise, network manipulation, or misconfiguration.

Clock skew: SSH certificate validation depends on correct time. A host with significant drift might accept expired certificates or reject valid ones. BeaconSSH doesn’t solve this — NTP on all hosts is an operational prerequisite.

Split-brain during CA rotation: Host A has the new bundle (trusts old + new CA). Host B hasn’t updated yet (trusts only old). A certificate signed by the new CA works on A but fails on B. This is expected — the rotation window must account for the longest host update interval.

Why Not a Daemon?

This approach seems reasonable, but fails because:

  • A daemon is a persistent process with a larger attack surface
  • It maintains state, opens ports, and needs reconnection handling
  • A cron job runs, does its work, and exits — minimal footprint
  • If the job fails, the system operates with the last known good config

The tradeoff: trust updates aren’t instant. There’s a delay between the certificate server publishing a new bundle and all hosts installing it. For CA rotation, this delay is included in the transition window. For emergency response, you may need to update TrustedUserCAKeys directly via configuration management.

Fine-Grained Control with AuthorizedPrincipalsFile

TrustedUserCAKeys is coarse-grained — “trust this CA.” For fine-grained control, OpenSSH supports AuthorizedPrincipalsFile, which restricts which certificate principals can log in as which local user:

# /etc/ssh/auth_principals/deploy
github:user:alice
github:user:bob

Only certificates with these specific principals can use the deploy account. Defense-in-depth: even a valid certificate with the right CA signature is rejected if the principal doesn’t match.

Summary

The host-side trust model is simple but deliberate. A cron job fetches, verifies, and writes. OpenSSH validates offline. No callbacks, no daemons, no runtime dependencies. The security of the system rests on a file on disk and the simplicity of the mechanism that puts it there.

The final post steps back from specifics and examines the diagnostic and observability layer — how to monitor a security system without compromising it.