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.
118 lines
6.4 KiB
Markdown
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.
|