ea330bd767
Secondaries never replicated any member zone: the master's catalog zone
requires key-authenticated AXFR (allow-transfer { key "transfer-key"; }),
but the rendered secondary config transferred without presenting the key,
so every catalog transfer was REFUSED and no member zones provisioned.
Two further gaps compounded it: member zones had no allow-transfer at all,
and secondaries pointed at the primary's pod IP, which dies on restart.
- Render the catalog transfer key into the secondary catalog-zones
default-primaries and the secondary catalog zone primaries, so
key-authenticated AXFR from the primary is accepted.
- Add allow-transfer { key "<transfer-key>"; } to catalog member primary
zones (when the zone does not set an explicit allow-transfer), so
secondaries can pull them; applied to existing zones via modzone.
- Point secondaries at the stable primary Service ClusterIP instead of the
primary pod IP, so replication survives primary pod restarts (falls back
to the pod IP when no primary Service exists).
149 lines
5.9 KiB
Go
149 lines
5.9 KiB
Go
package bind
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
bindv1alpha1 "git.unkin.net/unkin/bind-operator/api/v1alpha1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
func newCluster(mode bindv1alpha1.BindMode) *bindv1alpha1.BindCluster {
|
|
return &bindv1alpha1.BindCluster{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "auth", Namespace: "dns"},
|
|
Spec: bindv1alpha1.BindClusterSpec{Mode: mode, Replicas: 3},
|
|
}
|
|
}
|
|
|
|
func TestRenderResolverEnablesRecursion(t *testing.T) {
|
|
primary, secondary := RenderNamedConf(RenderInput{Cluster: newCluster(bindv1alpha1.ModeResolver)})
|
|
if !strings.Contains(primary, "recursion yes;") {
|
|
t.Fatalf("resolver primary should enable recursion:\n%s", primary)
|
|
}
|
|
if !strings.Contains(secondary, "recursion yes;") {
|
|
t.Fatalf("resolver secondary should enable recursion")
|
|
}
|
|
}
|
|
|
|
func TestRenderAuthoritativeDisablesRecursion(t *testing.T) {
|
|
primary, _ := RenderNamedConf(RenderInput{Cluster: newCluster(bindv1alpha1.ModeAuthoritative)})
|
|
if !strings.Contains(primary, "recursion no;") {
|
|
t.Fatalf("authoritative should disable recursion:\n%s", primary)
|
|
}
|
|
if !strings.Contains(primary, "allow-new-zones yes;") {
|
|
t.Fatalf("authoritative should allow new zones for dynamic provisioning")
|
|
}
|
|
}
|
|
|
|
func TestRenderCatalogOnSecondaryOnly(t *testing.T) {
|
|
in := RenderInput{
|
|
Cluster: newCluster(bindv1alpha1.ModeAuthoritative),
|
|
Catalog: &bindv1alpha1.BindCatalogZone{Spec: bindv1alpha1.BindCatalogZoneSpec{ZoneName: "catalog.internal", DefaultPrimaries: []string{"10.0.0.1"}}},
|
|
PrimaryAddress: "auth-0.auth-headless.dns.svc.cluster.local",
|
|
}
|
|
primary, secondary := RenderNamedConf(in)
|
|
if strings.Contains(primary, "catalog-zones") {
|
|
t.Fatalf("primary must not consume the catalog it publishes:\n%s", primary)
|
|
}
|
|
if !strings.Contains(secondary, "catalog-zones") {
|
|
t.Fatalf("secondary must consume the catalog zone:\n%s", secondary)
|
|
}
|
|
if !strings.Contains(secondary, "type secondary;") {
|
|
t.Fatalf("secondary must declare the catalog zone as a secondary")
|
|
}
|
|
}
|
|
|
|
func TestRenderCatalogOmittedWhenPrimaryIPUnknown(t *testing.T) {
|
|
// Primary IP not known yet and no explicit default-primaries: the secondary
|
|
// must not emit a catalog-zones / secondary catalog zone with an empty
|
|
// primaries list (which BIND rejects at config load).
|
|
in := RenderInput{
|
|
Cluster: newCluster(bindv1alpha1.ModeAuthoritative),
|
|
Catalog: &bindv1alpha1.BindCatalogZone{Spec: bindv1alpha1.BindCatalogZoneSpec{ZoneName: "catalog.internal"}},
|
|
PrimaryAddress: "",
|
|
}
|
|
_, secondary := RenderNamedConf(in)
|
|
if strings.Contains(secondary, "catalog-zones") || strings.Contains(secondary, "primaries {") {
|
|
t.Fatalf("secondary must omit catalog primaries when the primary IP is unknown:\n%s", secondary)
|
|
}
|
|
}
|
|
|
|
func TestRenderCatalogUsesPrimaryIP(t *testing.T) {
|
|
in := RenderInput{
|
|
Cluster: newCluster(bindv1alpha1.ModeAuthoritative),
|
|
Catalog: &bindv1alpha1.BindCatalogZone{Spec: bindv1alpha1.BindCatalogZoneSpec{ZoneName: "catalog.internal"}},
|
|
PrimaryAddress: "10.42.0.7",
|
|
}
|
|
_, secondary := RenderNamedConf(in)
|
|
if !strings.Contains(secondary, "primaries { 10.42.0.7; }") {
|
|
t.Fatalf("secondary should point primaries at the primary pod IP:\n%s", secondary)
|
|
}
|
|
}
|
|
|
|
func TestRenderCatalogPrimariesCarryTransferKey(t *testing.T) {
|
|
// When the catalog declares a transfer key, secondaries must present it in
|
|
// both the catalog-zones default-primaries and the secondary catalog zone,
|
|
// or the key-authenticated primary REFUSES the AXFR.
|
|
in := RenderInput{
|
|
Cluster: newCluster(bindv1alpha1.ModeAuthoritative),
|
|
Catalog: &bindv1alpha1.BindCatalogZone{Spec: bindv1alpha1.BindCatalogZoneSpec{ZoneName: "catalog.internal", TransferKeyRef: "transfer-key"}},
|
|
PrimaryAddress: "10.43.0.5",
|
|
}
|
|
_, secondary := RenderNamedConf(in)
|
|
if !strings.Contains(secondary, `default-primaries { 10.43.0.5 key "transfer-key"; }`) {
|
|
t.Fatalf("catalog-zones default-primaries must carry the transfer key:\n%s", secondary)
|
|
}
|
|
if !strings.Contains(secondary, `primaries { 10.43.0.5 key "transfer-key"; }`) {
|
|
t.Fatalf("secondary catalog zone primaries must carry the transfer key:\n%s", secondary)
|
|
}
|
|
}
|
|
|
|
func TestRenderForwardZoneInView(t *testing.T) {
|
|
rec := true
|
|
in := RenderInput{
|
|
Cluster: newCluster(bindv1alpha1.ModeResolver),
|
|
Views: []bindv1alpha1.BindView{{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "openforwarder"},
|
|
Spec: bindv1alpha1.BindViewSpec{ClusterRef: "auth", MatchClients: []string{"acl-main"}, Recursion: &rec},
|
|
}},
|
|
Forwards: []bindv1alpha1.BindZone{{
|
|
Spec: bindv1alpha1.BindZoneSpec{ClusterRef: "auth", ZoneName: "unkin.net", Type: bindv1alpha1.ZoneForward, ViewRef: "openforwarder", Forwarders: []string{"198.18.19.15"}},
|
|
}},
|
|
}
|
|
primary, secondary := RenderNamedConf(in)
|
|
for _, out := range []string{primary, secondary} {
|
|
if !strings.Contains(out, `view "openforwarder"`) {
|
|
t.Fatalf("view missing:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, `zone "unkin.net" {`) || !strings.Contains(out, "type forward;") || !strings.Contains(out, "forwarders { 198.18.19.15; }") {
|
|
t.Fatalf("forward zone not rendered inside view (must be on all pods):\n%s", out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRenderACL(t *testing.T) {
|
|
in := RenderInput{
|
|
Cluster: newCluster(bindv1alpha1.ModeAuthoritative),
|
|
ACLs: []bindv1alpha1.BindACL{{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "internal"},
|
|
Spec: bindv1alpha1.BindACLSpec{Entries: []string{"10.0.0.0/8", "192.168.0.0/16"}},
|
|
}},
|
|
}
|
|
primary, _ := RenderNamedConf(in)
|
|
if !strings.Contains(primary, `acl "internal" { 10.0.0.0/8; 192.168.0.0/16; };`) {
|
|
t.Fatalf("ACL not rendered correctly:\n%s", primary)
|
|
}
|
|
}
|
|
|
|
func TestCatalogHashStable(t *testing.T) {
|
|
// SHA-1 of the wire format of "example.com" is well-defined and stable.
|
|
h1 := catalogHash("example.com")
|
|
h2 := catalogHash("example.com.")
|
|
if h1 != h2 {
|
|
t.Fatalf("trailing dot should not change hash: %s vs %s", h1, h2)
|
|
}
|
|
if len(h1) != 40 {
|
|
t.Fatalf("expected 40-char hex sha1, got %d: %s", len(h1), h1)
|
|
}
|
|
}
|