The pieces
Control plane (custoscp) — an HTTP API backed by Postgres. It is the source of truth for
users, hosts, SSH keys, grants, credentials, and secret sets. It signs authorized-key snapshots and
seals secret bundles.
Daemon (custosd) — one per host. It does three jobs:
- Answers sshd’s
AuthorizedKeysCommandon every login attempt, over a local Unix socket. - Holds a long-lived WebSocket to the control plane and applies what it receives.
- Serves bound secret sets to local apps over a second Unix socket, for
custosd exec.
/etc/ssh/sshd_config.d/70-custos.conf and never
edits the main config.
Direction of travel
The daemon dials out to the control plane. Hosts need no inbound ports and no public address. The control plane cannot reach a host that is not connected; it pushes to the ones that are and reconciles the rest on reconnect.The SSH login path
On each login attempt, sshd runscustosd authkeys, which queries the running daemon over
/run/custos/custosd.sock and prints the authorized keys for the requested account. The daemon
holds no local policy: it serves whatever was in the last snapshot the control plane signed.
If the daemon is unreachable, authkeys retries, then falls back to the last-known-good cache file
so a control-plane outage does not lock everyone out of every host.
Because sshd bypasses the API’s auth middleware entirely, the snapshot query is what enforces user
state — a suspended user’s keys are excluded from the snapshot, which is what makes suspension
actually cut SSH.
Delivery model
Everything the control plane sends is either a full snapshot or a sealed bundle, never a delta:- Snapshots carry the complete authorized set for a host. The daemon replaces its cache with each one. Bulk revoke is just a fresh snapshot; revoking a host entirely is an empty snapshot followed by a forced disconnect.
- Secret sets are sealed as one blob to the host’s X25519 key, so set names and bindings are inside the ciphertext. The daemon keeps them in memory only and never writes them to disk.
Trust layers
App-level crypto exists exactly where an end-to-end guarantee is required. Full message-by-message
breakdown in the daemon protocol.
Secrets at rest
Credentials and secret-set values are envelope-encrypted in Postgres: a per-secret AES-256-GCM data key, wrapped by a master key held behind a key-wrapper interface (CUSTOS_MASTER_KEY in the simple
deployment, a KMS or HSM in production).
This is not zero-knowledge. The control plane can decrypt stored secrets — it has to, in order to
seal them to a host or return them to an authorized API caller. The threat model is a stolen
database, not a malicious operator.
Enrollment and host identity
A host joins once, with an admin-issued single-use token. During enrollment the daemon generates its own Ed25519 identity key and X25519 encryption key and registers only the public halves; the private halves never leave the machine. From then on the host authenticates by signing a challenge nonce — there is no bearer credential on the host to steal. Custos enforces one active host per machine, keyed on a hash of/etc/machine-id. Revoking a host
frees the machine to re-enroll, fresh, with no inherited grants.