A key that is present and unusable, and what the certificate work became

Issue 014: the node's serving key was stored in the host's own encoding, so
every check that reads the file passed and no server could start. Same shape as
013 — two halves of one mechanism designed separately, each correct about its
own half. Where a file exists so a third party can read it, the format is the
interface.
This commit is contained in:
2026-08-31 00:42:13 +02:00
parent 778efaba8b
commit a6872ac099
2 changed files with 80 additions and 2 deletions
+32 -2
View File
@@ -3,7 +3,9 @@ layer: to-be
status: designed
code:
- mesh-control internal/catalogue/filtering.go
- mesh-host internal/apply (the service that reflects it)
- mesh-control internal/identity/authority.go
- mesh-host internal/identity/serving.go
- mesh-host internal/apply (the service that reflects a rule set)
updated: 2026-08-31
decisions:
- 02-DECISIONS/0005-the-node-host.md
@@ -315,7 +317,7 @@ something:
| **flush the ruleset** when loading | that empties every table on the machine, the runtime's among them. Only the mesh's own table is replaced, and it is declared empty first so the replacement works on a machine loading one for the first time |
| carry a **command to load itself** | the link may not carry an action ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). A service is declared to reflect the file instead, so replacing it restarts what loads it — the shape that rule leaves, used here for the first time for its real purpose |
**And it is enforced, which is what separates this from `scope:`.** Proven on two real machines:
**And it is enforced, which is what separates this from `scope:`.** Checked on two real machines:
two ports opened, one declared, and from the other machine the declared one answers and the
undeclared one does not — then the module is removed and the port closes with nobody editing a
rule. *A rule set that is written but never loaded passes every check that reads the file, which
@@ -348,6 +350,34 @@ fingerprint in its token ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-j
so nothing needs the CA before membership. It certifies internal names afterwards, and that is
all it does.
### What was built
*2026-08-31.*
**A node generates a fourth key**, and the reason is the one the other three already give: *a key
used for two purposes is one rotation away from breaking the other.* The identity key would work
for TLS and reusing it would mean rotating a node's identity every time its certificate is
replaced. The private half never leaves the machine; the mesh is told the public half at
enrolment.
**So there is no certificate request and nothing to seal.** The mesh signs a statement binding a
public key to a name it alone assigns, which is the whole of what a certificate authority does.
It issues rather than stores: the node's key does not change, so signing again produces an equally
valid certificate and there is nothing to keep in step.
**A machine with no name inside the mesh is refused**, not given a certificate for nothing. A
certificate for a name nothing resolves is a certificate nothing can check.
**And the key is stored in the format a server reads** — PKCS#8 PEM, not the host's own encoding.
That is not an implementation detail of whoever writes the file: the file exists *because
something else reads it*, so the format is the interface
([04-ISSUES/014](../../04-ISSUES/014-a-key-that-is-present-and-unusable/00-report.md)).
*Checked by a real handshake between two machines: one serves on its internal name with the key it
generated, the other verifies against the mesh's authority and nothing else. Every cheaper check
passed while the server could not start — the key was present, the certificate was valid, and
nothing read either the way a server would.*
## What this removes
The list is worth having in one place, because it is most of the argument: