Files
pilens/internal/netgen/netgen_test.go
T
Andy Dixon 5dbf85f84e Phase 4: authorised HTTPS interception (Mode B)
Toggleable transparent interception via mitmdump in a venv. Own RSA CA generated
in-app, downloadable (public cert only, key never leaves the box), with per-OS
install guide and fingerprint. nftables redirect accepts only dnat'd connections
(never an open proxy). HTTP Explorer with headers and opt-in body viewers (JSON/
text/hex/image, sandboxed). Honest status: pinned/untrusting = reported errors,
excluded hosts pass through, bodies off by default. Helper op + systemd unit +
installer venv build + boot restore.
2026-10-10 08:05:42 +01:00

108 lines
3.4 KiB
Go

package netgen
import (
"strings"
"testing"
"pilens.dev/pilens/internal/config"
)
func testNet() config.Network {
n := config.DefaultNetwork()
n.Management.Interfaces = []string{"eth0"}
n.Management.AllowedSubnets = []string{"10.1.0.0/24"}
n.Uplink = "eth0"
n.AP.Interface = "wlan0"
n.AP.Passphrase = `pass\word 123`
n.LAN.Reservations = []config.Reservation{{MAC: "AA:BB:CC:00:11:22", IP: "192.168.50.20", Hostname: "phone"}}
n.LAN.DNSOverrides = []config.DNSOverride{{Name: "Api.Test.Local", IP: "192.168.50.1"}}
return n
}
func TestValidate(t *testing.T) {
n := testNet()
if err := n.Validate(true); err != nil {
t.Fatal(err)
}
bad := []func(*config.Network){
func(n *config.Network) { n.AP.Interface = "eth0" },
func(n *config.Network) { n.AP.SSID = "a;b" },
func(n *config.Network) { n.AP.SSID = "bad\nname" },
func(n *config.Network) { n.AP.Passphrase = "short" },
func(n *config.Network) { n.AP.Channel = 36 },
func(n *config.Network) { n.LAN.DHCPStart = "192.168.50.1" },
func(n *config.Network) { n.LAN.DHCPEnd = "192.168.51.10" },
func(n *config.Network) { n.Management.AllowedSubnets = []string{"192.168.50.0/25"} },
func(n *config.Network) { n.LAN.DNSOverrides[0].Name = "x\nserver=1.2.3.4" },
func(n *config.Network) { n.Uplink = "eth0; rm -rf" },
func(n *config.Network) { n.Forwarding.Mode = "open" },
func(n *config.Network) { n.LAN.Address = "192.168.50.0/24" },
}
for i, f := range bad {
c := testNet()
f(&c)
if c.Validate(true) == nil {
t.Errorf("case %d: expected validation error", i)
}
}
}
func TestKeyfile(t *testing.T) {
k := NMKeyfile(testNet())
for _, want := range []string{"mode=ap", "interface-name=wlan0", `psk=pass\\word 123`, "key-mgmt=wpa-psk", "method=disabled", "address1=192.168.50.1/24", "ssid=PiLens-Lab"} {
if !strings.Contains(k, want) {
t.Errorf("keyfile missing %q", want)
}
}
}
func TestDnsmasq(t *testing.T) {
d := DnsmasqConf(testNet())
for _, want := range []string{"interface=wlan0", "dhcp-range=192.168.50.100,192.168.50.199,255.255.255.0,720m", "dhcp-host=aa:bb:cc:00:11:22,192.168.50.20,phone", "host-record=api.test.local,192.168.50.1", "option:router,192.168.50.1"} {
if !strings.Contains(d, want) {
t.Errorf("dnsmasq missing %q\n%s", want, d)
}
}
}
func TestNftables(t *testing.T) {
s := Nftables(testNet(), nil)
for _, want := range []string{
"delete table inet pilens",
`iifname "wlan0" jump ap_forward`,
"meta nfproto ipv6 counter drop",
`ip saddr 192.168.50.0/24 oifname "eth0" masquerade`,
"10.1.0.0/24",
`tcp dport 8443 counter drop`,
} {
if !strings.Contains(s, want) {
t.Errorf("ruleset missing %q", want)
}
}
if strings.Contains(s, "flush ruleset") {
t.Error("must never flush the whole ruleset")
}
n := testNet()
n.Forwarding.Mode = "deny"
if !strings.Contains(Nftables(n, nil), `"forwarding disabled"`) {
t.Error("deny mode must drop forwarded traffic")
}
}
func TestNftablesInterception(t *testing.T) {
off := Nftables(testNet(), nil)
if strings.Contains(off, "redirect to") || strings.Contains(off, "ct status dnat") {
t.Fatal("interception rules present when disabled")
}
on := Nftables(testNet(), &Intercept{Enabled: true, Port: 8081})
for _, want := range []string{
"chain prerouting {",
`iifname "wlan0" meta l4proto tcp tcp dport { 80, 443 } redirect to :8081`,
"tcp dport 8081 ct status dnat accept",
} {
if !strings.Contains(on, want) {
t.Errorf("interception ruleset missing %q", want)
}
}
}