Files
wild-central/internal/nftables/manager_test.go
Paul Payne b45c0d6a81 fix: Set nftables input chain priority to -1 for CrowdSec compatibility
CrowdSec's blacklist chain runs at priority 0. Our loopback accept rule
needs to run first so local traffic isn't blocked. Also cleans up .bak
files after successful SafeApply.
2026-10-15 07:23:48 +00:00

159 lines
4.8 KiB
Go

package nftables
import (
"strings"
"testing"
)
func TestNewManager_DefaultPath(t *testing.T) {
m := NewManager("")
if m.GetRulesPath() != defaultRulesPath {
t.Errorf("got %q, want %q", m.GetRulesPath(), defaultRulesPath)
}
}
func TestNewManager_CustomPath(t *testing.T) {
m := NewManager("/tmp/wild-central.nft")
if m.GetRulesPath() != "/tmp/wild-central.nft" {
t.Errorf("got %q, want /tmp/wild-central.nft", m.GetRulesPath())
}
}
func TestPortsToStr(t *testing.T) {
cases := []struct {
ports []int
want string
}{
{[]int{80, 443, 8404}, "80, 443, 8404"},
{[]int{443}, "443"},
{[]int{}, ""},
}
for _, c := range cases {
if got := portsToStr(c.ports); got != c.want {
t.Errorf("portsToStr(%v) = %q, want %q", c.ports, got, c.want)
}
}
}
func TestGenerate_AlwaysIncludesStructure(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{80, 443}, nil, nil, "")
for _, want := range []string{
"table inet wild-central",
"set allowed_tcp_ports",
"chain input",
"type filter hook input priority -1; policy accept;",
"iif \"lo\" accept",
"ct state { established, related } accept",
} {
if !strings.Contains(out, want) {
t.Errorf("expected %q in output, got:\n%s", want, out)
}
}
}
func TestGenerate_WithPorts_IncludesElements(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{80, 443, 8404}, nil, nil, "")
if !strings.Contains(out, "elements = { 80, 443, 8404 }") {
t.Errorf("expected elements line with ports, got:\n%s", out)
}
}
func TestGenerate_NoPorts_TCPSetHasNoElements(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{}, nil, nil, "")
// TCP set should have no elements when no ports given
if strings.Contains(out, "allowed_tcp_ports") && strings.Contains(out, "elements") {
// Check that the elements line is not inside the TCP block
tcpBlock := out[strings.Index(out, "set allowed_tcp_ports"):]
tcpBlock = tcpBlock[:strings.Index(tcpBlock, "}")+1]
if strings.Contains(tcpBlock, "elements") {
t.Errorf("expected no elements in allowed_tcp_ports block when no ports, got:\n%s", tcpBlock)
}
}
// UDP set always has DNS/DHCP elements
if !strings.Contains(out, "53, 67, 68") {
t.Errorf("expected DNS/DHCP elements in allowed_udp_ports even with no TCP ports, got:\n%s", out)
}
}
func TestGenerate_ExtraPortsMerged(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{80, 443}, []int{22, 443}, nil, "") // 443 deduplicated
if !strings.Contains(out, "22") {
t.Errorf("expected extra port 22 in output, got:\n%s", out)
}
// 443 should appear only once
count := strings.Count(out, "443")
if count != 1 {
t.Errorf("expected 443 exactly once, got %d times in:\n%s", count, out)
}
}
func TestGenerate_NoWANInterface_NoDropRules(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{80, 443}, nil, nil, "")
if strings.Contains(out, "drop") {
t.Errorf("expected no drop rules without WAN interface, got:\n%s", out)
}
}
func TestGenerate_WithWANInterface_IncludesDropRules(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{80, 443}, nil, nil, "eth0")
if !strings.Contains(out, `iif "eth0"`) {
t.Errorf("expected WAN interface in rules, got:\n%s", out)
}
if !strings.Contains(out, "drop") {
t.Errorf("expected drop rule with WAN interface, got:\n%s", out)
}
if !strings.Contains(out, "@allowed_tcp_ports") {
t.Errorf("expected reference to allowed_tcp_ports set, got:\n%s", out)
}
if !strings.Contains(out, "@allowed_udp_ports") {
t.Errorf("expected reference to allowed_udp_ports set, got:\n%s", out)
}
// DNS and DHCP UDP ports should be in the allowed_udp_ports set
if !strings.Contains(out, "53, 67, 68") {
t.Errorf("expected DNS/DHCP UDP ports in allowed_udp_ports set, got:\n%s", out)
}
}
func TestGenerate_AlwaysIncludesUDPSet(t *testing.T) {
m := NewManager("")
// No WAN interface — permissive mode, but UDP set should still be visible
out := m.Generate([]int{80, 443}, nil, []int{51820}, "")
if !strings.Contains(out, "set allowed_udp_ports") {
t.Errorf("expected allowed_udp_ports set even without WAN interface, got:\n%s", out)
}
if !strings.Contains(out, "51820") {
t.Errorf("expected extra UDP port 51820 in allowed_udp_ports set, got:\n%s", out)
}
// No drop rules without WAN interface
if strings.Contains(out, "drop") {
t.Errorf("expected no drop rules without WAN interface, got:\n%s", out)
}
}
func TestGenerate_ExtraUDPPorts(t *testing.T) {
m := NewManager("")
out := m.Generate([]int{80, 443}, nil, []int{5353}, "eth0")
// User UDP port should appear in the UDP exception list
if !strings.Contains(out, "5353") {
t.Errorf("expected extra UDP port 5353 in output, got:\n%s", out)
}
// Hardcoded DNS/DHCP should still be there
if !strings.Contains(out, "53, 67, 68") {
t.Errorf("expected DNS/DHCP UDP ports still present, got:\n%s", out)
}
}