Exclude sub-zone hosts from wildcard parent interfaces
This commit is contained in:
@@ -473,6 +473,20 @@ func TestValidateHosts(t *testing.T) {
|
||||
},
|
||||
wantErr: "interface \"eth99\" not defined in interfaces",
|
||||
},
|
||||
{
|
||||
name: "host interface matched by wildcard",
|
||||
zones: map[string]Zone{
|
||||
"fw": {Type: ZoneFirewall},
|
||||
"net": {Type: ZoneIP},
|
||||
"lan": {Type: ZoneIP, Parents: []string{"net"}},
|
||||
},
|
||||
interfaces: []Interface{
|
||||
{Zone: "net", Interface: "enp+"},
|
||||
},
|
||||
hosts: []Host{
|
||||
{Zone: "lan", Interface: "enp2s0", Addresses: []string{"192.0.2.0/24"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "zone not defined",
|
||||
zones: map[string]Zone{
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Host struct {
|
||||
@@ -44,7 +45,8 @@ func (c *Config) validateHosts() error {
|
||||
|
||||
ifaceFound := false
|
||||
for _, iface := range c.Interfaces {
|
||||
if iface.Interface == h.Interface || iface.PhysicalName() == h.Interface {
|
||||
prefix, wild := strings.CutSuffix(iface.PhysicalName(), "+")
|
||||
if iface.Interface == h.Interface || iface.PhysicalName() == h.Interface || (wild && strings.HasPrefix(h.Interface, prefix)) {
|
||||
ifaceFound = true
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1351,11 +1351,11 @@ func (c *Compiler) resolveZone(zone, addr string) []zoneMatch {
|
||||
return nil
|
||||
}
|
||||
|
||||
// subZoneHosts lists the host addresses on iface that belong to sub-zones of zone, and those
|
||||
// sub-zone hosts' exclusions, which fall back to zone.
|
||||
// subZoneHosts lists the host addresses on interfaces overlapping iface that belong to sub-zones of
|
||||
// zone, and those sub-zone hosts' exclusions, which fall back to zone.
|
||||
func (c *Compiler) subZoneHosts(zone, iface string) (sub, back []string) {
|
||||
for _, h := range c.cfg.Hosts {
|
||||
if h.Interface == iface && c.cfg.IsSubZone(h.Zone, zone) {
|
||||
if ifacesOverlap(h.Interface, iface) && c.cfg.IsSubZone(h.Zone, zone) {
|
||||
sub = append(sub, h.Addresses...)
|
||||
back = append(back, h.Exclusions...)
|
||||
}
|
||||
@@ -1363,6 +1363,21 @@ func (c *Compiler) subZoneHosts(zone, iface string) (sub, back []string) {
|
||||
return sub, back
|
||||
}
|
||||
|
||||
// ifacesOverlap reports whether two interface names (a "+" suffix being a prefix wildcard) can match the same interface.
|
||||
func ifacesOverlap(a, b string) bool {
|
||||
pa, wa := strings.CutSuffix(a, "+")
|
||||
pb, wb := strings.CutSuffix(b, "+")
|
||||
switch {
|
||||
case wa && wb:
|
||||
return strings.HasPrefix(pa, pb) || strings.HasPrefix(pb, pa)
|
||||
case wa:
|
||||
return strings.HasPrefix(pb, pa)
|
||||
case wb:
|
||||
return strings.HasPrefix(pa, pb)
|
||||
}
|
||||
return a == b
|
||||
}
|
||||
|
||||
// addrsOverlap reports whether a host address can match a rule address; unparsable or negated rule addresses keep the host.
|
||||
func addrsOverlap(host, rule string) bool {
|
||||
if rule == "" || strings.HasPrefix(rule, "!") {
|
||||
|
||||
@@ -3663,3 +3663,59 @@ func TestCompile_HostsRouteBack(t *testing.T) {
|
||||
t.Errorf("routeback hosts lan lan policy = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompile_WildcardParentExcludesSubZoneHosts(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
Settings: config.Settings{TableName: "test", AddressFamily: config.FamilyINET},
|
||||
Zones: map[string]config.Zone{
|
||||
"fw": {Type: config.ZoneFirewall}, "net": {Type: config.ZoneIP},
|
||||
"lan": {Type: config.ZoneIP, Parents: []string{"net"}}, "lxd": {Type: config.ZoneIP},
|
||||
},
|
||||
Interfaces: []config.Interface{
|
||||
{Zone: "net", Interface: "enp+"}, {Zone: "net", Interface: "wlo1"}, {Zone: "lxd", Interface: "lxdbr0"},
|
||||
},
|
||||
Hosts: []config.Host{
|
||||
{Zone: "lan", Interface: "enp2s0", Addresses: []string{"192.0.2.0/24"}},
|
||||
{Zone: "lan", Interface: "wlo1", Addresses: []string{"198.51.100.0/24"}},
|
||||
},
|
||||
Policy: []config.Policy{
|
||||
{Source: "lxd", Dest: "net", Action: config.PolicyAccept},
|
||||
{Source: "net", Dest: "all", Action: config.PolicyDrop},
|
||||
{Source: "all", Dest: "all", Action: config.PolicyReject},
|
||||
},
|
||||
PortGroups: make(map[string]config.PortGroup),
|
||||
}
|
||||
state := mustCompile(t, cfg)
|
||||
want := []string{
|
||||
"iif=lxdbr0 oif=enp ip4 !daddr=192.0.2.0/24", "iif=lxdbr0 oif=enp ip6",
|
||||
"iif=lxdbr0 oif=wlo1 ip4 !daddr=198.51.100.0/24", "iif=lxdbr0 oif=wlo1 ip6",
|
||||
}
|
||||
if got := describeTagged(state, "forward", "policy:0"); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("lxd->net accept = %q, want %q", got, want)
|
||||
}
|
||||
want = []string{
|
||||
"iif=enp ip4 !saddr=192.0.2.0/24", "iif=enp ip6",
|
||||
"iif=wlo1 ip4 !saddr=198.51.100.0/24", "iif=wlo1 ip6",
|
||||
}
|
||||
if got := describeTagged(state, "input", "policy:1"); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("net->fw drop = %q, want %q", got, want)
|
||||
}
|
||||
if got := describeTagged(state, "input", "policy:2"); len(got) == 0 || got[0] != "iif=enp2s0 ip4 saddr=192.0.2.0/24" {
|
||||
t.Errorf("lan->fw reject = %q, want lan on enp2s0 first", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfacesOverlap(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
a, b string
|
||||
want bool
|
||||
}{
|
||||
{"enp2s0", "enp2s0", true}, {"enp2s0", "enp3s0", false},
|
||||
{"enp+", "enp2s0", true}, {"enp2s0", "enp+", true}, {"enp+", "wlo1", false},
|
||||
{"en+", "enp+", true}, {"enp+", "eno+", false}, {"enp", "enp+", true},
|
||||
} {
|
||||
if got := ifacesOverlap(tt.a, tt.b); got != tt.want {
|
||||
t.Errorf("ifacesOverlap(%q, %q) = %v, want %v", tt.a, tt.b, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user