Files
bind-operator/internal/controller/bindtsigkey_controller.go
T
unkinben fe5fbdaf6d 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
2026-07-03 15:48:13 +10:00

109 lines
3.5 KiB
Go

package controller
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
bindv1alpha1 "git.unkin.net/unkin/bind-operator/api/v1alpha1"
"git.unkin.net/unkin/bind-operator/internal/bind"
)
// BindTSIGKeyReconciler generates TSIG key material into a Secret.
type BindTSIGKeyReconciler struct {
client.Client
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=bind.unkin.net,resources=bindtsigkeys,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=bind.unkin.net,resources=bindtsigkeys/status,verbs=get;update;patch
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
func (r *BindTSIGKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
var key bindv1alpha1.BindTSIGKey
if err := r.Get(ctx, req.NamespacedName, &key); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
algorithm := string(key.Spec.Algorithm)
if algorithm == "" {
algorithm = string(bindv1alpha1.TSIGHMACSHA256)
}
keyName := key.Spec.KeyName
if keyName == "" {
keyName = key.Name
}
secretName := key.Spec.SecretName
if secretName == "" {
secretName = key.Name + "-tsig"
}
var secret corev1.Secret
err := r.Get(ctx, types.NamespacedName{Namespace: key.Namespace, Name: secretName}, &secret)
switch {
case apierrors.IsNotFound(err):
if key.Spec.ImportExisting {
return r.fail(ctx, &key, "SecretMissing", fmt.Sprintf("import secret %s not found", secretName))
}
material, genErr := bind.GenerateSecret(bind.SecretBytesForAlgorithm(algorithm))
if genErr != nil {
return ctrl.Result{}, genErr
}
newSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: key.Namespace, Labels: map[string]string{managedByLabel: managedByValue}},
Data: map[string][]byte{
"algorithm": []byte(algorithm),
"keyName": []byte(keyName),
"secret": []byte(material),
"key.conf": []byte(bind.KeyClause(keyName, algorithm, material)),
},
}
if err := ctrl.SetControllerReference(&key, newSecret, r.Scheme); err != nil {
return ctrl.Result{}, err
}
if err := r.Create(ctx, newSecret); err != nil {
return ctrl.Result{}, err
}
logger.Info("generated TSIG key", "key", key.Name, "secret", secretName)
case err != nil:
return ctrl.Result{}, err
}
key.Status.SecretName = secretName
key.Status.KeyName = keyName
key.Status.Ready = true
key.Status.ObservedGeneration = key.Generation
setReady(&key.Status.Conditions, key.Generation, true, "KeyReady", "TSIG key material present")
if err := r.Status().Update(ctx, &key); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *BindTSIGKeyReconciler) fail(ctx context.Context, key *bindv1alpha1.BindTSIGKey, reason, msg string) (ctrl.Result, error) {
key.Status.Ready = false
key.Status.ObservedGeneration = key.Generation
setReady(&key.Status.Conditions, key.Generation, false, reason, msg)
if err := r.Status().Update(ctx, key); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{RequeueAfter: requeueShort}, nil
}
func (r *BindTSIGKeyReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&bindv1alpha1.BindTSIGKey{}).
Owns(&corev1.Secret{}).
Complete(r)
}