Rotate a node's key without a flag day shipped

Documents the atlasd 3.0.0 release lineView as Markdown

This guide replaces the transport key of one node, drone, in a four-node record fleet. Nothing changes on the other nodes' configurations, nobody restarts but the drone and the node you publish the list through, and the drone keeps its node id, its routes and its place in every peer table throughout.

What you need: a fleet running [auth] provider = "fleet" (see the node record), the authoring copy of its fleet list on the operator host, and the config-author key that signs it. To rotate on a schedule the whole fleet acts on — with the retire step gated on the nodes that matter, the mesh secret included — use Rotate keys and the mesh secret without stranding anyone.

The rotation runs in four steps, and the node is reachable through all of them:

StepRecord listsDrone initiates withDrone answers on
Before6142ed496142ed496142ed49
1–2 List and load6142ed49, 5123339a6142ed49either
3 Activate6142ed49, 5123339a5123339aeither
4 Retire5123339a5123339a5123339a

1 — List the next key in the record

Mint the next keypair on the drone, or in whatever system produces your keys; only the public half leaves the node.

$ atlasd keygen
  ✓ Keypair generated (Curve25519)

  ⚷ Private Key (keep secret)
  private_key = "…"

  ⚷ Public Key (share with peers)
  public_key  = "0gmysyqqf3f6vVOsqi4/u7ufJq6+DS4gQuAHP0fLQgI="

On the operator host, add the public key to the drone's record as a second transport key. A window is optional; valid_from = 0 makes it valid at once.

fleet-list.toml — the drone's record
[[node.keys.transport]]
key = "aC+J3tbTGW+7vBHMWMDbeCnjdsFxCxNS4W5gCmxAHWk="   # current, 6142ed49
valid_from = 0
valid_to = 0

[[node.keys.transport]]
key = "0gmysyqqf3f6vVOsqi4/u7ufJq6+DS4gQuAHP0fLQgI="   # next, 5123339a
valid_from = 0
valid_to = 0

Check and sign. The edit invalidated the old signature; signing bumps the sequence number and signs again.

$ atlasd fleet check --file fleet-list.toml
fleet `field-1` seq 1: 4 records, 0 error(s), 0 warning(s), signature does not verify against the anchors
$ atlasd fleet sign --file fleet-list.toml
fleet `field-1` seq 2 signed by 3c24f045 (4 records, ffc955e8)

Publish it: copy the signed file to the [auth] fleet_list path of one node, typically the ground station, and restart that daemon. Every session it brings up carries the list onward, hop by hop. The journal on each node records identity.fleet_list_adopted, and the Fleet tab and atlasd fleet show -c /etc/atlas/config.toml on any node show seq 2.

2 — Load the next key on the node

Add the private half to the drone's [interface] as next_private_key and restart the drone. It now answers handshakes addressed to either key, and opens end-to-end sealed traffic on either.

$ atlasd identity show
interface      atlas0
mode           record fleet
node id        e191..81b6 (af2e580d-5365-4b5f-acfa-9a9da1f69e73)
record         drone
capabilities   relay=true classes=[Control,Voice,Position,Telemetry,Video,Bulk] config_author=false dashboard=own
transport      6142ed49 valid · held here
transport      5123339a valid · held here
signing        e5c003f3 valid · held here
fleet list     `field-1` seq 2

3 — Activate the next key

Swap the two values: the new key becomes private_key and the old one moves to next_private_key, so a peer still addressing the old key is answered. Restart the drone. It now initiates with the new key; each peer finds the key in the drone's record and rebinds its existing peer entry, logging:

identity.peer_key_changed  node e191..81b6 now presents its key 5123339a (was 6142ed49)

The node id is unchanged in every snapshot, the routes through and to the drone stay where they were, and the Fleet tab still shows the drone as one row.

4 — Retire the old key

Remove the old entry from the drone's record (or set its valid_to to the moment it should stop), then check, sign and publish as in step 1. Once a node holds the new list, it seals end-to-end traffic for the drone to the new key and no longer accepts the old one in a handshake.

$ atlasd fleet sign --file fleet-list.toml
fleet `field-1` seq 3 signed by 3c24f045 (4 records, 9dad4498)

When the Fleet tab shows seq 3 on every node, remove next_private_key from the drone at its next restart. Until then it does no harm: the drone simply holds a key nobody uses.

Rotating the signing key

The same order applies to the Ed25519 key that signs the drone's dashboard token: add the new public key as a second [[node.keys.signing]] entry, sign and publish, replace signing_key on the node and restart, then remove the old entry and sign again. Tokens minted with either key verify while both are listed.

If something is off

  • The node refuses to start with "this node's transport key … has no valid record": its private_key and next_private_key are both missing from the list it holds. Publish the list that lists the key first.
  • fleet check reports three keys valid at once: close the old key's window or remove it before adding another.
  • A node still shows the old sequence number: it has no live session to a node holding the new list. The list reaches it on its next session; nothing needs copying by hand.
Esc
↑ ↓ navigateEnter — openEsc — close