package catalogue import ( "strings" "testing" ) // The home server's case (novox/hq issue 154): it sits on 192.168.1.0/24, and also on a public /24 a // provider handed it, beside the private network's own link and a container bridge. var homeServer = []AttachedNetwork{ {Link: "eth0", Network: "192.168.1.10/24"}, {Link: "eth1", Network: "203.0.113.7/24"}, {Link: "eth0", Network: "fe80::1/64"}, {Link: "eth0", Network: "fd12:3456:789a::5/64"}, {Link: "wg-mesh", Network: "10.10.0.4/24"}, {Link: "docker0", Network: "172.17.0.1/16"}, } func judged(t *testing.T) []LocalNetwork { t.Helper() return JudgeLocalNetworks(homeServer, []string{"eth0", "eth1"}, "wg-mesh") } // **A public subnet never counts** (ADR 0340 §2), and only a private one on a link facing outside does // (§3): the tunnel's and a bridge's are this machine's own, whatever their addresses. func TestOnlyAPrivateNetworkOnALinkFacingOutsideCounts(t *testing.T) { got := CountedLocalNetworks(judged(t)) want := []string{"192.168.1.0/24", "fd12:3456:789a::/64"} if strings.Join(got, " ") != strings.Join(want, " ") { t.Fatalf("counted %v, want %v", got, want) } for _, j := range judged(t) { if j.Why == "" { t.Errorf("%s on %s is judged without a why", j.Network, j.Link) } switch j.Network { case "203.0.113.0/24": if j.Counts || !strings.Contains(j.Why, "public") { t.Errorf("the public subnet is judged %v (%s), want never counted as public", j.Counts, j.Why) } case "fe80::/64": if j.Counts || !strings.Contains(j.Why, "link-local") { t.Errorf("a link-local network is judged %v (%s)", j.Counts, j.Why) } case "10.10.0.0/24", "172.17.0.0/16": if j.Counts { t.Errorf("%s on %s counts, and it is not the local network", j.Network, j.Link) } } } } // A network overlapping a private range without lying wholly inside it does not count either, so a // wide prefix around a private range cannot carry the internet with it. func TestANetworkNotWhollyInAPrivateRangeDoesNotCount(t *testing.T) { for _, n := range []string{"10.0.0.0/7", "172.0.0.0/8", "100.64.0.0/10", "0.0.0.0/0", "::/0"} { got := JudgeLocalNetworks([]AttachedNetwork{{Link: "eth0", Network: n}}, []string{"eth0"}, "") if len(got) != 1 || got[0].Counts { t.Errorf("%s counts as a local network: %+v", n, got) } } } // **The filter admits the local network on that port's rule alone** (§5), beside the private network, // in both chains, and never the public subnet. func TestALocalNetworkPortIsAdmittedFromItsNetworksAndTheMesh(t *testing.T) { rules := []Rule{ {Port: 1883, Protocol: "tcp", From: FromLocalNetwork, Because: []string{"mosquitto"}}, {Port: 5432, Protocol: "tcp", From: FromMesh, Because: []string{"postgres"}}, } local := CountedLocalNetworks(judged(t)) out := AsNftablesWithLocal(rules, []string{"10.10.0.1", "10.10.0.4"}, false, nil, []string{"eth0", "eth1"}, "wg-mesh", local) for _, want := range []string{ "ip saddr { 10.10.0.1, 10.10.0.4, 192.168.1.0/24 } tcp dport 1883 accept", "ip6 saddr { fd12:3456:789a::/64 } tcp dport 1883 accept", "ip saddr { 10.10.0.1, 10.10.0.4, 192.168.1.0/24 } ct original proto-dst 1883 accept", // The mesh-only port is not widened by the machine having a local network. "ip saddr { 10.10.0.1, 10.10.0.4 } tcp dport 5432 accept", } { if !strings.Contains(out, want) { t.Errorf("the rule set lacks %q:\n%s", want, out) } } if strings.Contains(out, "203.0.113") { t.Errorf("the public subnet reached the rule set:\n%s", out) } if strings.Contains(out, "tcp dport 1883 accept\n") && !strings.Contains(out, "saddr") { t.Errorf("1883 is open to anywhere:\n%s", out) } for _, line := range strings.Split(out, "\n") { if strings.TrimSpace(line) == "tcp dport 1883 accept" { t.Fatalf("1883 is open to anywhere:\n%s", out) } } } // **Nothing resolved is closed and said** (§6): a machine that reported no private network admits the // port from the private network only, and the rule set says it found none — never widened to anywhere. func TestNoLocalNetworkIsSaidAndNotWidened(t *testing.T) { rules := []Rule{{Port: 1883, Protocol: "tcp", From: FromLocalNetwork, Because: []string{"mosquitto"}}} out := AsNftablesWithLocal(rules, []string{"10.10.0.1"}, false, nil, []string{"eth0"}, "wg-mesh", nil) if !strings.Contains(out, "1883 wanted the local network, and this machine reported no private network") { t.Errorf("the rule set does not say the local network resolved to nothing:\n%s", out) } if !strings.Contains(out, "ip saddr { 10.10.0.1 } tcp dport 1883 accept") { t.Errorf("the port is not admitted from the private network:\n%s", out) } for _, line := range strings.Split(out, "\n") { if strings.TrimSpace(line) == "tcp dport 1883 accept" { t.Fatalf("an unresolved local network was widened to anywhere:\n%s", out) } } said := LocalNetworkPorts(rules, nil)[0].Said() if !strings.Contains(said, "none") { t.Errorf("plan says %q, want it to say no local network was found", said) } } // `local-network` is a source a manifest may name (§1, changing ADR 0045 §3), and an unknown source is // still refused. func TestAManifestMayListenFromTheLocalNetwork(t *testing.T) { if _, err := ParseManifest([]byte(`{"module":"mosquitto","version":"1", "listens":[{"port":1883,"from":"local-network"}]}`)); err != nil { t.Fatalf("a listen from the local network is refused: %v", err) } _, err := ParseManifest([]byte(`{"module":"mosquitto","version":"1", "listens":[{"port":1883,"from":"lan"}]}`)) if err == nil || !strings.Contains(err.Error(), `from "lan"`) { t.Fatalf("a source the mesh cannot render is not refused: %v", err) } } // **A fourth reach** (§4, changing ADR 0138 §4): `internal` with the local networks added to what the // filter opens — the internal name, not the public one. func TestTheLocalNetworkReachIsInternalWithTheLocalNetworkAdded(t *testing.T) { public, internal := namesFor(t, aRoutedWeb(), reachSet(ReachLocalNetwork)) if public != "" || internal != "app.anchor.internal" { t.Fatalf("names are %q and %q, want the internal name only", public, internal) } bare := aRoutedWeb() bare.Contributes = nil r := Resolution{Node: "anchor", Modules: []Manifest{bare}} rules, err := r.Rules(Rendering{Settings: reachSet(ReachLocalNetwork)}) if err != nil { t.Fatalf("rules: %v", err) } if len(rules) != 1 || rules[0].From != FromLocalNetwork { t.Fatalf("reach local-network made the filter say %+v", rules) } } // Two modules wanting one port, one from the mesh and one from the local network, render one rule from // the local network: it covers the mesh, and anywhere covers it. func TestTheLocalNetworkSitsBetweenTheMeshAndAnywhere(t *testing.T) { got := widest([]Rule{ {Port: 53, Protocol: "udp", From: FromMesh, Because: []string{"a"}}, {Port: 53, Protocol: "udp", From: FromLocalNetwork, Because: []string{"b"}}, }) if len(got) != 1 || got[0].From != FromLocalNetwork || len(got[0].Because) != 2 { t.Fatalf("got %+v", got) } got = widest(append(got, Rule{Port: 53, Protocol: "udp", From: FromEverywhere, Because: []string{"c"}})) if len(got) != 1 || got[0].From != FromEverywhere { t.Fatalf("got %+v", got) } } // On an adopted node the opening is the private network's part only: the found firewall admits the // local network, and an opening that widened to everywhere would open the port to the internet. func TestAnAdoptedNodeOpensTheLocalNetworkPortToTheMeshOnly(t *testing.T) { out := Openings([]Rule{{Port: 1883, Protocol: "tcp", From: FromLocalNetwork}}, nil, nil) if len(out) != 1 || out[0]["from"] != OpeningFromMesh { t.Fatalf("openings %v", out) } } // A global unicast IPv6 subnet on a link facing outside is public, and never counts: the IPv6 side of the // rule that `local-network` cannot open a port to the internet (ADR 0340 §2). func TestAGlobalIPv6SubnetIsPublicAndNeverCounts(t *testing.T) { got := JudgeLocalNetworks([]AttachedNetwork{{Link: "eth0", Network: "2001:db8:1234::7/48"}}, []string{"eth0"}, "wg-mesh") if len(got) != 1 { t.Fatalf("judged %+v", got) } if got[0].Counts { t.Fatalf("the global IPv6 subnet %s counts as a local network", got[0].Network) } if got[0].Network != "2001:db8:1234::/48" || !strings.Contains(got[0].Why, "public, never counted") { t.Fatalf("judged %s with why %q, want it named public and never counted", got[0].Network, got[0].Why) } if n := CountedLocalNetworks(got); len(n) != 0 { t.Fatalf("counted %v", n) } }