08d46ccce0
An unsigned localhost query is REFUSED for a zone behind a key-matched view and answers empty with exit 0; signing it with the update creds reaches the view.
166 lines
7.7 KiB
Go
166 lines
7.7 KiB
Go
package controller
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
bindv1alpha1 "git.unkin.net/unkin/bind-operator/api/v1alpha1"
|
|
"git.unkin.net/unkin/bind-operator/internal/bind"
|
|
)
|
|
|
|
func zoneWith(spec bindv1alpha1.BindZoneSpec) *bindv1alpha1.BindZone {
|
|
spec.ZoneName = "acme.unkin.net"
|
|
return &bindv1alpha1.BindZone{Spec: spec}
|
|
}
|
|
|
|
func testCluster() *bindv1alpha1.BindCluster {
|
|
c := &bindv1alpha1.BindCluster{}
|
|
c.Name, c.Namespace = "auth", "bind-internal"
|
|
return c
|
|
}
|
|
|
|
const stableNS = "auth-0.auth-headless.bind-internal.svc.cluster.local."
|
|
|
|
func TestZoneNameservers(t *testing.T) {
|
|
ttl60 := int32(60)
|
|
cases := []struct {
|
|
name string
|
|
spec bindv1alpha1.BindZoneSpec
|
|
want []string
|
|
ttl int32
|
|
declared bool
|
|
}{
|
|
{"fallback is out-of-zone, so it needs no glue", bindv1alpha1.BindZoneSpec{}, []string{stableNS}, 3600, false},
|
|
{"declared wins", bindv1alpha1.BindZoneSpec{Nameservers: []string{"ns1.unkin.net"}}, []string{"ns1.unkin.net."}, 3600, true},
|
|
{"spec.defaultTTL applies", bindv1alpha1.BindZoneSpec{Nameservers: []string{"ns1.unkin.net."}, DefaultTTL: 60}, []string{"ns1.unkin.net."}, 60, true},
|
|
// An apex NS in spec.records cannot converge on its own: BIND ignores an
|
|
// RRset-wide delete at the apex, so it has to go through the apex path.
|
|
{"apex NS in records counts as declared", bindv1alpha1.BindZoneSpec{Records: []bindv1alpha1.Record{
|
|
{Name: "@", Type: "ns", Values: []string{"a.ns.unkin.net.", "b.ns.unkin.net."}},
|
|
{Name: "www", Type: "A", Values: []string{"10.0.0.1"}},
|
|
}}, []string{"a.ns.unkin.net.", "b.ns.unkin.net."}, 3600, true},
|
|
// A TTL on the apex NS record itself must survive the fold.
|
|
{"record TTL beats the zone default", bindv1alpha1.BindZoneSpec{DefaultTTL: 3600, Records: []bindv1alpha1.Record{
|
|
{Name: "@", Type: "NS", TTL: &ttl60, Values: []string{"a.ns.unkin.net."}},
|
|
}}, []string{"a.ns.unkin.net."}, 60, true},
|
|
{"spec.nameservers beats records", bindv1alpha1.BindZoneSpec{
|
|
Nameservers: []string{"ns1.unkin.net."},
|
|
Records: []bindv1alpha1.Record{{Name: "@", Type: "NS", Values: []string{"other.unkin.net."}}},
|
|
}, []string{"ns1.unkin.net."}, 3600, true},
|
|
}
|
|
for _, c := range cases {
|
|
got, ttl, declared := zoneNameservers(zoneWith(c.spec), testCluster())
|
|
if declared != c.declared || ttl != c.ttl || strings.Join(got, ",") != strings.Join(c.want, ",") {
|
|
t.Errorf("%s: got %v/%d/%v; want %v/%d/%v", c.name, got, ttl, declared, c.want, c.ttl, c.declared)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A query that cannot see the zone returns nothing, which must not be read as an
|
|
// empty apex: retracting blind means deleting the last NS record, which named
|
|
// rejects, leaving the zone stuck.
|
|
func TestApexNSUpdatesUnreadableLiveSetIsAdditive(t *testing.T) {
|
|
zone := zoneWith(bindv1alpha1.BindZoneSpec{Nameservers: []string{"ns1.unkin.net."}})
|
|
got := apexNSUpdates(zone, []string{"ns1.unkin.net."}, nil, 3600)
|
|
assertUpdates(t, got, []bind.RecordUpdate{
|
|
{FQDN: "acme.unkin.net.", Type: "NS", TTL: 3600, Values: []string{"ns1.unkin.net."}, PerValue: true},
|
|
})
|
|
}
|
|
|
|
// The apex NS RRset must be converged per record: an RRset-wide delete at the
|
|
// apex is ignored by BIND, which would leave the placeholder published alongside
|
|
// the real nameservers. Retracting an in-zone name takes its glue with it.
|
|
func TestApexNSUpdatesConverges(t *testing.T) {
|
|
zone := zoneWith(bindv1alpha1.BindZoneSpec{Nameservers: []string{"ns1.unkin.net."}, DefaultTTL: 60})
|
|
got := apexNSUpdates(zone, []string{"ns1.unkin.net."}, []string{"ns1.acme.unkin.net."}, 60)
|
|
assertUpdates(t, got, []bind.RecordUpdate{
|
|
{FQDN: "acme.unkin.net.", Type: "NS", TTL: 60, Values: []string{"ns1.unkin.net."}, PerValue: true},
|
|
{FQDN: "acme.unkin.net.", Type: "NS", Values: []string{"ns1.acme.unkin.net."}, PerValue: true, Delete: true},
|
|
{FQDN: "ns1.acme.unkin.net.", Type: "A", Delete: true},
|
|
})
|
|
}
|
|
|
|
// Glue retirement is not special-cased to the name ns1: the seed glues every
|
|
// declared in-zone nameserver.
|
|
func TestApexNSUpdatesRetiresAnyInZoneGlue(t *testing.T) {
|
|
zone := zoneWith(bindv1alpha1.BindZoneSpec{Nameservers: []string{"a.ns.unkin.net."}})
|
|
got := apexNSUpdates(zone, []string{"a.ns.unkin.net."}, []string{"dns.acme.unkin.net."}, 3600)
|
|
assertUpdates(t, got, []bind.RecordUpdate{
|
|
{FQDN: "acme.unkin.net.", Type: "NS", TTL: 3600, Values: []string{"a.ns.unkin.net."}, PerValue: true},
|
|
{FQDN: "acme.unkin.net.", Type: "NS", Values: []string{"dns.acme.unkin.net."}, PerValue: true, Delete: true},
|
|
{FQDN: "dns.acme.unkin.net.", Type: "A", Delete: true},
|
|
})
|
|
}
|
|
|
|
func TestApexNSUpdatesNoopWhenConverged(t *testing.T) {
|
|
zone := zoneWith(bindv1alpha1.BindZoneSpec{Nameservers: []string{"ns1.unkin.net"}})
|
|
// Case differs: DNS names compare case-insensitively, so this is converged.
|
|
if got := apexNSUpdates(zone, []string{"ns1.unkin.net."}, []string{"NS1.Unkin.Net."}, 3600); len(got) != 0 {
|
|
t.Fatalf("expected no updates, got %+v", got)
|
|
}
|
|
}
|
|
|
|
// Changing the declared set replaces only what changed, keeping the overlap.
|
|
func TestApexNSUpdatesPartialChange(t *testing.T) {
|
|
zone := zoneWith(bindv1alpha1.BindZoneSpec{Nameservers: []string{"a.ns.unkin.net.", "c.ns.unkin.net."}})
|
|
got := apexNSUpdates(zone, []string{"a.ns.unkin.net.", "c.ns.unkin.net."}, []string{"a.ns.unkin.net.", "b.ns.unkin.net."}, 3600)
|
|
assertUpdates(t, got, []bind.RecordUpdate{
|
|
{FQDN: "acme.unkin.net.", Type: "NS", TTL: 3600, Values: []string{"c.ns.unkin.net."}, PerValue: true},
|
|
{FQDN: "acme.unkin.net.", Type: "NS", Values: []string{"b.ns.unkin.net."}, PerValue: true, Delete: true},
|
|
})
|
|
}
|
|
|
|
// A declared in-zone nameserver owns its glue; removing it would fail named's
|
|
// post-update nameserver sanity check.
|
|
func TestApexNSUpdatesKeepsNeededGlue(t *testing.T) {
|
|
zone := zoneWith(bindv1alpha1.BindZoneSpec{Nameservers: []string{"ns1.acme.unkin.net."}})
|
|
if got := apexNSUpdates(zone, []string{"ns1.acme.unkin.net."}, []string{"ns1.acme.unkin.net."}, 3600); len(got) != 0 {
|
|
t.Fatalf("expected no updates, got %+v", got)
|
|
}
|
|
}
|
|
|
|
// spec.records owning the ns1 address means the glue is real data, not the seed
|
|
// placeholder.
|
|
func TestApexNSUpdatesLeavesRecordOwnedGlue(t *testing.T) {
|
|
zone := zoneWith(bindv1alpha1.BindZoneSpec{
|
|
Nameservers: []string{"ns1.unkin.net."},
|
|
Records: []bindv1alpha1.Record{{Name: "ns1", Type: "a", Values: []string{"10.0.0.53"}}},
|
|
})
|
|
got := apexNSUpdates(zone, []string{"ns1.unkin.net."}, []string{"ns1.acme.unkin.net."}, 3600)
|
|
assertUpdates(t, got, []bind.RecordUpdate{
|
|
{FQDN: "acme.unkin.net.", Type: "NS", TTL: 3600, Values: []string{"ns1.unkin.net."}, PerValue: true},
|
|
{FQDN: "acme.unkin.net.", Type: "NS", Values: []string{"ns1.acme.unkin.net."}, PerValue: true, Delete: true},
|
|
})
|
|
}
|
|
|
|
// The apex NS is converged by the apex path, so it must not also be emitted as a
|
|
// record: an RRset-wide delete there is ignored and the add would append.
|
|
func TestRecordsToUpdatesSkipsApexNS(t *testing.T) {
|
|
records := []bindv1alpha1.Record{
|
|
{Name: "@", Type: "NS", Values: []string{"a.ns.unkin.net."}},
|
|
{Name: "sub", Type: "NS", Values: []string{"d.ns.unkin.net."}},
|
|
{Name: "@", Type: "MX", Values: []string{"10 mail.unkin.net."}},
|
|
}
|
|
got := recordsToUpdates("acme.unkin.net", records, 3600)
|
|
if len(got) != 2 || got[0].FQDN != "sub.acme.unkin.net." || got[1].Type != "MX" {
|
|
t.Fatalf("got %+v", got)
|
|
}
|
|
}
|
|
|
|
func assertUpdates(t *testing.T, got, want []bind.RecordUpdate) {
|
|
t.Helper()
|
|
if len(got) != len(want) {
|
|
t.Fatalf("got %d updates %+v; want %d %+v", len(got), got, len(want), want)
|
|
}
|
|
for i := range want {
|
|
g, w := got[i], want[i]
|
|
if g.FQDN != w.FQDN || g.Type != w.Type || g.TTL != w.TTL || g.Delete != w.Delete || g.PerValue != w.PerValue {
|
|
t.Errorf("update %d = %+v; want %+v", i, g, w)
|
|
continue
|
|
}
|
|
if strings.Join(g.Values, ",") != strings.Join(w.Values, ",") {
|
|
t.Errorf("update %d values = %v; want %v", i, g.Values, w.Values)
|
|
}
|
|
}
|
|
}
|