Skip to main content
Two steps: the user’s public key has to exist in Custos, and they need an active host.access grant on the host.

1. Register the user’s SSH public key

Keys belong to the user, not to a host. GET /keys lists them; DELETE /keys/{id} removes one. Deleting a key does not erase its history — the fingerprint is denormalized into the SSH access log.

2. Grant host.access

Target a group instead of a host ("target_kind":"group") to grant access to every host in that group, including hosts added later. The grant recomputes the host’s snapshot and pushes it over the daemon’s live WebSocket immediately. Offline hosts reconcile on their next connect.

3. Log in

The account must be one of the host’s declared accounts. sshd calls custosd authkeys, which asks the daemon for the authorized keys for that account.

Revoking

DELETE /grants/{id} revokes the grant and pushes a fresh snapshot — effective on the next login attempt, not just the next reboot. Revocation is a soft delete, so the trail survives. To cut a person off everywhere at once, suspend the user (POST /users/{id}/suspend): that kills their sessions and pushes new snapshots to every affected host. The snapshot query filters on active user status, which is what makes suspension cut SSH as well as API access.

Troubleshooting

A connected daemon reports cached ssh keys: 0. Force a resync:
The response includes key_count. If that is 0, the control plane itself has no active host.access grant plus matching public key for the host — the problem is the grant or the key, not delivery. Logins fail while the control plane is down. They should not. custosd authkeys retries the daemon socket and then falls back to the last-known-good cache file on disk. sshd is not calling Custos at all. Check the drop-in exists and sshd accepted it:
AuthorizedKeysCommandUser must be a real existing user.

Auditing