Peer authentication

Documents the atlasd v0.2 release lineView as Markdown

Ten nodes tolerate hand-edited peer lists. A hundred don't. The [auth] section answers exactly one question — what happens when a handshake arrives from a public key that is not in the config? — and it answers it with three providers of increasing dynamism, every one of which fails closed. Peers you configured explicitly are always accepted; a provider only ever extends trust, never revokes what you wrote.

Provider static — the default

Unknown keys are rejected. Identical to having no [auth] table at all. This is the right answer for fixed fleets: the config file is the complete trust statement, and nothing can widen it.

Provider keyfile — an allowlist that updates without restarts

[auth]
provider = "keyfile"
keyfile = "/etc/atlas/authorized_peers.toml"

The keyfile is a TOML list of [[peer]] entries — public_key plus allowed_ips — re-read whenever its modification time changes. Granting a new node is appending an entry; revoking is deleting one; neither touches the daemon. Two properties to plan around:

  • Revocation stops new sessions. An established session lives until its next rekey — with the default rekey interval, minutes, not days. Rotate the interval down if your threat model needs faster eviction.
  • An unreadable keyfile authorizes nobody. Fail closed means a botched deploy of the allowlist quietly reverts you to static behavior rather than open doors.

Provider command — your control plane decides

[auth]
provider = "command"
command = "/usr/local/lib/atlas/authorize-peer"
command_timeout_ms = 5000
deny_cache_secs = 30

For every unknown key, the daemon executes your hook as command <base64-pubkey> <source-addr>. Exit 0 authorizes, and each stdout line becomes an allowed-IPs CIDR for the new peer; any other exit denies. This is the integration point for an enrolment database, LDAP, an inventory system — whatever owns identity in your world. The contract is small enough to implement in a shell script and strict enough to trust:

  • The hook runs off the packet path, under a hard time budget (command_timeout_ms) — a hung hook drops that handshake, never stalls the daemon.
  • Denied keys are cached (deny_cache_secs) so a handshake flood from an unauthorized key cannot hammer your backend.
  • A missing or non-executable hook refuses to start the daemon. Misconfiguration is loud, at startup, on purpose.
#!/bin/sh
# authorize-peer <base64-pubkey> <source-addr>
# Example: ask an internal inventory service; print the peer's tunnel /32.
KEY="$1"
RESP=$(curl -fsS --max-time 3 "https://inventory.example.internal/atlas/authorize?key=${KEY}") || exit 1
echo "$RESP"      # e.g. "10.0.100.57/32"
exit 0

Choosing, honestly

Fleet shapeProviderWhy
Fixed, small, security-reviewed configsstaticThe config is the audit artifact.
Growing fleet, human-approved joinskeyfileGrant/revoke is a file edit — scriptable, diffable, no restarts.
Enrolment owned by an external systemcommandAtlas asks; your system answers; the contract is two arguments and an exit code.

Library embedders have a fourth option: inject a custom authenticator implementation directly into the engine, bypassing the providers entirely.

Scope check: [auth] governs data-plane peers — who may join the tunnel. Dashboard users, API tokens and mesh read tokens are a separate, independently documented surface: see the REST API auth model.
Esc
↑ ↓ navigateEnter — openEsc — close