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.
108 lines
3.4 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|