ADR 0103: what an adopted node holds and what its guard refuses; the node host and connectivity designs name 0102 and 0103
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
---
|
||||
topic: the mesh
|
||||
status: accepted
|
||||
date: 2026-09-22
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md
|
||||
---
|
||||
|
||||
# 103. What an adopted node holds, and what its guard refuses
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md) holds a found file or
|
||||
container until its module is taken. It also has the mesh guard the store's port and the
|
||||
broker's management port with a table that only refuses. Two independent reviews of the build
|
||||
found both rules drawn too narrowly and, for the guard, in the wrong place:
|
||||
|
||||
1. **Other kinds of resource reach what was found.** An untaken module's directory is re-owned
|
||||
and re-moded if a predecessor's directory is already at its path, and a database refuses to
|
||||
start on a data directory whose mode changed. A service of the same name as a predecessor's
|
||||
unit is started, stopped or re-enabled. An action run *in* a held container runs inside the
|
||||
predecessor's service. A container that is not found under its own name is created, and can
|
||||
mount the predecessor's data beside the predecessor's own container.
|
||||
2. **The guard's two ports are not the only ones a found firewall misses.** A published
|
||||
container port is forwarded, not received, and a firewall that filters only incoming traffic
|
||||
never sees it (ADR 0100's own Context). The broker's plaintext port is published on every
|
||||
interface and admitted by the filter from the private network only. So on an adopted node it
|
||||
is reachable from anywhere. The same holds for any published port a module restricts to the
|
||||
private network.
|
||||
3. **The guard refuses by port alone.** On a machine that routes for others, such as a
|
||||
predecessor's private-network hub, a packet for another machine's database port is refused as
|
||||
well. And a port the guard refuses for a module that is assigned but not taken may still be
|
||||
the predecessor's own, serving the predecessor's other machines.
|
||||
|
||||
## Decision
|
||||
|
||||
**Found covers every kind that can reach what the machine already has.** On an adopted node,
|
||||
for a module not yet taken:
|
||||
|
||||
- a **directory** present with no record is held: its mode and owner are left, and nothing in it
|
||||
is touched;
|
||||
- a **service** whose unit is present with no record is held: its state and whether it starts at
|
||||
boot are left. A reload named by the module still happens, since a reload stops nothing
|
||||
([ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md));
|
||||
- a **container** not found by its name is still held if it would mount a path or a volume that
|
||||
is present with no record, because it would share the predecessor's data;
|
||||
- an **action** or one-off step run *in* a held container is held until the module is taken.
|
||||
|
||||
What is held is reported as held and is never removed, as ADR 0100 decides for files and
|
||||
containers.
|
||||
|
||||
**The guard refuses what the found firewall may miss, for taken modules only.** Its ports are
|
||||
derived, not listed. They are every machine port a *taken* module publishes that the filter
|
||||
would admit from the private network only, together with the store's port and the broker's
|
||||
management port. A port of a module that is assigned but not taken is not guarded: it may still
|
||||
be the predecessor's. The guard matches only packets addressed to this machine. Traffic the
|
||||
machine routes for others is never its business.
|
||||
|
||||
**An opening already answered by a found rule is not added.** If the found firewall already
|
||||
admits what an opening says, the opening is reported as satisfied by the found rule, and the
|
||||
mesh adds nothing and later removes nothing. The found firewall treats two rules differing only
|
||||
in their comment as one rule, so adding the mesh's would take over the operator's.
|
||||
|
||||
**A machine raised adopted stays adopted if genesis is run again.** The installer reads the
|
||||
node's mode from what the machine records, not only from the operator's flag. A run without
|
||||
the flag on an adopted machine is refused.
|
||||
|
||||
## Consequences
|
||||
|
||||
- An untaken module on an adopted node can come up only beside what was found, never on top of
|
||||
it: whatever would share the predecessor's data waits for the cutover.
|
||||
- The guard grows with the modules taken, and a module's published private-network port is
|
||||
protected on an adopted node the way the derived filter protects it on a converged one.
|
||||
- Guarding only taken modules means a foundation port on a node joining adopted is guarded once
|
||||
its module is taken, not when it is assigned. Genesis takes the foundation's modules, so the
|
||||
control-node's store is guarded from the first push.
|
||||
|
||||
## How it is checked
|
||||
|
||||
Unit tests hold the host to holding a found directory, a found service and a container that
|
||||
would mount found data, to deferring an action run in a held container, and to adding no
|
||||
opening a found rule answers. They hold the controller to deriving the guard from taken modules'
|
||||
private-network published ports, and the guard's text to matching only this machine's
|
||||
addresses. The installer's tests hold a re-run without the flag on an adopted machine to a
|
||||
refusal. The adoption lab bed asserts that the broker's plaintext port is unreachable from
|
||||
outside the private network, and that an operator's own rule for a port the mesh opens
|
||||
survives the opening being removed.
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md),
|
||||
[ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md)
|
||||
@@ -91,6 +91,7 @@ python3 00-META/checks/index.py fail if stale
|
||||
- **0100** — [A node in use is adopted before it is converged](0100-a-node-in-use-is-adopted-before-it-is-converged.md)
|
||||
- **0101** — [A machine's own resolver does not make it in use](0101-a-machines-own-resolver-does-not-make-it-in-use.md)
|
||||
- **0102** — [The mesh writes into a shared file, never over it](0102-the-mesh-writes-into-a-shared-file-never-over-it.md)
|
||||
- **0103** — [What an adopted node holds, and what its guard refuses](0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md)
|
||||
|
||||
### Its tiers, from the bottom up
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ code: [mesh-host]
|
||||
updated: 2026-09-22
|
||||
decisions:
|
||||
- 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md
|
||||
- 02-DECISIONS/0102-the-mesh-writes-into-a-shared-file-never-over-it.md
|
||||
- 02-DECISIONS/0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md
|
||||
- 02-DECISIONS/0019-how-this-repository-works.md
|
||||
- 02-DECISIONS/0004-a-node-and-how-it-joins.md
|
||||
- 02-DECISIONS/0005-the-node-host.md
|
||||
@@ -150,6 +152,18 @@ checked:* unit tests hold the host to keeping a found file and container, conver
|
||||
taken, never removing a held file and reporting one that changed; the adoption bed asserts a found
|
||||
file byte for byte unchanged until its module is taken.
|
||||
|
||||
**Found reaches every kind that can touch what the machine has**
|
||||
([ADR 0103](../../02-DECISIONS/0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md)). For a module not yet taken, a directory present with no record
|
||||
keeps its mode and owner, a unit present with no record keeps its state and boot setting, a
|
||||
container that would mount found data is not created, and an action run in a held container
|
||||
waits for the cutover. **A file the machine shares is written into, never over**
|
||||
([ADR 0102](../../02-DECISIONS/0102-the-mesh-writes-into-a-shared-file-never-over-it.md)): the host sets the mesh's keys in the object already there,
|
||||
keeps every other key, records what each of its keys held before and gives them back when the
|
||||
file is undeclared. Such a file replaces nothing, so it is never held. A service that re-reads
|
||||
its configuration is **reloaded** for what it names in `reload-on`, never restarted. *How it is
|
||||
checked:* unit tests hold the host to each of these, and the adoption bed asserts the runtime's
|
||||
own settings survive adoption and a container without a restart policy keeps running.
|
||||
|
||||
## Where a declaration comes from
|
||||
|
||||
One behaviour, two sources
|
||||
|
||||
@@ -9,6 +9,7 @@ code:
|
||||
- mesh-host internal/apply (the service that reflects a rule set)
|
||||
updated: 2026-09-22
|
||||
decisions:
|
||||
- 02-DECISIONS/0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md
|
||||
- 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md
|
||||
- 02-DECISIONS/0099-a-step-that-runs-once-names-what-it-reads.md
|
||||
- 02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md
|
||||
@@ -550,9 +551,13 @@ the found firewall in that firewall's own terms, marks it as the mesh's, removes
|
||||
marked, and re-checks every opening on each reconcile so a reload or a reboot does not lose it for
|
||||
longer than one reconcile. An opening is state, not a command, so it travels over the link like any
|
||||
other resource. **The mesh guards its own ports in a table of its own that only refuses** —
|
||||
passing everything by default and holding nothing but refusals of the foundation's own two ports,
|
||||
checked free at genesis, so it cannot close what the machine serves, and the found firewall's
|
||||
reload does not touch it. It refuses the store's port and the broker's management port except from
|
||||
passing everything by default and holding nothing but refusals, and the found firewall's reload
|
||||
does not touch it. Its ports are derived ([ADR 0103](../../02-DECISIONS/0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md)): every machine port a
|
||||
*taken* module publishes that the filter admits from the private network only, with the store's
|
||||
port and the broker's management port. A port of a module not yet taken may still be the
|
||||
predecessor's, so it is not guarded; and only packets addressed to this machine are matched, so
|
||||
traffic it routes for others passes. An opening a found rule already answers is not added, so
|
||||
removing it never removes the operator's rule. It refuses those ports except from
|
||||
the private network and the machine itself — loopback and the container runtime's networks, known by the interface a packet arrives on, never by
|
||||
its source address alone — at
|
||||
the prerouting hook, before the container runtime redirects the packet, so it matches the port the
|
||||
|
||||
Reference in New Issue
Block a user