Initial bind-operator: 9 CRDs + controllers
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
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
package bind
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TSIGCreds carries the material needed to authenticate a dynamic update.
|
||||
type TSIGCreds struct {
|
||||
Name string // TSIG key name
|
||||
Algorithm string // e.g. hmac-sha256
|
||||
Secret string // base64-encoded key
|
||||
}
|
||||
|
||||
// RecordUpdate describes a desired record set to apply to a zone.
|
||||
type RecordUpdate struct {
|
||||
FQDN string // fully-qualified owner name, trailing dot recommended
|
||||
Type string // RR type
|
||||
TTL int32 // record TTL
|
||||
Values []string // RDATA entries
|
||||
Delete bool // when true, delete the RRset instead of replacing it
|
||||
}
|
||||
|
||||
// NSUpdate applies a set of record changes to zone by executing nsupdate on the
|
||||
// primary pod, targeting the local server and authenticating with creds. All
|
||||
// changes are sent in a single atomic transaction.
|
||||
func (e *Executor) NSUpdate(ctx context.Context, namespace, pod, zone string, creds TSIGCreds, updates []RecordUpdate) error {
|
||||
var b strings.Builder
|
||||
b.WriteString("server 127.0.0.1\n")
|
||||
b.WriteString(fmt.Sprintf("zone %s\n", dot(zone)))
|
||||
for _, u := range updates {
|
||||
// Replace semantics: clear the RRset first, then add the desired values.
|
||||
b.WriteString(fmt.Sprintf("update delete %s %s\n", dot(u.FQDN), u.Type))
|
||||
if u.Delete {
|
||||
continue
|
||||
}
|
||||
for _, v := range u.Values {
|
||||
b.WriteString(fmt.Sprintf("update add %s %d %s %s\n", dot(u.FQDN), u.TTL, u.Type, v))
|
||||
}
|
||||
}
|
||||
b.WriteString("send\n")
|
||||
|
||||
cmd := []string{"nsupdate", "-y", fmt.Sprintf("%s:%s:%s", creds.Algorithm, creds.Name, creds.Secret)}
|
||||
if out, err := e.Exec(ctx, namespace, pod, cmd, b.String()); err != nil {
|
||||
return fmt.Errorf("nsupdate zone %s: %w (out: %s)", zone, err, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// dot ensures a name is fully qualified with a trailing dot.
|
||||
func dot(name string) string {
|
||||
if name == "" || name == "@" {
|
||||
return "@"
|
||||
}
|
||||
if strings.HasSuffix(name, ".") {
|
||||
return name
|
||||
}
|
||||
return name + "."
|
||||
}
|
||||
Reference in New Issue
Block a user