Repository navigation
fix(roles/bind)!: validate DNSSEC and follow the crypto policy (fix #355, fix #356) - #385
Merged
Merged
Conversation
`disable-empty-zone` takes the name of a built-in empty zone to disable (cfg_type_astring in lib/isccfg/namedconf.c), not a boolean, so `disable-empty-zone yes;` disabled a zone named "yes.", which does not exist. The boolean switch would be `empty-zones-enable`. Verified on Rocky 8, 9 and 10 (bind 9.11.36, 9.16.23, 9.18.33) with the template rendered before and after: with the role's `forward only;` named creates no automatic empty zones either way; without it, both render the same 97/97/98 empty zones and answer `dig -x 10.1.2.3` from 10.IN-ADDR.ARPA, while the control `disable-empty-zone "10.IN-ADDR.ARPA";` drops exactly that zone.
, fix #356) The template hardcoded `dnssec-validation no;` and dropped the crypto-policies include that the named.conf of the bind package carries on RHEL 8, 9 and 10. Rebuild the options block after the packaged file: same statements in the same order (secroots-file, recursing-file, geoip-directory on EL 9+, dnssec-enable yes on EL 8, the include), with the Linuxfabrik options (ACLs, forward only, forwarders, RPZ, version) grouped at the end. allow-query { localhost; } and the loopback-only listen-on are deliberately not taken over. bind__dnssec_validation (default true) renders dnssec-validation. Answers for zones forwarded to or stubbed from internal servers fail validation below a signed parent: host.corp.internal and host.corp.isc.org forwarded to a local named return SERVFAIL on bind 9.11.36, 9.16.23 and 9.18.33. The forward, stub and static-stub zones of bind__zones and the new bind__dnssec_validate_except therefore go into validate-except. BIND 9.11 rejects validate-except (unknown option), so on RHEL 8 the role asserts that nothing needs excluding. The crypto policy's disable-algorithms/disable-ds-digests only feed the validator and trust anchor loading (bin/named/server.c, lib/dns/validator.c, lib/ns/query.c in 9.18.33), so the include takes effect together with validation. Verified with the bind playbook on Rocky 8, 9 and 10 ubi-init containers: named active, local zone NOERROR, forwarded zones NOERROR, dnssec-failed.org SERVFAIL, isc.org with the ad flag, both answered without validation when bind__dnssec_validation is false; the EL 8 host with forward zones stops at the assert; rndc secroots/recursing write to /var/named/data; second run changed=0.
BIND does not create an automatic empty zone (RFC 6303) for a name it
would forward with policy `only` (bin/named/server.c in 9.18.33), and
the global `forward only` plus forwarders covers every name. named
therefore created none of its 97/98 empty zones, and reverse lookups
for private and special-use addresses went to the forwarders: a logging
forwarder received 5.2.42.10.in-addr.arpa, 7.7.168.192.in-addr.arpa and
1.0.16.172.in-addr.arpa on bind 9.11.36, 9.16.23 and 9.18.33, also with
a 1.42.10.in-addr.arpa zone in bind__zones. `empty-zones-enable yes`
does not change that; `forward first` does, but falls back to iterative
resolution when the forwarders fail.
Render a `type forward; forwarders { };` zone for each name of BIND's
empty_zones[] list, which switches forwarding off for that name only and
lets named create the empty zone. resolver.arpa is only in the 9.18
list, 0.in-addr.arpa and the ::1 reverse zone are defined by
named.rfc1912.zones. Skipped are names that bind__zones defines (a
duplicate zone is an error) and the parents of forward and stub zones
in bind__zones, since an empty zone answered 10.99.0.1 with NXDOMAIN
instead of passing it to the forwarders of a 99.10.in-addr.arpa zone.
Verified on Rocky 8, 9 and 10 against a logging forwarder: private
reverse lookups (10/8, 172.17/16, 100.64/10, 169.254/16, fd00::/8,
fe80::/10) answered NXDOMAIN from the empty zones, master zones in
bind__zones answered from their files, a forward zone and its parent
still forwarded, public PTR and A lookups still forwarded, never
resolved iteratively. The bind playbook answers `dig -x 10.1.2.3` from
10.IN-ADDR.ARPA and the second run reports changed=0.
…-secondary example
NavidSassan
force-pushed
the
fix/bind-dnssec
branch
from
October 2, 2026 09:18
79d9b83 to
df3e348
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The
bindrole validates DNSSEC and follows the system-wide crypto policy, like thenamed.confthe bind package ships on RHEL 8, 9 and 10. Breaking, see below. Along the way the role gets a Molecule scenario, which found a regression forslavezones.dnssec-validation yesby default, switchable withbind__dnssec_validation. Answers for zones that named forwards to or stubs from internal servers fail validation below a signed parent (.internal, or a subdomain of a signed public domain), so theforward,stubandstatic-stubzones inbind__zonesgo intovalidate-exceptautomatically; other zones can be listed inbind__dnssec_validate_except. BIND 9.11 on RHEL 8 has novalidate-except, so the role stops there with a clear message if anything needs excluding. The check runs in its own validation block taggedalways.include "/etc/crypto-policies/back-ends/bind.config", plussecroots-file,recursing-file,geoip-directory(RHEL 9/10) anddnssec-enable yes(RHEL 8). The template follows the packaged file's statements and order, with the Linuxfabrik options (ACLs,forward only, forwarders, RPZ,version) grouped at the end.allow-query { localhost; }and the loopback-onlylisten-on/listen-on-v6are deliberately not taken over; the README documents this under "How the Role Behaves" and as "Deviates from the upstream default" onbind__listen_on_addressesandbind__listen_ipv6.disable-empty-zone yes;, which takes a zone name, not a boolean, and had no effect.forward onlykept named from creating any of them, so these lookups went to the forwarders. The template switches forwarding off for exactly these zones (type forward; forwarders { };), except for zones thatbind__zonesdefines and parents offorward,static-stubandstubzones inbind__zones. Without recursion named creates no empty zones at all, so the blocks are only rendered withbind__recursion: true.type: 'slave'get theirfilestatement again. b9a9df6 leftslaveout of the list of types that take one, so such a secondary kept the transferred zone in memory only and could not answer it after a restart until the primary was reachable. The README's primary-secondary example also usedtype: 'master'for a secondary zone.meta/argument_specs.ymldeclares all user-facing variables.roles/exampleand CONTRIBUTING.md:| boolon every boolean in the templates and tasks,| length > 0in conditionals,ansible.builtin.serviceinstead ofansible.builtin.systemd.Test
New Molecule scenario
extensions/molecule/bindon Rocky 8, 9 and 10 VMs: converge, verify, idempotence, verify, all green. The three hosts form a ring, each is the primary of its own zone and the secondary of a neighbour's zone (master/slaveon Rocky 8,primary/secondaryon Rocky 9/10); Rocky 9 and 10 also forward a third host's zone below the undelegated.exampleTLD.verify.ymlasks the running named withdig:aaaaand the primary's data, zone copy saved under/var/named(this check caught theslaveregression on Rocky 8)aa, which needs the zone invalidate-except-x 10.1.2.3: NXDOMAIN withaafrom the local empty zonednssec-failed.org: SERVFAIL;isc.org:adflagBefore the scenario existed, verified by hand with the
bindplaybook against Rocky 8, 9 and 10ubi-initcontainers:bind__dnssec_validation: false: forward zones anddnssec-failed.orgresolvenamed-checkconf -pshows the policy'sdisable-algorithms,rndc secroots/rndc recursingwrite to/var/named/data/disable-empty-zoneremoval: identical automatic empty zones before and after on BIND 9.11, 9.16 and 9.18, with a control that drops one zonebind__zonesanswered from their files, a forward zone below172.16.0.0/16and its parent still forwarded, public lookups still forwarded and never resolved iterativelyNot covered: a real RHEL host, and the RHEL 8 assert since it moved into the
alwaysblock.Breaking Changes
named validates DNSSEC, so forged or broken answers for signed zones are answered with
SERVFAIL. Internal zones below a signed domain or TLD that named does not get from aforward,static-stuborstubzone inbind__zones(for example viabind__named_conf_raw) have to be listed inbind__dnssec_validate_except, or setbind__dnssec_validation: false. On RHEL 8 with forward or stub zones, setbind__dnssec_validation: false.