Make intra-cluster NOTIFY loop-free (TSIG-keyed, no pod IPs in restart config)
v0.2.5 (PR #14) added an options-scope allow-notify enumerating the primary pod IP on secondaries. Options-scope config feeds the config-hash annotation that rolls the StatefulSet, so any config change rolled the pods, the primary came back on a new pod IP, the operator re-rendered with the new IP, the hash changed, the pods rolled again — an infinite roll loop across every BindCluster. The prod deployment was reverted to v0.2.4. Replace the pod-IP allow-notify with TSIG-authenticated NOTIFY: - Secondaries render `allow-notify { key "<name>"; };` — a static key element with NO IPs. It depends only on the key name, so pod-IP churn can never change the render, the config-hash, or trigger a restart. - The primary signs its outgoing NOTIFYs: the zone-scope also-notify entries (already enumerating replica pod IPs, applied via rndc addzone/modzone with NO restart) now carry `key "<name>"`. - Key choice: reuse the cluster's catalog transfer TSIG key (TransferKeyRef). Secondaries already present it for AXFR and it is in keys.conf on every pod, so no new key plumbing is needed. Add a permanent regression guard for the loop class: - controller: reconcile the ConfigMap with the primary pod on two different IPs and assert the config-hash is byte-identical. - render: render restart-scoped input and assert no pod IP appears in allow-notify; RenderInput no longer has any pod-IP field. Zone-scope also-notify (rndc, no restart) legitimately still lists pod IPs; only restart-scoped config must be pod-IP-independent.
This commit is contained in:
@@ -176,14 +176,6 @@ func (r *BindClusterReconciler) reconcileConfigMap(ctx context.Context, c *bindv
|
||||
// across primary pod restarts (falls back to the pod IP when no primary
|
||||
// Service exists; the Pod/Service watches re-render when it changes).
|
||||
in := bind.RenderInput{Cluster: c, PrimaryAddress: primaryTransferAddress(ctx, r.Client, c)}
|
||||
// Secondaries transfer from the stable primary Service ClusterIP, but the
|
||||
// primary pod's NOTIFYs are sourced from its pod IP, which BIND refuses as
|
||||
// "non-primary" unless it appears in allow-notify. Render the primary pod IP
|
||||
// so intra-cluster NOTIFYs are accepted immediately (the Pod watch re-renders
|
||||
// the ConfigMap when the pod IP changes across restarts).
|
||||
if ip := primaryPodIP(ctx, r.Client, c); ip != "" {
|
||||
in.PrimaryPodAddresses = []string{ip}
|
||||
}
|
||||
|
||||
var acls bindv1alpha1.BindACLList
|
||||
if err := r.List(ctx, &acls, client.InNamespace(c.Namespace)); err == nil {
|
||||
@@ -239,6 +231,16 @@ func (r *BindClusterReconciler) reconcileConfigMap(ctx context.Context, c *bindv
|
||||
}
|
||||
}
|
||||
|
||||
// Secondaries accept intra-cluster NOTIFYs signed with the cluster's catalog
|
||||
// transfer TSIG key (the primary signs its also-notify NOTIFYs with it — see
|
||||
// bindzone_controller). Render `allow-notify { key "<name>"; }` — a static key
|
||||
// element, NO pod IPs — so it never changes on pod churn and cannot re-render
|
||||
// the restart-scoped config (the v0.2.5 roll loop). Resolve the catalog's
|
||||
// TransferKeyRef to the BIND key name (KeyName override, else the ref).
|
||||
if in.Catalog != nil && in.Catalog.Spec.TransferKeyRef != "" {
|
||||
in.NotifyKeyName = tsigKeyName(ctx, r.Client, c.Namespace, in.Catalog.Spec.TransferKeyRef)
|
||||
}
|
||||
|
||||
primaryConf, secondaryConf := bind.RenderNamedConf(in)
|
||||
data := map[string]string{
|
||||
"named.conf.primary": primaryConf,
|
||||
|
||||
Reference in New Issue
Block a user