Files
mesh-controller/internal/catalogue/machineside_test.go
T
jschoubben c67f836185 The mesh may only move a port it actually publishes
The lab caught this: a module declaring a port and running no container
had its rule set opened on 20000 while its service sat on 9101. The
firewall reported success and blocked the thing it was told to admit,
which is the precise failure the filtering comment warns about, arrived
at from the other side.

Assignment was applied to every declared port. But a container's mapping
is the thing that translates, and where there is none the software binds
what it binds — the mesh choosing a number does not move the service, it
only makes the mesh wrong about where it is.

The declaration side already knew this: publishedOn rewrites container
ports and nothing else. Filtering did not, so the two disagreed about
the same fact. MachineSide is now the one derivation both follow.

It also fixes a second case nobody had hit yet: a mapping the manifest
wrote itself, like the mail system's 7080:80. That is passed through
untouched when composing, so assigning it a machine port would have
opened a rule on a port the container does not publish. Either side of
such a mapping now names it, and the host side is the answer — a module
may read `listens` as what its software binds or as what the machine
exposes, and both readings want the same number.

Recorded either way, assigned or not: the map means where this module's
port is on this machine, and every reader needs that answer regardless
of who chose it.

Tests bite — making it always assignable reproduces the lab failure.
2026-09-01 19:31:05 +02:00

61 lines
2.5 KiB
Go

package catalogue
import "testing"
// A module with nothing that publishes binds what it binds, and the mesh may not move it.
//
// This is the case that made novox/hq 04-ISSUES/028's fix wrong on its first pass: a port was
// assigned to every module that declared one, so a service listening directly had the rule set
// opened on a number nothing was listening on, and its real port shut. The firewall reported
// success and blocked the service, which is the exact failure the mechanism exists to prevent.
func TestAPortNothingPublishesIsNotTheMeshsToMove(t *testing.T) {
m := Manifest{Module: "talker", Listens: []Listening{{Port: 9101, From: FromMesh}}}
at, mayAssign := m.MachineSide(9101)
if mayAssign {
t.Fatal("the mesh took a port it cannot move: nothing translates it, so assigning one " +
"opens the wrong number and leaves the service unreachable")
}
if at != 9101 {
t.Fatalf("a port nothing publishes reaches the machine where it binds, not at %d", at)
}
}
// A container publishing in short form is exactly the case the mesh may choose.
func TestAContainerPublishingShortIsTheMeshsToChoose(t *testing.T) {
m := Manifest{Module: "store", Resources: []map[string]any{
{"type": "container", "id": "server", "ports": []any{"5432"}},
}}
if _, mayAssign := m.MachineSide(5432); !mayAssign {
t.Fatal("a container's mapping is what translates a port, so this one is the mesh's to " +
"choose; refusing it puts every module back on a number it guessed")
}
}
// A manifest that wrote its own mapping already chose, and the machine side is the outer one.
func TestAMappingTheManifestWroteIsNotReassigned(t *testing.T) {
m := Manifest{Module: "mail", Resources: []map[string]any{
{"type": "container", "id": "front", "ports": []any{"7080:80"}},
}}
for _, named := range []int{7080, 80} {
at, mayAssign := m.MachineSide(named)
if mayAssign {
t.Fatalf("%d was reassigned though the manifest published it explicitly, which "+
"would open a rule on a port the container does not publish", named)
}
if at != 7080 {
t.Fatalf("naming %d gave %d; the machine side of 7080:80 is 7080", named, at)
}
}
}
// A port some other container publishes is not this port.
func TestAPortNotInTheMappingIsNotFound(t *testing.T) {
m := Manifest{Module: "mail", Resources: []map[string]any{
{"type": "container", "id": "front", "ports": []any{"25", "7080:80"}},
}}
if at, mayAssign := m.MachineSide(993); mayAssign || at != 993 {
t.Fatalf("993 is published by nothing here, so it binds where it binds: got %d, %v",
at, mayAssign)
}
}