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
61 lines
1.9 KiB
Go
61 lines
1.9 KiB
Go
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 + "."
|
|
}
|