A node's identity is a keypair it generates. This was never open.
I have been treating "what a node presents to prove it is that node" as an undecided design question for weeks, and blocking on it. It was decided. 08-connectivity says of the overlay keys: each node generates its own keypair, the private key never leaves the machine, the public key is published to the mesh -- and says explicitly that this IS ADR 0004's "a node holds its own identity", applied. Nobody had applied it to the thing 0004 is actually about. What caused it was a word. The lifecycle said a joining node receives its own durable identity, which reads as the mesh issuing something, and then the question is what. The mesh issues nothing. A node arrives holding its identity; what it receives is being known. That line now says what happens: it presents the one-time secret and its own public key, which the mesh records. The rule above it then holds literally rather than aspirationally. The mesh stores a public key, so a copy of the mesh's database grants nothing, and compromise of a node really is compromise of only that node. Also recorded, since it was asked directly: same principle as SSH, own key, not the machine's SSH host key. Host keys are regenerated by reinstalls and image clones, which would silently un-enrol a node; their lifecycle belongs to sshd rather than the mesh; and a partial host has no SSH daemon at all, so an identity scheme resting on one excludes a supported kind of node. The good half of that idea is kept: the mesh knows every node, so it can distribute host keys the way it distributes authorised keys, and node-to-node SSH stops depending on trust-on-first-use.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user