The vault-plugin-secrets-bind-tsig plugin needs an HTTP endpoint that
creates, reads, rotates and deletes TSIG keys on its behalf, decoupling
Vault from direct Kubernetes API access. This adds that companion API and
lets the operator deploy it declaratively.
- Add BindTSIGAPI CRD: creating one makes the operator reconcile a
Deployment, Service, ConfigMap (env vars), token Secret and namespaced
RBAC for the companion API. Spec covers image, replicas, port,
targetNamespace, tokenSecretName, extra env, service exposure and
resources.
- Generate the master access token Secret only when absent, so a
VaultStaticSecret may pre-seed/overwrite it; the operator does not own it.
- Add the companion API server (internal/tsigapi): bearer-auth HTTP
contract POST /v1/keys, GET/DELETE /v1/keys/{name}, POST
/v1/keys/{name}/rotate, backed by BindTSIGKey custom resources the
operator reconciles into key material.
- Add cmd/tsigapi entrypoint and Dockerfile.tsigapi (distroless).
- Wire the reconciler into setup, regenerate CRDs/RBAC/deepcopy, and add
Woodpecker build (PR dry-run) and release (tag push) steps for the
bind-tsig-api image.
- Cover the API server with auth and key-lifecycle unit tests.
Secondaries reject RFC2136/nsupdate writes, but the read Service round-
robins across all pods. Add an optional per-cluster write endpoint that
targets only the primary pod (ordinal 0) via the StatefulSet pod-name
label. Reads keep using the all-pods Service.
- api: BindCluster.spec.primaryService (*ClusterServiceSpec) — ClusterIP
for in-cluster writers (external-dns) or LoadBalancer for external
- reconcilePrimaryService creates <cluster>-primary selecting pod-0 when
set, deletes it when unset
- regenerate CRDs + install.yaml
Adds BindCluster.spec.service.externalTrafficPolicy so DNS LoadBalancers
can preserve client source IPs (Local), which the source-IP ACLs on the
authoritative/resolver need to actually restrict external clients (Cluster
SNATs everything to node IPs).
- api: ClusterServiceSpec.externalTrafficPolicy (enum Cluster;Local)
- set it on the client Service for LoadBalancer/NodePort types
- regenerate CRDs + install.yaml
type:forward zones are pure configuration (conditional forwarding), not
replicated data, so a resolver needs them on all pods. They were being
rndc-addzone'd on the primary only, so queries hitting a secondary pod
missed the forwarding. Render them into named.conf instead.
- render forward zones inside their view (or top-level when no views)
- BindCluster lists type:forward zones and watches BindZone to re-render
- BindZone controller skips forward zones (config-managed, no addzone)
- unit test for forward-zone-in-view rendering
TSIG keys were included in every cluster's keys.conf namespace-wide. When
multiple clusters share a namespace, that leaks keys across clusters. Add
spec.clusterRef so a key can target a specific BindCluster; empty keeps
the shared (all-clusters-in-namespace) behaviour.
- api: BindTSIGKey.spec.clusterRef (optional)
- BindCluster keys.conf now includes only keys with matching or empty
clusterRef
- regenerate CRDs + install.yaml bundle
Two bugs made every provisioned zone fail to load:
1. The seed zone's apex NS (ns1.<zone>) is in-zone but had no address
record, so BIND check-integrity refused to load it and rndc addzone
reverted. Add a glue A record pointing at the primary pod IP.
2. Secondaries rendered primaries/default-primaries with the primary's
DNS name, but BIND only accepts IP addresses there (it read the name
as a remote-servers list and failed config load, crash-looping the
secondary). Render the primary pod IP instead, and watch Pods so the
config re-renders when that IP appears or changes.
- bind.WriteSeedZone writes 'ns1 IN A <primaryIP>' glue
- controllers resolve primaryPodIP and pass it to the seed (requeue if
the primary has no IP yet)
- BindCluster renders PrimaryAddress from pod-0's IP and watches Pods
- render omits catalog primaries when the IP is unknown (no empty list)
Uses internetsystemsconsortium/bind9 as the default base image instead of
a self-hosted one, verified against internetsystemsconsortium/bind9:9.20
(runs as root; named/rndc/nsupdate at /usr/sbin,/usr/sbin,/usr/bin).
- project operator config at /etc/bind-operator instead of overmounting
the image's /etc/bind (keeps bind.keys / base config intact)
- reference named/rndc/nsupdate by absolute path (exec PATH may exclude
/usr/sbin)
- centralise filesystem + binary paths in internal/bind/consts.go
- default spec.image to internetsystemsconsortium/bind9:9.20
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