package firewall import ( "os" "testing" ) // The mesh's own ban list is a ban wherever it hangs (novox/hq ADR 0186). // // Captured from the home server after the intrusion prevention had banned four addresses: its ban // chain is jumped to from INPUT, whose policy accepts, and from the container runtime's user chain, // which hangs off a FORWARD the runtime set to DROP. Requiring every path to come from an accepting // built-in made the machine report "NOT the mesh alone" about a chain the mesh had just written. func TestTheMeshsOwnBanChainIsABanBehindADroppingForward(t *testing.T) { legacy, err := os.ReadFile("testdata/home-server-bans-S.txt") if err != nil { t.Fatal(err) } filters := Filters("", map[string]string{"iptables-legacy": string(legacy)}, false) var ban, other []string for _, f := range filters { switch f.Owner { case OwnerBan: ban = append(ban, f.Where) case OwnerOther: other = append(other, f.Where) } } if len(other) > 0 { t.Errorf("the machine reports %v as rule sets the mesh did not write", other) } found := false for _, w := range ban { if w == "chain f2b-route-proxy (iptables-legacy)" { found = true } } if !found { t.Errorf("the intrusion prevention's own chain was not read as a ban; bans were %v", ban) } if !Alone(filters) { t.Error("a machine filtered by the mesh and its own bans does not read as the mesh alone") } } // A chain that accepts anything is doing more than banning, and is still not a ban — which is what // keeps a predecessor's allow-and-drop chain classified as something the operator must look at. func TestAChainThatAcceptsIsNotABan(t *testing.T) { rules := "-P INPUT ACCEPT\n" + "-A INPUT -j HAL-MESH-ONLY\n" + "-N HAL-MESH-ONLY\n" + "-A HAL-MESH-ONLY -s 10.0.0.0/8 -j ACCEPT\n" + "-A HAL-MESH-ONLY -s 203.0.113.7/32 -j DROP\n" for _, f := range Filters("", map[string]string{"iptables-legacy": rules}, false) { if f.Where == "chain HAL-MESH-ONLY (iptables-legacy)" && f.Owner != OwnerOther { t.Errorf("a chain that accepts was classified as %s", f.Owner) } } }