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
81 lines
2.8 KiB
Go
81 lines
2.8 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 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)
|
|
}
|
|
}
|