Skip to content

Harden DNS defaults: RRL on by default, deny AXFR without an explicit ACL (P1-3, P1-4) - #466

Merged
ebourgeois merged 2 commits into
mainfrom
rrl-axfr-secure-defaults
Aug 29, 2026
Merged

Harden DNS defaults: RRL on by default, deny AXFR without an explicit ACL (P1-3, P1-4)#466
ebourgeois merged 2 commits into
mainfrom
rrl-axfr-secure-defaults

Conversation

@ebourgeois

Copy link
Copy Markdown
Contributor

Summary

Two secure-by-default changes to the generated named.conf.options, closing findings P1-3 and P1-4 from the security-remediation roadmap:

Response Rate Limiting — on by default (P1-3)

  • New RateLimitConfig CRD field: spec.config.rateLimit.responsesPerSecond (inherited from cluster spec.global, instance value overrides).
  • Rendered as rate-limit { responses-per-second N; }; via a new {{RATE_LIMIT}} placeholder in templates/named.conf.options.tmpl.
  • On by default at 15/s per source /24 (ISC's recommended conservative starting point). responsesPerSecond: 0 disables RRL entirely (no block emitted).
  • Why: the threat model (M-08) claimed BIND9 rate limiting was implemented, but no rate-limit directive was ever generated — a false assurance against DNS amplification/reflection (D1/D3). This makes M-08 true.

Deny AXFR by default (P1-4)

  • build_options_conf now emits an explicit allow-transfer { none; }; at the options level whenever no transfer ACL is configured at the instance, role, or global level, instead of omitting the directive and inheriting BIND9's built-in allow-transfer { any; }.
  • Why: a standalone primary zone with no secondaries and no explicit ACL was open to AXFR from anyone (bulk zone enumeration — I2; amplification — D3), contradicting the model's "AXFR restricted to secondaries" posture (M-07).
  • Zones that legitimately need transfers still get a zone-level ACL scoped to their secondary IPs (bind9::zone_ops), which overrides the options-level default in BIND9 — replication is unaffected.

Also in this PR

  • Regenerated CRDs (deploy/operator/crds/*.crd.yaml) and API docs (docs/src/reference/bind9instance-spec.md).
  • Tests: 4 new RRL tests (default-on, custom value, disabled-with-0, default-when-no-config); corrected test_configmap_with_no_config_section and added test_configmap_with_config_but_no_transfer_acl_denies_by_default.
  • Replaced CLAUDE.md with AGENTS.md (CLAUDE.md is now a symlink to it).
  • Bumped pymdown-extensions ^10.7 → ^11.0.1 (docs).

Impact

  • Breaking change
  • Requires cluster rollout (CRD update + new option in generated config)
  • Behavior change: RRL enforced by default (set responsesPerSecond: 0 to opt out); zones without an explicit transfer ACL now deny AXFR by default

Testing

  • cargo test: 1305 passed, 0 failed (run on this branch, rebased on current main)

… ACL (P1-3, P1-4)

Response Rate Limiting (P1-3):
- Add RateLimitConfig (spec.config.rateLimit.responsesPerSecond, inherited
  from cluster spec.global, instance overrides) rendered as
  'rate-limit { responses-per-second N; };' in named.conf.options
- On by default at 15/s per source /24 (ISC's recommended starting
  point); responsesPerSecond: 0 disables it. Threat model M-08 claimed
  RRL was implemented but no directive was ever generated (D1/D3)

Deny AXFR by default (P1-4):
- Emit explicit 'allow-transfer { none; };' at the options level when
  no transfer ACL is configured at instance, role, or global level,
  instead of inheriting BIND9's built-in 'allow-transfer { any; }',
  which left standalone primaries open to bulk zone enumeration (I2)
  and amplification (D3). Zone-level ACLs for real secondaries still
  override the options-level default, so replication is unaffected

Also: regenerate CRDs and API docs, add RRL/AXFR tests, replace
CLAUDE.md with AGENTS.md (CLAUDE.md is now a symlink), and bump
pymdown-extensions to ^11.0.1 for docs

Signed-off-by: Erick Bourgeois <erick@jeb.ca>
chacha20 0.10.1 was yanked from crates.io, failing the Security
Vulnerability Scan job (cargo audit --deny warnings) on PR #466.
0.10.2 is the latest compatible release; cargo audit is clean and
the test suite passes (47 passed, 0 failed).

Signed-off-by: Erick Bourgeois <erick@jeb.ca>
@ebourgeois
ebourgeois merged commit 53df509 into main Aug 29, 2026
36 checks passed
@ebourgeois
ebourgeois deleted the rrl-axfr-secure-defaults branch August 29, 2026 21:46
ebourgeois added a commit that referenced this pull request Aug 30, 2026
…ons (#468)

#466 added the {{RATE_LIMIT}} placeholder to named.conf.options.tmpl and
substituted it in the instance-level builder (build_options_conf), but not
in the cluster-level builder (build_cluster_options_conf). Every cluster
ConfigMap therefore shipped the literal placeholder, and named exited at
startup with:

  /etc/bind/named.conf.options:20: '}' expected near '{'

crash-looping the bind9 operand container and failing the e2e suite
(test_instance_pod_label_selector_finds_pods: pods never Ready in 4m) on
main and on every PR rebased onto it.

Render the cluster-level rate-limit from spec.global.rateLimit (default
15/s, 0 disables) and add a regression test asserting the directive is
rendered and no template placeholder survives substitution.

Verified locally: unit suite green, and an operand pod previously in
CrashLoopBackOff becomes Ready once the placeholder is substituted.

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.

1 participant