Files
mesh-controller/internal/catalogue/reach_test.go
T
jschoubben 4b33b72160 Reach asks for names on a routed endpoint, and its port stays the manifest's
A routed endpoint's port is how the proxy reaches it and nothing else (ADR 0045):
a public service listens from the mesh, only the proxy reaches it, and it is
exposed by name. So reach on a routed endpoint asks for names, and the port keeps
what the manifest said; on an unrouted one — git over ssh, a mail port, the bus —
it governs the port, because there is no name and the port is the only way in.

Found by trying to express a real module rather than by review: routed name public
because browsers post to it, machine-side port private because it serves a
dashboard in cleartext. Under one value for both there was no way to say it, and
'public' would have reopened a port narrowed an hour earlier.

novox/hq ADR 0138, corrected in place the same day.
2026-09-29 02:50:41 +02:00

207 lines
7.9 KiB
Go

package catalogue
import (
"strings"
"testing"
)
// a web module with one routed endpoint, the shape almost every routed module in the catalogue has.
func aRoutedWeb() Manifest {
return Manifest{
Module: "web",
Listens: []Listening{{Port: 3000, From: FromMesh}},
Contributes: map[string]map[string]any{
"route": {"label": "app", "port": 3000},
},
}
}
func reachSet(reach string) SettingsBy {
return SettingsBy{"web": {{From: "node anchor",
Values: map[string]any{ReachSetting: map[string]any{"3000": reach}}}}}
}
// namesFor renders the contribution a routed module makes and returns the two names it carries.
func namesFor(t *testing.T, m Manifest, settings SettingsBy) (public, internal string) {
t.Helper()
r := Resolution{Node: "anchor", Modules: []Manifest{m},
PublicDomain: "example.test", At: "anchor.internal"}
given, err := r.contributions(settings, nil, nil)
if err != nil {
t.Fatalf("contributions: %v", err)
}
for _, c := range given["route"] {
p, _ := c.Values["name"].(string)
i, _ := c.Values["internal-name"].(string)
return p, i
}
t.Fatal("the module contributed no route")
return "", ""
}
// **Nothing said composes both names, exactly as before.** This is the assertion that keeps every
// mesh already running identical until an assignment speaks, and it is the one that would break first
// if reach were read where it should not be.
func TestAnEndpointWithNoReachKeepsBothNames(t *testing.T) {
public, internal := namesFor(t, aRoutedWeb(), nil)
if public != "app.example.test" || internal != "app.anchor.internal" {
t.Fatalf("names are %q and %q, want both composed as before", public, internal)
}
}
// An internal endpoint has an internal name and no public one — so the proxy serves it inside, and
// the public authority is never asked for a name nobody wanted. This is what "must not be public"
// could not say before.
func TestAnInternalEndpointHasNoPublicName(t *testing.T) {
public, internal := namesFor(t, aRoutedWeb(), reachSet(ReachInternal))
if public != "" {
t.Fatalf("an internal endpoint composed the public name %q", public)
}
if internal != "app.anchor.internal" {
t.Fatalf("internal name is %q, want app.anchor.internal", internal)
}
}
// And the mirror: a public endpoint gets the public name and not the internal one, so the mesh's own
// authority is not asked to certify a name the service is not reached by.
func TestAPublicEndpointHasNoInternalName(t *testing.T) {
public, internal := namesFor(t, aRoutedWeb(), reachSet(ReachPublic))
if internal != "" {
t.Fatalf("a public endpoint composed the internal name %q", internal)
}
if public != "app.example.test" {
t.Fatalf("public name is %q, want app.example.test", public)
}
}
func TestBothComposesBothNames(t *testing.T) {
public, internal := namesFor(t, aRoutedWeb(), reachSet(ReachBoth))
if public == "" || internal == "" {
t.Fatalf("both should compose both names, got %q and %q", public, internal)
}
}
// **The filter reads the same value — for an endpoint the proxy does not serve.**
//
// A routed endpoint's port is how the proxy reaches it and nothing else (ADR 0045): a public service
// listens from the mesh, only the proxy reaches it, and it is exposed by name. So on a routed
// endpoint the reach asks for a name and the port keeps what the manifest said.
func TestAnUnroutedEndpointsPortFollowsItsReach(t *testing.T) {
// The same module with its route taken away: now the port is the only way in, so reach governs it.
bare := aRoutedWeb()
bare.Contributes = nil
for _, c := range []struct{ reach, want string }{
{ReachInternal, FromMesh},
{ReachPublic, FromEverywhere},
{ReachBoth, FromEverywhere},
{ReachMachine, FromMachine},
} {
r := Resolution{Node: "anchor", Modules: []Manifest{bare}}
rules, err := r.Rules(Rendering{Settings: reachSet(c.reach)})
if err != nil {
t.Fatalf("%s: rules: %v", c.reach, err)
}
found := false
for _, rule := range rules {
if rule.Port == 3000 {
found = true
if rule.From != c.want {
t.Fatalf("reach %q made the filter say %q, want %q", c.reach, rule.From, c.want)
}
}
}
if !found {
t.Fatalf("reach %q produced no rule for the port", c.reach)
}
}
}
// **A public name does not open the machine's port**, which is the case that found this.
//
// A module whose routed name must be public and whose machine-side port must not be had no way to say
// so while one value drove both. Under one value it could not be expressed; the port would reopen.
func TestAPublicNameLeavesARoutedPortAsTheManifestSaid(t *testing.T) {
r := Resolution{Node: "anchor", Modules: []Manifest{aRoutedWeb()},
PublicDomain: "example.test", At: "anchor.internal"}
rules, err := r.Rules(Rendering{Settings: reachSet(ReachPublic)})
if err != nil {
t.Fatal(err)
}
for _, rule := range rules {
if rule.Port == 3000 && rule.From != FromMesh {
t.Fatalf("a public reach opened a routed port to %q; the proxy is how it is reached",
rule.From)
}
}
// And the name it asked for is there, so the reach was not simply ignored.
public, internal := namesFor(t, aRoutedWeb(), reachSet(ReachPublic))
if public != "app.example.test" || internal != "" {
t.Fatalf("names are %q and %q, want the public one only", public, internal)
}
}
// A reach for a port the module does not listen on reaches nothing, and is refused where it is
// written rather than accepted and ignored.
func TestAReachForAPortTheModuleDoesNotListenOnIsRefused(t *testing.T) {
_, err := Reaches(aRoutedWeb(), []Layer{{From: "node anchor",
Values: map[string]any{ReachSetting: map[string]any{"9999": ReachInternal}}}})
if err == nil || !strings.Contains(err.Error(), "reaches nothing") {
t.Fatalf("a reach naming an undeclared port was accepted: %v", err)
}
}
// A value that is not a reach is refused, and the refusal names the four so a reader is one edit from
// right. "mesh" is the tempting wrong answer, because that is the filter's word for nearly the same
// thing.
func TestAValueThatIsNotAReachIsRefused(t *testing.T) {
for _, wrong := range []string{"mesh", "anywhere", "private", "true"} {
_, err := Reaches(aRoutedWeb(), []Layer{{From: "node anchor",
Values: map[string]any{ReachSetting: map[string]any{"3000": wrong}}}})
if err == nil || !strings.Contains(err.Error(), "a reach is") {
t.Fatalf("%q was accepted as a reach: %v", wrong, err)
}
}
}
// **A port that says both reach and expose is refused.** They say the same thing in different words,
// and accepting both would have the filter follow one while the names followed the other — the
// disagreement ADR 0138 exists to remove, reintroduced by the migration away from the older word.
func TestReachAndExposeForOnePortAreRefused(t *testing.T) {
_, err := Reaches(aRoutedWeb(), []Layer{{From: "node anchor", Values: map[string]any{
ReachSetting: map[string]any{"3000": ReachInternal},
ExposeSetting: map[string]any{"3000": FromEverywhere},
}}})
if err == nil || !strings.Contains(err.Error(), "same thing in different") {
t.Fatalf("a port set both ways was accepted: %v", err)
}
}
// A path-level refusal carries no port: it is a rule about a name, not an endpoint, and it inherits
// whatever that name turned out to be. Narrowing the endpoint must not silently drop it.
func TestARuleWithNoPortIsLeftAlone(t *testing.T) {
m := aRoutedWeb()
m.ContributesMany = map[string]map[string]map[string]any{
"route": {"refused": {"label": "app", "path": "/internal", "deny": true}},
}
r := Resolution{Node: "anchor", Modules: []Manifest{m},
PublicDomain: "example.test", At: "anchor.internal"}
given, err := r.contributions(reachSet(ReachInternal), nil, nil)
if err != nil {
t.Fatal(err)
}
var sawDeny bool
for _, c := range given["route"] {
if deny, _ := c.Values["deny"].(bool); deny {
sawDeny = true
// It keeps both, because it named no endpoint to be narrowed by.
if c.Values["name"] == nil || c.Values["internal-name"] == nil {
t.Fatalf("the path rule lost a name it shadows: %v", c.Values)
}
}
}
if !sawDeny {
t.Fatal("the path rule was dropped")
}
}