ADRs 0138 and 0139: an assignment binds an endpoint and says how far it reaches, and a network is forwarded because a module declared it
Both follow from the same rule the mesh is built on — a node's configuration is composed from the modules assigned to it. Reach was settled separately by the filter, the proxy's names and the certificate authority, so "this must not be public" could not be written; it becomes one value on the assignment that all three read. And the forward chain consulted two constants plus a typed list although modules already declare their networks; it now forwards what they declared, with the host rendering the addresses it allocated.
This commit is contained in:
@@ -7,8 +7,10 @@ code:
|
||||
- mesh-controller internal/identity/authority.go
|
||||
- mesh-host internal/identity/serving.go
|
||||
- mesh-host internal/apply (the service that reflects a rule set)
|
||||
updated: 2026-09-27
|
||||
updated: 2026-09-28
|
||||
decisions:
|
||||
- 02-DECISIONS/0138-an-assignment-binds-an-endpoint-and-says-how-far-it-reaches.md
|
||||
- 02-DECISIONS/0139-a-network-is-forwarded-because-a-module-declared-it.md
|
||||
- 02-DECISIONS/0104-a-provision-may-be-answered-by-an-adapter-to-the-predecessor.md
|
||||
- 02-DECISIONS/0106-the-bus-is-nats.md
|
||||
- 02-DECISIONS/0105-the-mesh-adopts-the-predecessors-tunnel-in-place.md
|
||||
@@ -625,6 +627,42 @@ the found firewall reloads and reachable from a container on the node, that a ma
|
||||
enrols through the openings before and after a reload and a reboot, and that after the flip the
|
||||
declared port is open and the undeclared one closed.
|
||||
|
||||
### The networks it forwards are the ones its modules declared
|
||||
|
||||
*2026-09-28, preparing the control-node's convergence —
|
||||
[issue 141](../../04-ISSUES/141-the-forward-chain-does-not-follow-the-modules/00-report.md), settled by
|
||||
[ADR 0139](../../02-DECISIONS/0139-a-network-is-forwarded-because-a-module-declared-it.md).*
|
||||
|
||||
The forward chain denies by default, so a machine's own guests have to be allowed back in. Until now
|
||||
that was two ranges named in the control plane and, since
|
||||
[ADR 0137](../../02-DECISIONS/0137-a-machine-says-which-networks-it-routes.md), a list a machine could
|
||||
add to. On the machine measured here, six of its container networks fell outside those ranges — and
|
||||
four of the six were networks the mesh's own modules had declared and the host had created, while two
|
||||
were the predecessor's leftovers. A range wide enough to keep the four keeps the two: a filter widened
|
||||
by hand to protect what should not be there.
|
||||
|
||||
**A network is forwarded because a module declared it.** The forward chain forwards the networks of the
|
||||
modules assigned to that node and, by default, nothing else; a module unassigned stops being forwarded
|
||||
at the next reconcile. The control plane says which networks, by name, and the host — which created
|
||||
them — renders their addresses, because the runtime allocates the subnet and the host is what knows it.
|
||||
That keeps §4's shape and this document's: the mesh decides, the host applies.
|
||||
|
||||
**This is derivation from the declaration, not from the machine.** 0137 rejected reading the machine,
|
||||
for two reasons that do not apply here: a network created between two declarations is already named in
|
||||
the one that asked for it, and a network nobody declared is never forwarded however it appeared. What
|
||||
0137's mechanism keeps is the case it was right for — guests no module declares, a test bed's range —
|
||||
added to the derived set and never replacing it.
|
||||
|
||||
The runtime's own default bridge, which a container attaches to when it names no module network, is the
|
||||
runtime's and not a module's, so the host renders it from what the runtime reports. With that, no range
|
||||
is named in the control plane at all.
|
||||
|
||||
*How it is checked:* a node with two modules declaring networks renders rules for exactly those two and
|
||||
none for a third present on the machine that nothing declared — which fails against forwarding by
|
||||
range, and is how it was written; unassigning one removes its rule at the next reconcile; the default
|
||||
bridge is asserted for a runtime whose bridge is somewhere other than the old constant named; and the
|
||||
guests of a declared network keep address and name service, per chain body.
|
||||
|
||||
## 5 — Certificates
|
||||
|
||||
**Two authorities, kept separate on purpose.**
|
||||
@@ -710,6 +748,60 @@ that verifies against the internal root and nothing else — which cannot succee
|
||||
first reached the name to certify it*
|
||||
([ADR 0066](../../02-DECISIONS/0066-public-routing-is-name-agnostic.md)).
|
||||
|
||||
## 6 — One statement behind exposure, filtering and certificates
|
||||
|
||||
*2026-09-28, preparing the control-node's convergence —
|
||||
[issue 140](../../04-ISSUES/140-an-endpoints-reach-is-not-declared/00-report.md), settled by
|
||||
[ADR 0138](../../02-DECISIONS/0138-an-assignment-binds-an-endpoint-and-says-how-far-it-reaches.md).*
|
||||
|
||||
The three sections above each decide, independently, how far a service reaches. §3 composes a name
|
||||
from a label and the node's domain. §4 opens a port to the source a listen named. §5 certifies the
|
||||
names that exist, from whichever authority the proxy holds. Each is coherent on its own, and together
|
||||
they mean **reachability is never stated anywhere** — it is the sum of three derivations, and a sum is
|
||||
not something anyone can review or refuse.
|
||||
|
||||
What that costs, measured: an identity provider holding a public certificate valid 90 days and an
|
||||
internal one valid 24 hours, neither asked for by any assignment, because both names existed and a
|
||||
proxy certifies what it serves. And an endpoint that is not routed — git over ssh — which can be
|
||||
spoken about only in the filter's vocabulary, so *this must be reachable from outside* is a setting
|
||||
exactly one mechanism reads.
|
||||
|
||||
**An endpoint is the thing that was missing.** A module declares named endpoints: one port it serves,
|
||||
what it is for, and what it would serve that to absent any instruction. A route contribution names an
|
||||
endpoint rather than repeating a port number. An assignment — which is where a module's configuration
|
||||
lives ([ADR 0046](../../02-DECISIONS/0046-a-module-configuration-is-its-assignments-not-its-manifest.md))
|
||||
— then binds each endpoint to a machine port and says how far it reaches.
|
||||
|
||||
One value, three readers:
|
||||
|
||||
| reach | the filter opens | the proxy serves | the certificate comes from |
|
||||
|---|---|---|---|
|
||||
| `internal` | the machine port, to the private network | the internal name | the mesh's own authority |
|
||||
| `public` | the machine port, to anywhere | the public name | the public authority |
|
||||
| `both` | the machine port, to anywhere | both names | each name's own authority |
|
||||
|
||||
**An endpoint that is not routed is reached and never named.** No route contribution means no name is
|
||||
composed and no certificate requested, while the filter still acts on it. That is the case the model
|
||||
could not express at all, and it is the ordinary case for anything that is not HTTP.
|
||||
|
||||
**Nothing moves until an assignment says so.** An endpoint whose assignment is silent keeps the
|
||||
default its manifest states, so every machine already converged renders exactly as it does today —
|
||||
the same property §4 needed when a machine gained a way to say which networks it routes.
|
||||
|
||||
This is what the certificate questions were waiting for. Which authority signs a name, whether a name
|
||||
may appear in a public issuance log, and what must be trusted where are all answerable once an
|
||||
endpoint says whether it is internal — and unanswerable while the proxy decides by composing every
|
||||
name it can. It is also the fact
|
||||
[issue 139](../../04-ISSUES/139-an-internal-route-name-resolves-to-the-consumers-node/00-report.md)
|
||||
needs: an internal name should be composed from the machine serving the endpoint, which is the
|
||||
assignment that bound it.
|
||||
|
||||
**How it is checked** is stated with the decision: one module with two endpoints of differing reach
|
||||
asserted per chain body; the names and the certificate requests following the reach and failing
|
||||
against today's behaviour, where both are always composed; an unrouted endpoint filtered and never
|
||||
named; an assignment naming an endpoint the module does not declare refused where it is said; and a
|
||||
silent assignment rendering byte-identically to today.
|
||||
|
||||
## What this removes
|
||||
|
||||
The list is worth having in one place, because it is most of the argument:
|
||||
|
||||
Reference in New Issue
Block a user