Files
hq/02-DECISIONS/0051-the-enrolment-token-carries-the-mesh.md
T
jschoubben ef5dd0751b Approve 0049-0053; drop a to-be item superseded by ADR 0044
The 'domain grouping' item cited ADR 0017 as live guidance. 0044 superseded
it -- there is no domain module to group into, so there is no domain list to
settle.
2026-08-27 01:00:29 +02:00

118 lines
6.4 KiB
Markdown

---
status: accepted
date: 2026-08-27
deciders: jochen
reconstructed: false
extends: 0039-the-link-is-the-security-boundary.md
---
# 51. The enrolment token carries where the mesh is and how to recognise it
## Context
[ADR 0039](0039-the-link-is-the-security-boundary.md) requires **mutual** authority: the node
proves it may join, and **the control plane proves it is the mesh**. It states why one-way is not
enough — the host applies whatever the link delivers, so *an attacker who can answer a joining
node's first call owns the machine.*
It does not say **how** the control plane proves itself, and
[ADR 0049](0049-a-route-is-a-grant.md) deferred the question again, noting only that internal
identity and public exposure are two different certificate stories.
Left unanswered it produces a circle. Verifying the mesh needs the mesh's CA. Obtaining the CA
means trusting whatever hands it over — which is the thing being verified.
There is a second circle in the same place, and today they are solved by the same unfortunate
mechanism. A node must reach the mesh before the mesh has configured it, so it cannot yet
resolve any mesh name. [Research 004](../01-RESEARCH/004-lab-network/analysis.md) records the
workaround:
> `dnsmasq-app` generates `.internal` names on each node and writes an `/etc/hosts` block **as a
> floor underneath, because a node must reach the mesh DB before its own DNS exists.**
and, separately, that a joining node must have *"registry database and object-store host and
credentials, plus an npm token"* placed on disk beforehand — the credentials
[ADR 0039](0039-the-link-is-the-security-boundary.md) exists to remove.
**Both circles are the same shape: a node needs some fact about the mesh before it has any
trustworthy way to obtain one.** Whatever supplies that fact must arrive by a path other than the
mesh.
## Considered options
1. **Ship the CA with the host binary.** Then the binary is mesh-specific, which
[ADR 0041](0041-the-host-depends-on-nothing.md) and
[ADR 0046](0046-the-installer-fetches-what-it-pins.md) both work to avoid, and rotating the
CA means rebuilding and redistributing the host everywhere. Rejected.
2. **Trust on first use, plainly.** Accept whatever answers the first call and pin it. Rejected:
it is exactly the attack ADR 0039 names, and the first call is the one moment the node has no
way to tell.
3. **A public certificate authority for the control plane's own endpoint.** Workable, and it
makes joining depend on public DNS and public issuance for a link that is otherwise entirely
the mesh's business. Rejected as a dependency, not as a technique — a mesh whose nodes cannot
join because an unrelated public authority is having a bad day has bought nothing.
4. **The token carries it.** Chosen.
## Decision
**The enrolment token carries three things**, and it is the only thing a joining node needs:
| | | |
|---|---|---|
| **where** | an **address**, not a name | there is no resolution yet, and this is why none is needed |
| **who** | the fingerprint of the identity to expect | what makes the mesh provable rather than assumed |
| **the right to join** | the one-time secret ADR 0039 already specifies | useless once used, useless after it expires |
The token is issued by the mesh for one enrolment and **carried out of band** — by the person
adopting the machine. That is what breaks both circles: its authenticity comes from the channel
it travelled, not from anything the node can check afterwards.
**This is trust-on-first-use with the first use moved out of band**, which is the difference
between a pin and a guess. The node does not accept whatever answers; it accepts the one thing
it was told to expect, before it spoke to anything.
### What this settles
**The mesh CA is not a bootstrap concern.** It is how `.internal` names are certified once a node
is a member, and nothing needs it earlier. The open item
[ADR 0049](0049-a-route-is-a-grant.md) left — *what the control plane presents to a node that
trusts nothing yet* — is closed: it presents the identity whose fingerprint the token named.
**Nothing needs name resolution before the link exists**, because the token carries an address.
The `/etc/hosts` floor exists to solve a problem that stops existing, and it should go rather than
be carried forward — a fallback nothing needs is a path nothing tests.
**Nothing is placed on disk beforehand except the token.** No database credential, no object-store
credential, no registry token. That is ADR 0039's central claim finally made true at the one
moment it was still false, and it is the difference between *a node holds only its own identity*
being a design statement and being a fact.
## Consequences
- **The token becomes security-critical in a way it was not**, because it now carries the pin.
Tampering with it in transit substitutes the mesh. That is a real exposure and it is strictly
better than the alternative: without a pin there is nothing to tamper *with*, and the node
trusts the first answer unconditionally. The exposure moves to a channel a person controls and
can verify, from one nobody could.
- **Token delivery is now a designed step, not an incidental one.** It is short-lived and
single-use, so interception is bounded — but how it reaches a machine is part of adoption and
needs saying. [Research 012](../01-RESEARCH/012-the-minimum-viable-node/00-overview.md) is
where that belongs.
- **Rotating the control plane's identity invalidates outstanding tokens**, which is correct and
needs to fail legibly. A node presenting a token with a stale fingerprint must be told that,
not left to time out.
- **The address in the token can go stale.** If the control plane moves, unissued tokens point
somewhere wrong. Tokens are short-lived, which bounds it; moving the control plane is
[`06`](../03-DESIGN/01-to-be/06-the-control-plane.md)'s undesigned territory regardless.
- **A rejoining node is an ordinary case, not a special one.** A node that has lost its identity
gets a new token. There is no recovery path to design because there is no long-lived secret to
recover.
## References
- [ADR 0039](0039-the-link-is-the-security-boundary.md) — the mutual authority this implements.
- [ADR 0038](0038-a-node-joins-by-linking-first.md) — the join this is the first step of.
- [ADR 0049](0049-a-route-is-a-grant.md) — the open item this closes.
- [Research 004](../01-RESEARCH/004-lab-network/analysis.md) — the `/etc/hosts` floor and the
credentials placed beforehand.