From 48d8c897497ad758872046ce17c88a54bb582756 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 26 Sep 2026 22:20:10 +0200 Subject: [PATCH] The broker opening is only on the broker's host, not every node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enrolling ace applied adoption.opening-tcp-5671-incoming to it, opening 5671 from anywhere (v4+v6) where nothing listens — the ace session caught it. foundation ports widen the broker's from:mesh port to from-anywhere so a machine that is not yet on the mesh can make its first dial; that belongs on the broker's host alone. foundationPortsFor keeps the port only when a module resolved onto this node listens on it, so novox opens 5671 and a node that merely dials out opens nothing. Two tests, both directions. --- cmd/mesh-controller/foundation_scope_test.go | 33 ++++++++++++++++++++ cmd/mesh-controller/plan.go | 27 +++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 cmd/mesh-controller/foundation_scope_test.go diff --git a/cmd/mesh-controller/foundation_scope_test.go b/cmd/mesh-controller/foundation_scope_test.go new file mode 100644 index 0000000..9ff8591 --- /dev/null +++ b/cmd/mesh-controller/foundation_scope_test.go @@ -0,0 +1,33 @@ +package main + +// The broker opening belongs only on the node that listens on it (novox/hq: it leaked onto +// every enrolled node's declaration, opening a from-anywhere hole for a port nothing there +// serves). foundationPortsFor is the scope. + +import ( + "testing" + + "github.com/novox/mesh-controller/internal/catalogue" +) + +func TestTheBrokerHostGetsTheFoundationOpening(t *testing.T) { + broker := catalogue.Manifest{Module: "lavinmq", Listens: []catalogue.Listening{ + {Port: 5671, Protocol: "tcp", From: "mesh"}, + {Port: 5672, Protocol: "tcp", From: "mesh"}, + }} + got := foundationPortsFor(5671, []catalogue.Manifest{broker}) + if len(got) != 1 || got[0] != 5671 { + t.Fatalf("the node that listens on the broker port keeps it; got %v", got) + } +} + +func TestANodeThatOnlyDialsTheBrokerGetsNoOpening(t *testing.T) { + // ace's set: things that reach the broker as a client, none listening on 5671. + ace := []catalogue.Manifest{ + {Module: "plex", Listens: []catalogue.Listening{{Port: 32400, Protocol: "tcp", From: "anywhere"}}}, + {Module: "postgres", Listens: []catalogue.Listening{{Port: 5432, Protocol: "tcp", From: "mesh"}}}, + } + if got := foundationPortsFor(5671, ace); got != nil { + t.Fatalf("a node that only dials out opens nothing for the broker; got %v", got) + } +} diff --git a/cmd/mesh-controller/plan.go b/cmd/mesh-controller/plan.go index e744e2d..6927def 100644 --- a/cmd/mesh-controller/plan.go +++ b/cmd/mesh-controller/plan.go @@ -534,11 +534,19 @@ func renderingFor(ctx context.Context, open *stores, node string, // The ports the mesh itself needs open, which no module declares. Read from the broker this // control plane was told about rather than written down twice: the address a node is handed in // its token and the port its machine must accept on are the same fact. + // + // **Only on the node that listens on it** (novox/hq issue: the broker opening leaked onto + // every node). The opening exists to WIDEN the broker's port to from-anywhere — a machine + // enrolling is not on the mesh yet, so the broker's own `from: mesh` listen would refuse its + // first dial. That widening belongs on the broker's host and nowhere else: a node that only + // dials out needs no incoming rule, and an opening for a port nothing here listens on is a + // from-anywhere hole for a dead port. So the foundation port is kept only when a module + // resolved onto THIS node actually listens on it. var foundation []int if b, err := broker.FromEnvironment(); err == nil { if _, port, err := net.SplitHostPort(b.Address); err == nil { if n, err := strconv.Atoi(port); err == nil { - foundation = append(foundation, n) + foundation = foundationPortsFor(n, plan.Modules) } } } @@ -1135,3 +1143,20 @@ func portsOn( } return out, nil } + +// foundationPortsFor is the broker port, kept only when a module resolved onto this node listens +// on it (novox/hq issue: the broker opening leaked onto every node). The foundation opening +// exists to WIDEN the broker's `from: mesh` port to from-anywhere, because a machine enrolling is +// not on the mesh yet and its first dial would be refused. That widening belongs on the broker's +// host alone: a node that only dials out needs no incoming rule, and an opening for a port +// nothing here listens on is a from-anywhere hole for a dead port. +func foundationPortsFor(brokerPort int, modules []catalogue.Manifest) []int { + for _, m := range modules { + for _, l := range m.Listens { + if l.Port == brokerPort { + return []int{brokerPort} + } + } + } + return nil +} -- 2.54.0