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
109 lines
3.5 KiB
Go
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)
|
|
}
|