Skip to content

fix(dnssec)!: make DNSSEC signing actually sign zones - #518

Merged
ebourgeois merged 1 commit into
mainfrom
fix-dnssec-default
Oct 3, 2026
Merged

ebourgeois merged 1 commit into
mainfrom
fix-dnssec-default

Conversation

@ebourgeois

@ebourgeois ebourgeois commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

fix(dnssec): make DNSSEC signing actually sign zones

Following the documented DNSSEC example crash-looped every instance,
and once worked around, no zone was ever signed. Four defects:

  • Reserved policy names: global.dnssec.signing.policy of default,
    insecure or none (any case) is now refused. BIND reserves them
    for its built-in policies, and dnssec-policy "default" { ... } made
    named reject its whole configuration. The unnamed default is now
    "bindy" (DEFAULT_DNSSEC_POLICY_NAME).
  • Inheritance: a DNSZone without spec.dnssecPolicy now inherits its
    primary instance's signing policy (instance config over
    cluster/provider global), as the CRD documents. Previously only an
    explicit policy was sent, so enabling signing on a cluster signed
    nothing (resolve_zone_dnssec_policy, zone_dnssec_policy).
  • Existing zones: on HTTP 409 the PATCH to bindcar now carries
    dnssecPolicy (bindcar 0.8.2, its ADR-0001), so a policy set or
    inherited after creation reaches BIND. Absent fields are omitted
    rather than sent as null, and no PATCH is sent when there is neither
    a policy nor secondaries.
  • Key directory: with signing enabled, named.conf.options renders
    key-directory "/var/cache/bind/keys". Without it BIND kept keys in
    its working directory and never read keys from keysFrom.secretRef.

CRD descriptions regenerated; docs and examples switch to
policy: "bindy" and state that exportToSecret and
keysFrom.persistentVolume are not implemented.

BREAKING CHANGE: dnssec.signing.policy: "default" (or "insecure" /
"none") is now refused. Rename the policy, e.g. to "bindy". Requires a
cluster rollout.
EOF

Following the documented DNSSEC example crash-looped every instance,
and once worked around, no zone was ever signed. Four defects:

- Reserved policy names: `global.dnssec.signing.policy` of `default`,
  `insecure` or `none` (any case) is now refused. BIND reserves them
  for its built-in policies, and `dnssec-policy "default" { ... }` made
  named reject its whole configuration. The unnamed default is now
  "bindy" (DEFAULT_DNSSEC_POLICY_NAME).
- Inheritance: a DNSZone without `spec.dnssecPolicy` now inherits its
  primary instance's signing policy (instance config over
  cluster/provider `global`), as the CRD documents. Previously only an
  explicit policy was sent, so enabling signing on a cluster signed
  nothing (resolve_zone_dnssec_policy, zone_dnssec_policy).
- Existing zones: on HTTP 409 the PATCH to bindcar now carries
  `dnssecPolicy` (bindcar 0.8.2, its ADR-0001), so a policy set or
  inherited after creation reaches BIND. Absent fields are omitted
  rather than sent as null, and no PATCH is sent when there is neither
  a policy nor secondaries.
- Key directory: with signing enabled, named.conf.options renders
  `key-directory "/var/cache/bind/keys"`. Without it BIND kept keys in
  its working directory and never read keys from `keysFrom.secretRef`.

CRD descriptions regenerated; docs and examples switch to
`policy: "bindy"` and state that `exportToSecret` and
`keysFrom.persistentVolume` are not implemented.

BREAKING CHANGE: `dnssec.signing.policy: "default"` (or "insecure" /
"none") is now refused. Rename the policy, e.g. to "bindy". Requires a
cluster rollout.

Signed-off-by: Erick Bourgeois <erick@jeb.ca>
@ebourgeois
ebourgeois merged commit d422055 into main Oct 3, 2026
37 checks passed
@ebourgeois
ebourgeois deleted the fix-dnssec-default branch October 3, 2026 18:57
ebourgeois added a commit that referenced this pull request Oct 3, 2026
…#520)

Config changes never reached running BIND pods. The shared cluster
ConfigMap was re-rendered only when the Bind9Cluster generation changed and
that reconcile won a race, and BIND reads named.conf only at start. An
operator upgrade that renders the same spec differently (the key-directory
fix in #518) therefore never reached an existing cluster.

- desired_configmap_for_instance renders the ConfigMap an instance mounts
  (the cluster's shared one, exactly as the cluster reconciler renders it,
  or a standalone instance's own); it is written only when its data differs.
- The pod template carries bindy.firestoned.io/config-hash, the sha256 of
  that data; a changed hash marks the Deployment for update and is patched
  onto the template, which rolls the pods.
- config_drifted makes stale config content, or pods with another hash, a
  reason to run the instance's resource step, which was otherwise skipped
  unless the generation, labels, parent generation or key rotation changed.

Known, not changed here: deployment_needs_update ignores the bind9
container image, and instances sharing a cluster ConfigMap roll together.

Signed-off-by: Erick Bourgeois <erick@jeb.ca>
ebourgeois added a commit that referenced this pull request Oct 3, 2026
… Deployments

The Deployment update path patched only the API container, labels and
placement. A Deployment created before DNSSEC signing was enabled
therefore never got the dnssec-keys volume, and with key-directory now
pointing at it (#518), BIND had nowhere to keep keys and zones stayed
unsigned. Seen live after rolling onto #518 and #520.

- volumes_missing(): detects a pod volume or bind9 volume mount that the
  operator renders but the running Deployment lacks. It compares by name
  and by (name, mountPath), because the API server adds defaults to the
  stored object.
- The reconcile treats missing volumes as drift, so the resource step
  runs.
- build_volumes_patch(): the update patch carries pod volumes and the
  bind9 container's volumeMounts as strategic-merge `$patch: replace`
  lists, so stale entries are removed instead of left behind.

Tests (volume_convergence): enabling signing on an existing Deployment
needs an update and an identical one does not; the patch carries the
dnssec-keys volume and the bind9 mount as replace lists.

Signed-off-by: Erick Bourgeois <erick@jeb.ca>
ebourgeois added a commit that referenced this pull request Oct 3, 2026
… Deployments (#521)

The Deployment update path patched only the API container, labels and
placement. A Deployment created before DNSSEC signing was enabled
therefore never got the dnssec-keys volume, and with key-directory now
pointing at it (#518), BIND had nowhere to keep keys and zones stayed
unsigned. Seen live after rolling onto #518 and #520.

- volumes_missing(): detects a pod volume or bind9 volume mount that the
  operator renders but the running Deployment lacks. It compares by name
  and by (name, mountPath), because the API server adds defaults to the
  stored object.
- The reconcile treats missing volumes as drift, so the resource step
  runs.
- build_volumes_patch(): the update patch carries pod volumes and the
  bind9 container's volumeMounts as strategic-merge `$patch: replace`
  lists, so stale entries are removed instead of left behind.

Tests (volume_convergence): enabling signing on an existing Deployment
needs an update and an identical one does not; the patch carries the
dnssec-keys volume and the bind9 mount as replace lists.

Signed-off-by: Erick Bourgeois <erick@jeb.ca>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants