diff --git a/02-DECISIONS/0039-the-link-is-the-security-boundary.md b/02-DECISIONS/0039-the-link-is-the-security-boundary.md index ef9d005..5539174 100644 --- a/02-DECISIONS/0039-the-link-is-the-security-boundary.md +++ b/02-DECISIONS/0039-the-link-is-the-security-boundary.md @@ -41,13 +41,35 @@ done, it has been done by hand, and doing it wrong has taken services down."* Compromise of any node is therefore compromise of the mesh's database, and there is no mechanism to recover from it. +### The link is not new + +Written first as though the link were a thing to build. It is not. +[ADR 0001](0001-nodes-communicate-over-a-broker.md) already has it: *every node connects +outbound to a single broker; nothing ever connects to a node*, each node declaring an exchange +named for itself and consuming from its own queue +([`00-as-is/01`](../03-DESIGN/00-as-is/01-mesh-and-transport.md)). + +That is already outbound-only, already per-node addressed, and already the one channel +everything arrives through. **This record is not proposing a channel. It is proposing that the +channel carry per-node identity instead of one shared credential.** + +The same as-is records the fault, for the broker rather than the database: *"the broker is a +single point of failure and a single point of trust. Its credential is mesh-wide, so rotating +it is a mesh-wide operation, and doing it wrong has taken the broker down."* + ## Considered options 1. **Keep shared credentials, scope them per node.** Least change: give each node its own database role. Rejected — it makes the blast radius smaller without changing its shape, and it keeps tier 0 speaking the control plane's schema, which ADR 0037 forbids for reasons that are not about security at all. -2. **Mutual authority on a node-initiated link, with the node holding nothing but its own +2. **Accept the exposure as the cost of simplicity.** A shared credential is one thing to + understand and nothing to build, and the objection to replacing it is real: mutual + authentication fails opaquely, and a node that cannot link is harder to debug than a node + with a wrong password. Rejected on the ground that the simplicity is what makes it + unrotatable — the credential cannot be changed *because* everything holds the same one, so + the arrangement's convenience and its unfixability are the same property. +3. **Mutual authority on a node-initiated link, with the node holding nothing but its own identity.** Chosen. ## Decision @@ -102,6 +124,30 @@ exchange, and it expires whether used or not. This replaces hand-carried shared secrets with a thing that is useless once used and useless after a while. +## What this actually costs + +The objection to weigh is overhead, and it is smaller than it looks because most of it is +already running. + +| Property | Where it comes from | +|---|---| +| outbound, node-initiated | already true — ADR 0001 | +| per-node addressing | already true — per-node exchange and queue | +| per-node credential | a broker user per node; the broker already has users, virtual hosts and per-queue permissions | +| mutual authority | transport-level certificates on a connection that already exists | +| bounded by form | already true — three message shapes and only three | +| **enrolment** | **the one genuinely new mechanism** | + +And ADR 0037 subtracts rather than adds: under it a node holds **no** database credential at +all, so this record replaces three hand-carried shared secrets with one per-node identity that +grants only identity. + +**It must fail legibly.** A boundary that refuses a node without saying why is worse than the +credential it replaced, because a wrong password at least announces itself. A node that cannot +link must report which side rejected it and on what grounds, in terms someone can act on. This +is `how-we-build` §5 applied to a security mechanism: a refusal that proves only that something +went wrong is transport reported as effect. + ## Consequences - **ADR 0037 removes a standing exposure as a side effect.** Its rule — the host never queries