package compiler import "testing" import "git.unkin.net/unkin/tomswallapi/internal/model" func baseInput() Input { return Input{ Generation: 7, Settings: model.Settings{AddressFamily: "inet", LogLevel: "info", IPForwarding: true, TableName: "tomswall", DefaultResolver: []string{"10.0.0.53"}}, Zones: map[string]model.Zone{ "zone-a": {Name: "zone-a", Type: "ip", Subnets: []string{"10.1.0.0/24"}}, "net": {Name: "net", Type: "ip"}, // no subnets: internet-facing }, Groups: map[string]model.AddressGroup{ "cloudflare": {Name: "cloudflare", Type: model.GroupASN, Members: []string{"13335"}, Refresh: "24h"}, }, PortGroups: map[string]model.PortGroup{ "https": {Name: "https", Proto: "tcp", Ports: []string{"443"}}, }, Rules: []model.Rule{ {ID: 1, Action: "accept", Source: []string{"zone-a"}, Dest: []string{"net:+asn_cloudflare"}, PortGroup: "https"}, }, } } func TestRenderFirewallEnforcesAndEmitsSet(t *testing.T) { in := baseInput() in.Device = model.Device{Name: "fw-a", Class: model.ClassFirewall} in.Bindings = []model.Binding{{Device: "fw-a", Zone: "zone-a", Interfaces: []string{"eth1"}}} cfg, err := Render(in) if err != nil { t.Fatalf("Render: %v", err) } if !cfg.Enforcing { t.Fatal("firewall should enforce") } if cfg.Generation != 7 { t.Errorf("generation = %d, want 7", cfg.Generation) } if len(cfg.Rules) != 1 { t.Fatalf("want 1 rule, got %d", len(cfg.Rules)) } r := cfg.Rules[0] // Interface-agnostic: source resolves to zone-a's subnets, no iif/oif. if len(r.Source) != 1 || r.Source[0].Zone != "zone-a" || len(r.Source[0].Subnets) != 1 || r.Source[0].Subnets[0] != "10.1.0.0/24" { t.Errorf("unexpected source match: %+v", r.Source) } // Dest is the no-subnet `net` zone gated by the asn set. if len(r.Dest) != 1 || r.Dest[0].Zone != "net" || r.Dest[0].Set != "asn_cloudflare" { t.Errorf("unexpected dest match: %+v", r.Dest) } if len(r.Dest[0].Subnets) != 0 { t.Errorf("net should carry no subnets, got %v", r.Dest[0].Subnets) } if r.Proto != "tcp" || len(r.Ports) != 1 || r.Ports[0] != "443" { t.Errorf("portgroup not resolved: proto=%q ports=%v", r.Proto, r.Ports) } // The referenced asn group must be emitted as a set carrying its source ASNs. if len(cfg.Sets) != 1 { t.Fatalf("want 1 set, got %d", len(cfg.Sets)) } set := cfg.Sets[0] if set.Name != "asn_cloudflare" || set.Kind != model.GroupASN || len(set.ASNs) != 1 || set.ASNs[0] != "13335" { t.Errorf("unexpected set: %+v", set) } if set.Members != nil { t.Errorf("asn set should not carry inline members before expansion, got %v", set.Members) } // Binding surfaced for the agent. if got := cfg.Bindings["zone-a"]; len(got) != 1 || got[0] != "eth1" { t.Errorf("binding not surfaced: %v", cfg.Bindings) } } func TestRenderTransparentRouterHasNoRules(t *testing.T) { in := baseInput() in.Device = model.Device{Name: "rt1", Class: model.ClassRouter, Fabric: "core"} in.Fabric = &model.Fabric{Name: "core", EnforceOnRouters: false} cfg, err := Render(in) if err != nil { t.Fatalf("Render: %v", err) } if cfg.Enforcing { t.Fatal("transparent router should not enforce") } if len(cfg.Rules) != 0 || len(cfg.Sets) != 0 { t.Errorf("transparent router should emit no rules/sets, got %d rules %d sets", len(cfg.Rules), len(cfg.Sets)) } } func TestRenderEnforcingRouter(t *testing.T) { in := baseInput() in.Device = model.Device{Name: "rt1", Class: model.ClassRouter, Fabric: "core"} in.Fabric = &model.Fabric{Name: "core", EnforceOnRouters: true} cfg, err := Render(in) if err != nil { t.Fatalf("Render: %v", err) } if !cfg.Enforcing || len(cfg.Rules) != 1 { t.Errorf("defense-in-depth router should enforce the rule: enforcing=%v rules=%d", cfg.Enforcing, len(cfg.Rules)) } } func TestRenderUnknownGroupIsError(t *testing.T) { in := baseInput() in.Device = model.Device{Name: "fw-a", Class: model.ClassFirewall} in.Rules = []model.Rule{{ID: 9, Action: "accept", Source: []string{"zone-a"}, Dest: []string{"net:+nope"}}} if _, err := Render(in); err == nil { t.Fatal("expected error for unknown address group") } } func TestEffectiveResolverPrefersDevice(t *testing.T) { in := baseInput() in.Device = model.Device{Name: "fw-a", Class: model.ClassFirewall, Resolver: []string{"10.9.9.9"}} cfg, err := Render(in) if err != nil { t.Fatalf("Render: %v", err) } if len(cfg.Resolver) != 1 || cfg.Resolver[0] != "10.9.9.9" { t.Errorf("device resolver should win: %v", cfg.Resolver) } }