Skip to content

Security model

Swarm’s API is protected by mutual TLS (mTLS) end to end. Every API request must present a client certificate signed by the swarm CA; the server itself presents a certificate signed by the same CA. This page describes the identity lifecycle and the trust anchors that make it work.

CLI (swarm) --HTTPS (mTLS)--> Server (swarm-server) --SSH--> Agent (swarm-agent)
|
Docker containers
  • CLI ↔ server: mutual TLS. The CLI authenticates with a CA-signed client certificate; it pins the swarm CA as its trust anchor for the server’s TLS identity.
  • Server ↔ agent: an SSH tunnel from the server to each VM. Host keys are verified with trust-on-first-use against the server’s known_hosts store (see Server setup); the agent itself binds to 127.0.0.1 on the VM.

On first start, the server generates a CA (ca.key/ca.crt) in ca_dir (default: next to the registry). The CA signs:

  • the server’s own TLS leaf certificate (regenerated on restart — clients pin the CA, not the leaf, so restarts don’t break clients), and
  • every client certificate issued through enrollment.

The CA certificate is the stable trust anchor. Clients capture it at login as ~/.swarm/server.pem; scripts can fetch it at any time from GET /ca (the only public endpoint besides GET /healthz).

Enrollment tokens (short-lived, single-use)

Section titled “Enrollment tokens (short-lived, single-use)”

Enrollment tokens are the only way to obtain a client certificate. They are single-use and expire after enroll_token_ttl (default 15m). Tokens are hashed at rest and persisted in tokens.json next to the registry, so a restart does not invalidate a token — but the TTL still applies. Only two sources mint them:

  • First start: the server prints exactly one token when the CA is first created (the bootstrap token). It is never printed again.
  • swarm server token: mints a fresh single-use token; requires an already-enrolled client.
Scenario What happens How to proceed
First-start token used within 15 min Token consumed, first user enrolled Normal; further users need swarm server token
First-start token expired unused, no users enrolled Token store rejects it; swarm server token is unavailable (nothing is enrolled yet) Reset auth state (below) to force a new first-start token
Token survives a server restart Still valid until its TTL elapses (store is persisted) Just use it
Token already consumed Rejected with 401 Mint another with swarm server token
TTL too short/long for your ops enroll_token_ttl in swarm-server.json (Go duration, e.g. 1h) Applies to tokens minted after the change

Reset auth state (only needed for the expired-first-token trap):

Terminal window
sudo systemctl stop swarm-server
sudo rm /var/lib/swarm/ca.key /var/lib/swarm/ca.crt /var/lib/swarm/tokens.json
sudo systemctl start swarm-server
journalctl -u swarm-server | grep -A2 "swarm enrollment token"

This regenerates the CA and a fresh bootstrap token. Only safe when no client has ever enrolled — otherwise every existing certificate and pinned trust anchor stops working (see CA rotation below). The registry is untouched.

  • Expiry: certificates are valid for 365 days; an expired certificate fails the TLS handshake. Re-enroll with swarm login --token <fresh-token> — this replaces the identity.
  • Revocation (swarm user revoke <serial>): immediate and serial-based; the revoked cert’s next request gets 401. Use this for departures, lost laptops, or suspected compromise — expiry is only the backstop.
  • Renewal before expiry: there is no automatic renewal; re-running swarm login --force --token <token> swaps in a new certificate (and, if the CA changed, a new trust anchor) at any time.

CA rotation (breaks everything, by design)

Section titled “CA rotation (breaks everything, by design)”

Replacing the CA (ca.key/ca.crt) invalidates all client certificates — the TLS layer only trusts the current CA — and every client’s pinned server.pem. After a CA rotation every user must re-enroll with a fresh token. This is the escape hatch for the unused-first-token trap above, and the correct response to CA key compromise. Back up ca.key/ca.crt (they live next to the registry) if you ever want to restore credentials after a server rebuild.

Surface Mechanism
API (all /api/*, including the container proxy) mTLS — client cert signed by the swarm CA
GET /healthz, GET /ca Public (no auth)
POST /enroll One-time token + PKCS#10 CSR
Enrolled users swarm user list / swarm user revoke <serial>
Server ↔ agent SSH tunnel, TOFU host-key verification

Sensitive server-side state is stored root-only (0600) next to the registry:

  • secrets.json — named secrets for container env injection (values never returned by the API).
  • git-credentials.json — per-host git credentials (tokens never returned by the API).
  • tokens.json — hashed enrollment tokens.

Provider credentials are never stored in the config file — they come from shell commands or environment variables (see Server setup).

  • Single role. All authenticated users are operators; no RBAC yet.
  • No server↔agent auth beyond the SSH tunnel. Host-key verification is trust-on-first-use; there is no per-agent secret.
  • Long-lived client certs (~1 year). Revocation is serial-list based and immediate; expiry is the backstop.

See Known limitations for the full list.