fe5fbdaf6d
Implements a Kubernetes operator that manages fleets of BIND9 servers declaratively, using controller-runtime (matching forgebot conventions). - add BindCluster reconciler: StatefulSet (pod-0 primary, secondaries), headless + client Services, rendered named.conf ConfigMap, TSIG keys Secret and rndc control Secret; watches dependent CRs to re-render - add BindTSIGKey reconciler that generates key material into a Secret - add BindZone/DNSRecord reconcilers using fully-dynamic delivery (rndc addzone + TSIG nsupdate against the primary pod) - add BindCatalogZone reconciler so secondaries auto-provision zones - add BindPolicy (RPZ), BindDNSSECPolicy, BindView, BindACL reconcilers - render primary/secondary named.conf variants selected by pod ordinal - generate CRDs, deepcopy and RBAC; add samples mapping the three Puppet roles (authoritative/resolver/external-dns) to three BindClusters - add Makefile, Dockerfile.operator, Woodpecker CI and kind manifests
80 lines
2.5 KiB
Go
80 lines
2.5 KiB
Go
package controller
|
|
|
|
import (
|
|
"testing"
|
|
|
|
bindv1alpha1 "git.unkin.net/unkin/bind-operator/api/v1alpha1"
|
|
)
|
|
|
|
func TestFQDN(t *testing.T) {
|
|
cases := []struct{ name, zone, want string }{
|
|
{"@", "example.com", "example.com."},
|
|
{"", "example.com", "example.com."},
|
|
{"www", "example.com", "www.example.com."},
|
|
{"www.example.com.", "example.com", "www.example.com."},
|
|
{"host", "10.in-addr.arpa", "host.10.in-addr.arpa."},
|
|
}
|
|
for _, c := range cases {
|
|
if got := fqdn(c.name, c.zone); got != c.want {
|
|
t.Errorf("fqdn(%q,%q)=%q want %q", c.name, c.zone, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRecordsToUpdatesTTLFallback(t *testing.T) {
|
|
custom := int32(60)
|
|
records := []bindv1alpha1.Record{
|
|
{Name: "@", Type: "A", Values: []string{"192.0.2.1"}},
|
|
{Name: "low", Type: "A", TTL: &custom, Values: []string{"192.0.2.2"}},
|
|
}
|
|
updates := recordsToUpdates("example.com", records, 3600)
|
|
if len(updates) != 2 {
|
|
t.Fatalf("expected 2 updates, got %d", len(updates))
|
|
}
|
|
if updates[0].TTL != 3600 {
|
|
t.Errorf("expected default TTL 3600, got %d", updates[0].TTL)
|
|
}
|
|
if updates[1].TTL != 60 {
|
|
t.Errorf("expected record TTL 60, got %d", updates[1].TTL)
|
|
}
|
|
if updates[0].FQDN != "example.com." {
|
|
t.Errorf("apex FQDN wrong: %s", updates[0].FQDN)
|
|
}
|
|
}
|
|
|
|
func TestRPZRulesToUpdates(t *testing.T) {
|
|
rules := []bindv1alpha1.RPZRule{
|
|
{Trigger: "qname", Match: "bad.example.com", Action: "nxdomain"},
|
|
{Trigger: "qname", Match: "walled.example.com", Action: "cname", Target: "block.internal"},
|
|
}
|
|
updates := rpzRulesToUpdates("rpz.internal", rules)
|
|
if len(updates) != 2 {
|
|
t.Fatalf("expected 2 updates, got %d", len(updates))
|
|
}
|
|
if updates[0].FQDN != "bad.example.com.rpz.internal." {
|
|
t.Errorf("qname owner wrong: %s", updates[0].FQDN)
|
|
}
|
|
if updates[0].Values[0] != "." {
|
|
t.Errorf("nxdomain rdata should be '.', got %q", updates[0].Values[0])
|
|
}
|
|
if updates[1].Values[0] != "block.internal." {
|
|
t.Errorf("cname rdata wrong: %q", updates[1].Values[0])
|
|
}
|
|
}
|
|
|
|
func TestCatalogEnabledDefault(t *testing.T) {
|
|
on := &bindv1alpha1.BindZone{Spec: bindv1alpha1.BindZoneSpec{Type: bindv1alpha1.ZonePrimary}}
|
|
if !catalogEnabled(on) {
|
|
t.Error("primary zone should default to catalog enabled")
|
|
}
|
|
no := false
|
|
off := &bindv1alpha1.BindZone{Spec: bindv1alpha1.BindZoneSpec{Type: bindv1alpha1.ZonePrimary, Catalog: &no}}
|
|
if catalogEnabled(off) {
|
|
t.Error("catalog=false should disable membership")
|
|
}
|
|
sec := &bindv1alpha1.BindZone{Spec: bindv1alpha1.BindZoneSpec{Type: bindv1alpha1.ZoneSecondary}}
|
|
if catalogEnabled(sec) {
|
|
t.Error("secondary zone should never be a catalog member")
|
|
}
|
|
}
|