Files
fancy-dns/internal/blocklist/example_test.go
T
developer e8c5b414d9
build / build-push (push) Failing after 23s
half broken
2026-09-27 17:17:53 +00:00

144 lines
5.3 KiB
Go

package blocklist
import (
"path/filepath"
"slices"
"testing"
)
// TestExampleRPZZoneBlocksWhatItSays runs the shipped policy zone through the
// exact path Manager.RefreshAll uses (parseRPZFiles, which prepends a per-file
// $ORIGIN derived from the file name) and pins the rules it produces.
//
// The origin is the reason this is a test and not a doc: a file called
// local.rpz.zone.example is read with the origin "local.", so the relative owner
// `*.doubleclick.net` became `*.doubleclick.net.local.` and the example blocked
// nothing. The example now spells every owner absolutely; if someone "tidies it
// up" back to relative names, the rules below stop matching and this fails.
func TestExampleRPZZoneBlocksWhatItSays(t *testing.T) {
path := filepath.Join("..", "..", "deploy", "zones", "local.rpz.zone.example")
rules, stats, err := parseRPZFiles([]string{path})
if err != nil {
t.Fatalf("parseRPZFiles(%s): %v", path, err)
}
type ruleWant struct {
name string
action Action
response string
}
want := []ruleWant{
{"*.doubleclick.net.", ActionNXDomain, ""},
{"*.ads.example.com.", ActionNXDomain, ""},
{"*.tracker.example.net.", ActionNXDomain, ""},
// A bare owner name in a policy zone is given the subdomain form,
// which covers the name's children but not the name itself; see
// TestExampleRPZZoneApexIsNotCovered.
{"*.analytics.example.org.", ActionNXDomain, ""},
{"*.noisy-cdn.example.", ActionDrop, ""},
{"*.metrics.internal.", ActionPassthrough, ""},
{"*.blocked.example.", ActionRedirect, "192.0.2.10"},
}
got := make([]ruleWant, 0, len(rules))
for _, r := range rules {
got = append(got, ruleWant{r.Name, r.Action, r.Response})
if r.TTL != 60 {
t.Errorf("rule %q has TTL %d, want the zone's $TTL 60", r.Name, r.TTL)
}
}
if !slices.Equal(got, want) {
t.Fatalf("rules mismatch\n got: %+v\nwant: %+v", got, want)
}
if stats.Rules != len(want) {
t.Errorf("stats.Rules = %d, want %d", stats.Rules, len(want))
}
// The A and the AAAA share an owner, so one of them is deduplicated
// rather than compiled twice.
if stats.Duplicates != 1 {
t.Errorf("stats.Duplicates = %d, want 1 (the second blocked.example. record)", stats.Duplicates)
}
}
// TestExampleRPZZoneMatchesTheNamesItNames builds the Set an instance would
// actually consult, so the pin above is about lookups and not only about the
// parser's output. A rule that exists but does not match is still a blocklist
// that does nothing.
func TestExampleRPZZoneMatchesTheNamesItNames(t *testing.T) {
path := filepath.Join("..", "..", "deploy", "zones", "local.rpz.zone.example")
rules, _, err := parseRPZFiles([]string{path})
if err != nil {
t.Fatalf("parseRPZFiles(%s): %v", path, err)
}
b := NewBuilder()
for _, r := range rules {
b.Add(r, "local-rpz", nil)
}
set := b.Build()
blocked := []string{
"www.doubleclick.net.",
"a.b.doubleclick.net.",
// These three carry an explicit "*." owner in the example, so only
// names below them are covered; the apex gap is pinned separately.
"cdn.ads.example.com.",
"pixel.tracker.example.net.",
"cdn.noisy-cdn.example.",
}
for _, name := range blocked {
e := set.Match(name)
if e == nil {
t.Errorf("%s is not matched by the example policy zone", name)
continue
}
if e.IsAllow() {
t.Errorf("%s matched an allow rule, want a block", name)
}
}
if e := set.Match("x.metrics.internal."); e == nil || !e.IsAllow() {
t.Errorf("x.metrics.internal. = %v, want the rpz-passthru allow rule", e)
}
if e := set.Match("unrelated.example."); e != nil {
t.Errorf("unrelated.example. matched %v, want no rule", e)
}
}
// TestExampleRPZZoneApexIsNotCovered pins a real gap rather than an intention.
//
// An RPZ record names the domain it applies to, and in RPZ semantics that means
// the domain and everything below it. rpzRule gives a bare owner name the
// subdomain form ("*.name"), and a subdomain-form rule does not match the name
// itself — so `analytics.example.org.` in a policy zone blocks
// sub.analytics.example.org. and leaves analytics.example.org. resolving
// normally. The apex form ("**.name") is what covers both.
//
// Not changed here: it alters which queries get blocked for anyone already
// running a policy zone, and the previous round's decision was to pin the
// open questions rather than change behaviour. If this is fixed, the fix is one
// character in rpzRule ("*." -> "**.") plus this test and the STATUS.md entry.
func TestExampleRPZZoneApexIsNotCovered(t *testing.T) {
path := filepath.Join("..", "..", "deploy", "zones", "local.rpz.zone.example")
rules, _, err := parseRPZFiles([]string{path})
if err != nil {
t.Fatalf("parseRPZFiles(%s): %v", path, err)
}
b := NewBuilder()
for _, r := range rules {
b.Add(r, "local-rpz", nil)
}
set := b.Build()
for _, name := range []string{"doubleclick.net.", "ads.example.com.", "analytics.example.org."} {
if e := set.Match(name); e != nil {
t.Errorf("%s matched %v; a policy record for a domain is expected to cover "+
"the domain itself as well as its subdomains (see STATUS.md)", name, e)
}
}
// The same gap on the other side: an rpz-passthru. for the apex does not
// allowlist the apex, so a name on the blocklist stays blocked.
if e := set.Match("metrics.internal."); e != nil && e.IsAllow() {
t.Errorf("metrics.internal. matched the allow rule %v; the apex of an "+
"rpz-passthru. record is not covered", e)
}
}