Read a machine's iptables rules when it has no nft, instead of calling it unfiltered (hq ADR 0100)

This commit is contained in:
2026-09-22 19:59:22 +02:00
parent c7ff0b9026
commit b4c21b4f67
2 changed files with 46 additions and 3 deletions
+31
View File
@@ -119,11 +119,20 @@ type fakeUFW struct {
// after it is disabled; empty is a machine without iptables. forward is a policy set since.
iptablesActive, iptablesInactive string
forward string
// noNft is a machine with no nft binary; iptablesRules is what `iptables -S` prints there.
noNft bool
iptablesRules string
}
// iptables answers `iptables -S FORWARD` from the captured output for ufw's state, and records
// a forward policy set with -P.
func (f *fakeUFW) iptables(name string, args []string) (string, error) {
if f.iptablesRules != "" && len(args) == 1 && args[0] == "-S" {
if name == "ip6tables" {
return "", nil
}
return f.iptablesRules, nil
}
if f.iptablesActive == "" {
return "", &exec.Error{Name: name, Err: exec.ErrNotFound}
}
@@ -189,6 +198,9 @@ func (f *fakeUFW) run(_ context.Context, name string, args ...string) (string, e
}
return "", &exec.Error{Name: name, Err: exec.ErrNotFound}
case "nft":
if f.noNft {
return "", &exec.Error{Name: name, Err: exec.ErrNotFound}
}
return f.ruleset, nil
case "iptables-legacy", "ip6tables-legacy":
return "", &exec.Error{Name: name, Err: exec.ErrNotFound}
@@ -756,3 +768,22 @@ func TestIncomingIsUfwsDefaultDirection(t *testing.T) {
t.Error("an incoming refusal ufw would merge was not refused")
}
}
func TestAMachineWithIptablesRulesAndNoNftIsNotReadAsUnfiltered(t *testing.T) {
// nft is not installed, and iptables-nft holds a firewall of somebody's. Read as "nothing
// filters here" the mesh would adopt it, open nothing, and be unreachable (novox/hq ADR 0100).
rules := "-P INPUT DROP\n-P FORWARD DROP\n-P OUTPUT ACCEPT\n-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT\n"
f := &fakeUFW{noNft: true, iptablesRules: rules}
kind, what, err := Detect(context.Background(), f.run)
if err != nil {
t.Fatal(err)
}
if kind != Unsupported {
t.Errorf("a machine filtered by iptables with no nft read as %s (%s)", kind, what)
}
// And a machine with nothing but the runtime's own rules and no nft is still unfiltered.
docker := "-P INPUT ACCEPT\n-P FORWARD DROP\n-P OUTPUT ACCEPT\n-N DOCKER\n-A DOCKER -i docker0 -j DROP\n"
if kind, _, err := Detect(context.Background(), (&fakeUFW{noNft: true, iptablesRules: docker}).run); err != nil || kind != None {
t.Errorf("a machine with only the runtime's rules read as %s: %v", kind, err)
}
}