Preview ssh as the derived filter admits it, and say a narrowing to the private network closes (hq ADR 0100)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user