diff --git a/02-DECISIONS/0004-a-node-and-how-it-joins.md b/02-DECISIONS/0004-a-node-and-how-it-joins.md index e5a1b7c..9c010eb 100644 --- a/02-DECISIONS/0004-a-node-and-how-it-joins.md +++ b/02-DECISIONS/0004-a-node-and-how-it-joins.md @@ -117,6 +117,51 @@ does not own. **Compromise of a node is compromise of that node** — which the does not have, because every node permanently holds the same database and object-store credentials, and there is no mechanism that rotates one and informs everything holding it. +### What that identity is: a keypair the node generates + +*Written 2026-08-29. This is the same rule as the sentence above, and it had been treated as an +open question for weeks because of a word.* + +**The node generates a keypair. The private half never leaves the machine. The mesh records the +public half.** Ed25519, the same as the control plane's signing key, in the other direction: +the mesh proves itself to a node by signing, and a node proves itself to the mesh by signing. + +**This was never open.** [`08-connectivity.md`](../03-DESIGN/01-to-be/08-connectivity.md) already +says it of the overlay keys, in these words: *each node generates its own keypair, the private key +never leaves the machine, the public key is published to the mesh* — and adds that this **is** +ADR 0004's *a node holds its own identity*, applied. What was missing was applying it to the thing +this record is about. + +**The word that caused it:** the lifecycle says a joining node *receives* its own durable identity, +which reads as the mesh issuing something, and then the question becomes *issuing what*. It does +not issue anything. The node arrives holding its identity; what it receives is **being known**. +Enrolment is the moment the mesh writes down a public key it will believe, and the one-time secret +is what buys the right to have it written down. + +**Everything above then holds literally.** Nothing is stored that could be stolen and replayed: the +mesh's copy is a public key, so a copy of the mesh's database grants nothing. *Compromise of a node +is compromise of that node* becomes true rather than aspirational, because the only secret on a +machine is the one that identifies it. + +### Its own key, not the machine's SSH host key + +Reusing the host key is the obvious economy and it is refused, for reasons that are operational +rather than fastidious: + +- **It is regenerated by ordinary events.** A reinstall, an image cloned, `ssh-keygen -A` on a + rebuild — each silently un-enrols the node, and the failure appears as an authentication problem + with no cause anybody changed. +- **It is managed by something else.** Its lifecycle belongs to the machine's SSH daemon, and an + identity the mesh depends on should not rotate on a schedule the mesh does not know about. +- **Not every node has one.** A partial host has no SSH daemon + ([ADR 0005](0005-the-node-host.md)), and an identity scheme that excludes a supported kind of + node is not one. + +**The mesh should still know the host key** — it knows every node, so it can distribute host keys +the same way it distributes authorised keys +([ADR 0006](0006-the-substrate-and-the-control-plane.md)), and node-to-node SSH stops depending on +trust-on-first-use. That is the good half of the idea, kept. + **Authority is mutual.** The node proves it may join, and the control plane proves it is the mesh. One-way is not enough: the host applies whatever the link delivers, so a node that cannot tell the mesh from something impersonating it will apply that something's declarations. diff --git a/03-DESIGN/01-to-be/09-the-node-lifecycle.md b/03-DESIGN/01-to-be/09-the-node-lifecycle.md index 047a5e4..3c5a23a 100644 --- a/03-DESIGN/01-to-be/09-the-node-lifecycle.md +++ b/03-DESIGN/01-to-be/09-the-node-lifecycle.md @@ -150,7 +150,7 @@ What happens, in order: 1. the host dials the broker at the address in the token, **over the underlay**; 2. it checks the broker's certificate against the pinned fingerprint — *before* sending anything; -3. it presents the one-time secret and receives its **own durable identity**; +3. it presents the one-time secret **and its own public key**, which the mesh records; 4. it reports its `profile` and `inventory` upward; 5. the control plane decides what this machine should be, and sends a declaration; 6. the host applies it, reads back, and reports.