From ecc349cb6fc8bfc17146c16138e6c15de165c8ca Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Fri, 9 Oct 2026 22:48:33 +1100 Subject: [PATCH] Skip fw->fw policies and treat dest-side + as intra-zone override --- internal/nftables/compiler.go | 2 +- internal/nftables/compiler_test.go | 36 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/internal/nftables/compiler.go b/internal/nftables/compiler.go index d1420f2..9a28ee6 100644 --- a/internal/nftables/compiler.go +++ b/internal/nftables/compiler.go @@ -868,7 +868,7 @@ func (c *Compiler) compilePolicies(state *FirewallState) error { for _, sz := range srcZones { for _, dz := range dstZones { if sz == dz { - if !explicitIntra && !strings.HasSuffix(pol.Source, "+") { + if sz == fwZone || (!explicitIntra && !strings.HasSuffix(pol.Source, "+") && !strings.HasSuffix(pol.Dest, "+")) { continue } overridden[sz] = true diff --git a/internal/nftables/compiler_test.go b/internal/nftables/compiler_test.go index b1ad0be..4371c1f 100644 --- a/internal/nftables/compiler_test.go +++ b/internal/nftables/compiler_test.go @@ -3388,3 +3388,39 @@ func TestCompile_IntraZoneMultiInterface(t *testing.T) { }) } } + +func TestCompile_FirewallSelfPolicySkipped(t *testing.T) { + for _, action := range []config.PolicyAction{config.PolicyAccept, config.PolicyDrop} { + t.Run(string(action), func(t *testing.T) { + state := mustCompile(t, listCfg(func(c *config.Config) { + c.Policy = []config.Policy{ + {Source: "fw", Dest: "fw", Action: action}, + {Source: "net", Dest: "fw", Action: config.PolicyDrop, Log: "info"}, + } + })) + for _, chain := range []string{"input", "output", "forward"} { + if got := taggedRules(state, chain, "policy:0"); len(got) != 0 { + t.Errorf("fw->fw emitted %d rules in %s", len(got), chain) + } + } + got := taggedRules(state, "input", "policy:1") + if len(got) != 1 || describeRule(got[0]) != "iif=eth0" { + t.Errorf("net->fw input rules = %d, want one scoped to eth0", len(got)) + } + }) + } +} + +func TestCompile_DestPlusOverridesIntraZone(t *testing.T) { + state := mustCompile(t, listCfg(func(c *config.Config) { + c.Zones["lxd"] = config.Zone{Type: config.ZoneIP} + c.Interfaces = append(c.Interfaces, config.Interface{Zone: "lxd", Interface: "lxdbr0"}, config.Interface{Zone: "lxd", Interface: "docker0"}) + c.Policy = []config.Policy{{Source: "lxd", Dest: "all+", Action: config.PolicyDrop}} + })) + if got := taggedRules(state, "forward", "intra:lxd"); len(got) != 0 { + t.Errorf("lxd all+ must override implicit intra-zone accept, got %d rules", len(got)) + } + if got := taggedRules(state, "forward", "policy:0"); len(got) == 0 { + t.Error("lxd all+ emitted no forward rules") + } +}