The firewall opens the port the mesh itself runs on

Rules are derived from what modules declare they listen on, and the substrate is
not a module. So the broker's port — the one every machine dials to enrol and to
receive every declaration it is ever sent — appeared in no ruleset the mesh has
ever generated.

Nothing caught it because a mesh of one never dials its own broker across the
network: the ruleset looks complete right up until a second machine tries to
join a firewalled anchor and is refused by the packet filter, during enrolment,
before the mesh can report anything about it. Assigning the firewall before
joining machines is both the natural order and the one that breaks.

It is a floor for the same reason ssh is. A machine nobody can reach cannot be
repaired; a machine the mesh cannot reach cannot be managed. Neither is a thing
any module asks for and neither may be derived away.

From anywhere rather than from the private network, deliberately: a node enrols
BEFORE it has an address on that network, so narrowing the rule to it would close
the door being knocked on.

The port is read from the broker this control plane was told about, so the
address handed out in a token and the port a machine must accept on stay one
fact. A mesh never told about a broker gets no such rule, rather than a broken
one — and cannot issue tokens either, which is where that surfaces.

Closes novox/hq 04-ISSUES/052.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
2026-09-15 00:54:31 +02:00
parent 5062c36fc9
commit dda001d64b
6 changed files with 113 additions and 18 deletions
+7 -1
View File
@@ -89,6 +89,12 @@ type Rendering struct {
// a fact about the mesh, and resolution answers questions about one machine.
Mesh []string
// Substrate is the ports the mesh itself needs reachable on every machine, which no module
// declares because the substrate is not a module (novox/hq 04-ISSUES/051 and 052). The broker
// is the one that matters: a machine dials it to enrol, and a firewall derived only from
// modules closes it.
Substrate []int
// Names is every machine's internal name and its address, for containers to be given.
//
// **A container does not inherit the machine's names**, so every internal name the mesh wrote
@@ -207,7 +213,7 @@ func (r Resolution) Declaration(with Rendering) ([]map[string]any, error) {
if err != nil {
return nil, err
}
filtering := AsNftables(rules, with.Mesh, r.PublicDomain != "")
filtering := AsNftables(rules, with.Mesh, r.PublicDomain != "", with.Substrate)
var out []map[string]any
for _, m := range r.Modules {