diff --git a/03-DESIGN/01-to-be/25-the-bus-on-nats.md b/03-DESIGN/01-to-be/25-the-bus-on-nats.md index 1052976..4338f1a 100644 --- a/03-DESIGN/01-to-be/25-the-bus-on-nats.md +++ b/03-DESIGN/01-to-be/25-the-bus-on-nats.md @@ -7,7 +7,7 @@ code: - mesh-tools src/broker-amqp.ts (to be replaced) - mesh-catalog modules/nats (to be written) - mesh-sdk src (the protocol's NATS binding, step 3) -updated: 2026-09-26 +updated: 2026-09-27 decisions: - 02-DECISIONS/0106-the-bus-is-nats.md - 02-DECISIONS/0119-amqp-is-a-provision-not-the-bus.md @@ -213,7 +213,32 @@ expresses this exactly, per subject, and better than a vhost could: - **A person's user** (§7) is a module-shaped user with permissions on the tool subjects it may invoke, issued and revoked by the controller like any account. -**Accounts are configuration, not API calls.** The controller composes the server's user list and +**The server does not verify client certificates, and TLS is still required.** *Revision, +2026-09-27, found by building the module's image and connecting to it as a host would.* The first +composed configuration said `verify: true`, which makes the server demand a **client** certificate — +and nothing in the mesh presents one. A host pins this server's exact certificate and authenticates +with the password the mesh minted ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)), +and a module's runtime does the same. With it on, every connection in the mesh dies at the TLS +handshake before any password is looked at, and the error — "client didn't provide a certificate" — +reads as a fault in the client rather than in the bus's configuration. The `tls` block is what makes +TLS required; `verify` only decides whether client certificates are checked. What is given up is a +second factor the mesh has no machinery to issue or rotate — a certificate per module per node — and +what is kept is stronger than a name check in both directions: an exact pin outward, a password +scoped per user inward. **Mutual TLS is a later question and would need that machinery first.** + +**The mesh composes the accounts; the module composes its server.** *Revision, 2026-09-27, while +building the composition.* An earlier reading of the paragraph below had the controller writing the +whole file. It writes only the user list. A server's ports, its TLS paths and its store directory +are properties of the container the module raises — they live in its image and its mounts and change +when it does — so the module declares its own configuration and `include`s the mesh's half. A +controller that wrote the whole file would have to be kept in step with a Dockerfile it never sees, +and a module could not change its own image without the mesh agreeing. Asking for the user list is +not enough to receive it: the file holds every user's password hash, so the claim on `mesh-broker` is +what authorises it. And the two files share one directory of necessity — an absolute include path is +resolved relative to the including file's own directory, so a server given one from elsewhere looks +for it underneath that directory and refuses to start. + +**Accounts are configuration, not API calls.** The controller composes the mesh's user list and its permissions into a file the host declares. **How that file reaches the running server is §5's, not this one's** — revision, first review: an earlier draft said "reloads" and cited a precedent that does not apply to a container (see §5). No management API, no credential travelling through a diff --git a/03-DESIGN/01-to-be/28-building-the-bus.md b/03-DESIGN/01-to-be/28-building-the-bus.md index 1421c79..d698032 100644 --- a/03-DESIGN/01-to-be/28-building-the-bus.md +++ b/03-DESIGN/01-to-be/28-building-the-bus.md @@ -178,23 +178,38 @@ paper is wrong until there is a second mesh to find out. **Out, and what each needs.** - *Delivery.* Resolution already prepends `file` resources whose content comes from the - rendering — a certificate does exactly this, and refuses when a module asks for one and none - was issued. The composed configuration is the same shape, and the open question is what the - module *declares* in order to receive it, which is design 29's ground rather than this - document's: a field naming where it wants the file, or nothing at all because the module - holding `mesh-broker` is the one that gets it. The server's own values — ports, TLS paths, - store directory — are constants of the module's own resources today and would have to be read - from one place rather than two. + **Delivery is in, and it settled what a module declares.** The mesh writes the *accounts* and + the module owns its *server*. The alternative was a manifest field enumerating ports, TLS + paths and a store directory so the controller could write a whole configuration — wrong, + because those are properties of the container the module raises and the controller would have + to be kept in step with a Dockerfile it never sees. So a module declares its own configuration + as a file resource and `bus-users` names where the mesh's half goes beside it; **asking is not + enough to receive it**, because that file holds every user's password hash, so the claim on + `mesh-broker` is what authorises it. - *Minting, and it is transport-coupled.* A password is minted at enrolment and at assignment, + Two things a running server changed. **An absolute include path is resolved relative to the + including file's directory** — `include /etc/nats/accounts.conf` from another directory makes + the server look for it *under* that directory and refuse to start — so both files share one. + And **`verify: true` was refusing every connection in the mesh**: it makes the server demand a + *client* certificate, and nothing in the mesh presents one — a host pins this server's exact + certificate and authenticates with the password the mesh minted ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), + design 25 §4). Every connection would have died at the TLS handshake before any password was + looked at, with an error that reads as a fault in the client. Removed; TLS is still required, + because the block is what requires it and `verify` only decides whether client certificates + are checked. **Design 25 §4 should say this**, and says nothing about it today. + + Also collected here: task 1.2's payoff, end to end against the module's own image — the user + list rewritten, the module noticing and reloading the server itself with no signal from + outside, and the connection the mesh already had still working afterwards. + + *Still out — minting, and it is transport-coupled.* A password is minted at enrolment and at assignment, and an enrolment reply carries exactly one. **A node on the old bus must not be handed a credential for the new one**, so which bus a node is joining has to be a fact the controller holds before it can mint for both — the same switch the host's `Transport` is, from the other end. - *Asserting on start.* The streams, the controller's consumers and every node's are defined - and idempotent; nothing calls them from a start path yet. + *Still out — asserting on start.* The streams, the controller's consumers and every node's + are defined and idempotent; nothing calls them from a start path yet. **People are not in the list**, deliberately: the account model is built and `operator issue` is not (4.4), so there is nobody to derive. Left empty rather than guessed at.