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.
Architecture
Section titled “Architecture”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_hostsstore (see Server setup); the agent itself binds to127.0.0.1on the VM.
The swarm CA
Section titled “The swarm CA”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):
sudo systemctl stop swarm-serversudo rm /var/lib/swarm/ca.key /var/lib/swarm/ca.crt /var/lib/swarm/tokens.jsonsudo systemctl start swarm-serverjournalctl -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.
Client certificates (long-lived, ~1 year)
Section titled “Client certificates (long-lived, ~1 year)”- 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.
Authentication at a glance
Section titled “Authentication at a glance”| 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 |
Secrets at rest
Section titled “Secrets at rest”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).
Current limitations
Section titled “Current limitations”- 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.