A least-privilege fleet shipped
Every node in a record fleet holds exactly the capability flags on its record. This guide writes three records — a ground station that runs the fleet, a weather sensor that only reports, and an observer node lent to a partner — and shows where each flag can be seen holding.
atlasd on the operator host, a config-author key (atlasd config keygen-author) whose public half is in every node's config-authors.toml, and an identity per node from atlasd keygen --identity, which prints the record UUID, the public keys and the matching fleet add-node line.1 — Decide what each node is for
| Record | relay | classes | config_author | dashboard |
|---|---|---|---|---|
gs — ground station | true | all six | true | fleet |
sensor — weather sensor | false | Telemetry, Position, Bulk | false | none |
observer — partner | false | Voice, Position | false | own |
Start from the defaults — no relaying, Telemetry, Position and Bulk, no authorship, reads of its own node — and raise only what the job needs. A flag you leave out of a record takes its default.
2 — Write the three records
$ atlasd fleet init --fleet field-1 --file fleet-list.toml
$ atlasd fleet add-node --file fleet-list.toml --name gs \
--node-id f09b1d95-0c96-440e-bd57-1296492bee9a \
--transport-key f7eHos0bvrtSbHkkeB7K4kHiHYez9+qYPUnuslmdahc= \
--signing-key RXwaUcrkWDhJ02lZMa1wYBa/pJXocKvlpYe1JIub1Lc= \
--allowed-ip 10.0.9.1/32 --role operator \
--relay --classes Control,Voice,Position,Telemetry,Video,Bulk \
--config-author --dashboard fleet
record `gs` added to fleet-list.toml (unsigned — run `atlasd fleet sign`)
$ atlasd fleet add-node --file fleet-list.toml --name sensor \
--node-id de8bae79-560d-43fb-b269-0e12b9c478a8 \
--transport-key lZJk7MeEDtZbjfDhy7L56+C9tncbO4P+7lUjpnUt7k0= \
--signing-key OyyVGXiJGjCfvPunLsHi5AH1szsD+Uav/zPUYLgXv6M= \
--allowed-ip 10.0.9.3/32 --role sensor --dashboard none
record `sensor` added to fleet-list.toml (unsigned — run `atlasd fleet sign`)
$ atlasd fleet add-node --file fleet-list.toml --name observer \
--node-id 9b4bd4f7-3f64-4270-911c-3a611a75678f \
--transport-key Lhvl7ILoNTWhGG9zY4zPwn2zkRc//YifPHUNqIrkcW8= \
--signing-key WrWhTU/EtsWWtruj6SikkvkOhLVz50/2zsi7utq8EtU= \
--allowed-ip 10.0.9.4/32 --role partner --classes Voice,Position
record `observer` added to fleet-list.toml (unsigned — run `atlasd fleet sign`)
The sensor names only --dashboard none; everything else is its default. In the file, its record reads:
[[node]]
allowed_ips = ["10.0.9.3/32"]
name = "sensor"
node_id = "de8bae79-560d-43fb-b269-0e12b9c478a8"
roles = ["sensor"]
[node.capabilities]
classes = [
"Telemetry",
"Position",
"Bulk",
]
config_author = false
dashboard = "none"
relay = false
[[node.keys.signing]]
key = "OyyVGXiJGjCfvPunLsHi5AH1szsD+Uav/zPUYLgXv6M="
valid_from = 0
valid_to = 0
[[node.keys.transport]]
key = "lZJk7MeEDtZbjfDhy7L56+C9tncbO4P+7lUjpnUt7k0="
valid_from = 0
valid_to = 0
3 — Check, sign and publish
$ atlasd fleet check --file fleet-list.toml
fleet `field-1` seq 0: 3 records, 0 error(s), 0 warning(s), signature unsigned
$ atlasd fleet sign --file fleet-list.toml
fleet `field-1` seq 1 signed by 3c24f045 (3 records, 33a84743)
$ atlasd fleet verify --file fleet-list.toml
fleet `field-1` seq 1: signature OK (anchor `operator`)
fleet check exits 1 on errors and warns on likely mistakes: overlapping allowed_ips, a record without a signing key, and config_author on a record whose roles do not include operator. Copy the signed list to each node as its bootstrap [auth] fleet_list for the first start; later lists travel over the mesh on their own.
4 — Watch each flag hold
| Try | What happens | Where you see it |
|---|---|---|
| The sensor's path between gs and the observer is the cheapest | Routes go another way; nothing is routed through the sensor | routes[].next_hop on every node; the topology view |
| The sensor passes on a packet it received for someone else | Its neighbour drops it | caps.relay_refused on the neighbour |
The sensor marks its telemetry Control | It is sent as Position | caps.class_clamped on the sensor, and on its first neighbour if the sensor's own check was skipped; caps.class_clamped_transit on any later hop that had to lower it again |
The observer sends Bulk | It is sent as Position, the lowest class it holds | caps.class_clamped |
| A config epoch signed with the observer's key | Refused by every node | epoch.rejected in each node's journal |
The observer's node token reads /api/stats on the ground station, which protects its reads | Refused like an anonymous request; it reads only the observer itself | The response to the request |
| The sensor mints a node token | It reads no node that protects its reads, not even the sensor | The response to the request |
On the ground station, the Fleet tab lists all three records with their flags (the sensor's "no relay" in yellow), and Peers shows each peer's flags beside its name. GET /api/identity on any node returns its own flags and the two caps counters.
5 — Change a flag later
fleet set-caps edits one record, refuses any change that would leave the list invalid, and clears the signature until the next sign:
$ atlasd fleet set-caps --file fleet-list.toml sensor --classes Telemetry,Position
`sensor`: relay=false classes=[Telemetry,Position] config_author=false dashboard=none (unsigned — run `atlasd fleet sign`)
$ atlasd fleet sign --file fleet-list.toml
Publish the signed list through any one node as in the rotation guide; each node applies the new flags when it adopts the list. The flags follow the record, so rotating a node's key never touches them.