From 3dc7d0e386412069818f2f39694b740f65a4d58b Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 22 Sep 2026 18:00:53 +0200 Subject: [PATCH] Preview ssh as the derived filter admits it, and say a narrowing to the private network closes (hq ADR 0100) --- cmd/mesh-controller/adopting_test.go | 73 +++++++++++++++++++++++----- cmd/mesh-controller/adoption.go | 56 ++++++++++++++++----- 2 files changed, 106 insertions(+), 23 deletions(-) diff --git a/cmd/mesh-controller/adopting_test.go b/cmd/mesh-controller/adopting_test.go index 1936b9a..e52a6f4 100644 --- a/cmd/mesh-controller/adopting_test.go +++ b/cmd/mesh-controller/adopting_test.go @@ -60,6 +60,21 @@ func anAdoptedAnchor(t *testing.T) (*stores, *[]string) { // reportsHolding has the anchor report, on what it was last sent, holding what is given. func reportsHolding(t *testing.T, open *stores, held ...link.Held) { + t.Helper() + reportsReaching(t, open, []link.Reach{ + {Protocol: "tcp", Address: "0.0.0.0", Port: 22, By: "sshd"}, + {Protocol: "tcp", Address: "0.0.0.0", Port: 8080, By: "hello-web", Published: true, + ContainerPort: 80}, + {Protocol: "tcp", Address: "0.0.0.0", Port: 5000, By: "predecessor-registry", + Published: true, ContainerPort: 5000}, + {Protocol: "tcp", Address: "127.0.0.1", Port: 15672, By: "mesh-broker", + Published: true, ContainerPort: 15672}, + }, held...) +} + +// reportsReaching has the anchor report, on what it was last sent, what is reachable on it and +// holding what is given. +func reportsReaching(t *testing.T, open *stores, reachable []link.Reach, held ...link.Held) { t.Helper() ctx := t.Context() body, err := composed(t, open, "anchor").Body() @@ -75,16 +90,7 @@ func reportsHolding(t *testing.T, open *stores, held ...link.Held) { } if err := (link.Enrolment{Inventory: open.inventory}).Heard(ctx, link.Report{ Node: "anchor", Applied: []string{"hello-web.x"}, Declared: digestOf(body), - Firewall: "ufw", Held: held, - Reachable: []link.Reach{ - {Protocol: "tcp", Address: "0.0.0.0", Port: 22, By: "sshd"}, - {Protocol: "tcp", Address: "0.0.0.0", Port: 8080, By: "hello-web", Published: true, - ContainerPort: 80}, - {Protocol: "tcp", Address: "0.0.0.0", Port: 5000, By: "predecessor-registry", - Published: true, ContainerPort: 5000}, - {Protocol: "tcp", Address: "127.0.0.1", Port: 15672, By: "mesh-broker", - Published: true, ContainerPort: 15672}, - }, + Firewall: "ufw", Held: held, Reachable: reachable, }); err != nil { t.Fatal(err) } @@ -159,7 +165,9 @@ func TestConvergingPreviewsThenChangesAndAdoptingKeepsWhatWasTaken(t *testing.T) "tcp/8080 hello-web (published, container port 80)", "declared by hello-web (from anywhere)", "WILL CLOSE — no module assigned here declares it", - "ssh is never closed", + // The anchor faces inward and is on the private network: the derived filter admits ssh + // from the mesh only. + "WILL CLOSE to everything outside the private network — ssh stays open from the mesh", "notes\n replacing the found file /etc/notes.conf (original kept at", "assigns nftables", "the found firewall (ufw) is disabled, never flushed", @@ -253,3 +261,46 @@ func TestTheApiRefusesTheFlipInTheCommandLinesWords(t *testing.T) { t.Fatalf("a take naming no module got %d", got.Code) } } + +// novox/hq ADR 0100: the preview says what the derived filter does, rendered as it is rendered. On +// a machine that faces inward, ssh is admitted from the private network only, and a port a module +// admits from the mesh only closes to everything outside it: both are said to close. +func TestThePreviewSaysWhatNarrowsToTheMeshCloses(t *testing.T) { + open, _ := anAdoptedAnchor(t) + ctx := t.Context() + register(t, open, catalogue.Manifest{Module: "store", Version: "1", + Listens: []catalogue.Listening{{Port: 5432, From: catalogue.FromMesh}}}) + if _, err := assign(ctx, open, "anchor", "store"); err != nil { + t.Fatal(err) + } + reportsReaching(t, open, []link.Reach{ + {Protocol: "tcp", Address: "0.0.0.0", Port: 22, By: "sshd"}, + {Protocol: "tcp", Address: "0.0.0.0", Port: 5432, By: "postgres"}, + {Protocol: "tcp", Address: "10.77.0.1", Port: 5432, By: "postgres"}, + {Protocol: "tcp", Address: "0.0.0.0", Port: 8080, By: "hello-web", Published: true, + ContainerPort: 80}, + }) + preview, err := converge(ctx, open, "anchor", false, "") + if err != nil { + t.Fatal(err) + } + lines := map[string]string{} + for _, line := range strings.Split(preview, "\n") { + fields := strings.Fields(line) + if len(fields) > 1 && strings.HasPrefix(fields[0], "tcp/") { + lines[fields[0]+" "+fields[1]] += line + "\n" + } + } + if got := lines["tcp/22 sshd"]; !strings.Contains(got, "WILL CLOSE to everything outside "+ + "the private network") { + t.Errorf("ssh on an inward machine is not said to close outside the mesh:\n%s", preview) + } + store := lines["tcp/5432 postgres"] + if strings.Count(store, "WILL CLOSE to everything outside the private network") != 1 || + !strings.Contains(store, "declared by store (from mesh)") { + t.Errorf("the store's narrowing is not said to close, or its mesh address is:\n%s", preview) + } + if got := lines["tcp/8080 hello-web"]; !strings.Contains(got, "declared by hello-web (from anywhere)") { + t.Errorf("a port open to everywhere is not said to stay:\n%s", preview) + } +} diff --git a/cmd/mesh-controller/adoption.go b/cmd/mesh-controller/adoption.go index bd03494..152998c 100644 --- a/cmd/mesh-controller/adoption.go +++ b/cmd/mesh-controller/adoption.go @@ -217,7 +217,9 @@ func converge(ctx context.Context, open *stores, node string, yes bool, filter s if err != nil { return "", err } - preview := previewOf(node, reported, rules, with.Foundation, plan, taken, filter, runs[filter]) + derived := derivedFilter{rules: rules, foundation: with.Foundation, mesh: with.Mesh, + outward: plan.PublicDomain != ""} + preview := previewOf(node, reported, derived, plan, taken, filter, runs[filter]) if !yes { return preview + fmt.Sprintf("\n\nNothing has changed. Run `converge %s --yes` to do it.", node), nil } @@ -252,7 +254,7 @@ func converge(ctx context.Context, open *stores, node string, yes bool, filter s } // previewOf is what converging a node will change, before it changes it. -func previewOf(node string, reported inventory.Adoption, rules []catalogue.Rule, foundation []int, +func previewOf(node string, reported inventory.Adoption, derived derivedFilter, plan catalogue.Resolution, taken []string, filter string, filterAssigned bool) string { var b strings.Builder fmt.Fprintf(&b, "converging %s\n", node) @@ -269,7 +271,7 @@ func previewOf(node string, reported inventory.Adoption, rules []catalogue.Rule, if r.Published { what += fmt.Sprintf(" (published, container port %d)", r.ContainerPort) } - fmt.Fprintf(&b, " %-44s %s\n", what, fate(r, rules, foundation)) + fmt.Fprintf(&b, " %-44s %s\n", what, derived.fate(r)) } if len(reported.Reachable) == 0 { b.WriteString(" nothing reported\n") @@ -317,26 +319,56 @@ func previewOf(node string, reported inventory.Adoption, rules []catalogue.Rule, return strings.TrimRight(b.String(), "\n") } +// derivedFilter is what the filter the flip loads is rendered from, as AsNftables renders it. +type derivedFilter struct { + rules []catalogue.Rule + foundation []int + // mesh is every address on the private network; outward says the machine faces outside. + mesh []string + outward bool +} + +// closesOutside is what a narrowing from everywhere to the private network is called: it closes. +const closesOutside = "WILL CLOSE to everything outside the private network" + // fate is what the derived filter does to one reachable thing: which module declares it and from -// where, or that it will close. -func fate(r inventory.Reach, rules []catalogue.Rule, foundation []int) string { +// where, or that it will close — wholly, or to everything outside the private network. Rendered +// exactly as AsNftables admits it, ssh included. +func (d derivedFilter) fate(r inventory.Reach) string { + // Bound to an address on the private network, it was never reachable from outside it, so + // admitting it from the mesh narrows nothing. + onMesh := slices.Contains(d.mesh, strings.Trim(r.Address, "[]")) if r.Protocol == "tcp" && r.Port == catalogue.SSHPort { - return "stays open — ssh is never closed" + // From everywhere only when the machine faces outward or the mesh has no addresses to + // narrow it to; otherwise from the private network only. + if d.outward || len(d.mesh) == 0 || onMesh { + return "stays open — ssh is never closed" + } + return closesOutside + " — ssh stays open from the mesh, never closed there" } - for _, port := range foundation { + for _, port := range d.foundation { if r.Protocol == "tcp" && r.Port == port { return "stays open — the mesh's own, from anywhere" } } - for _, rule := range rules { + for _, rule := range d.rules { if rule.Port != r.Port || rule.Protocol != r.Protocol { continue } - if rule.From == catalogue.FromMachine { - return fmt.Sprintf("WILL CLOSE to the network — declared by %s for this machine only", - strings.Join(rule.Because, ", ")) + by := strings.Join(rule.Because, ", ") + switch rule.From { + case catalogue.FromMachine: + return fmt.Sprintf("WILL CLOSE to the network — declared by %s for this machine only", by) + case catalogue.FromMesh: + if len(d.mesh) == 0 { + return fmt.Sprintf("WILL CLOSE — declared by %s from the mesh, and this node "+ + "knows no mesh addresses", by) + } + if !onMesh { + return fmt.Sprintf("%s — declared by %s from the mesh only", closesOutside, by) + } } - return fmt.Sprintf("declared by %s (from %s)", strings.Join(rule.Because, ", "), rule.From) + return fmt.Sprintf("declared by %s (from %s)", by, rule.From) } return "WILL CLOSE — no module assigned here declares it" }