Skip to content

Commit

Permalink
auth: allow turning off across-zone resolving
Browse files Browse the repository at this point in the history
Default is unchanged. Turning off the new setting causes CNAME targets
to not be followed across (local) zones. Also, queries that could be
answered by following a local delegations are similarly not resolved.
  • Loading branch information
zeha committed Nov 19, 2024
1 parent 42c3a1e commit 39a5e36
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 2 deletions.
17 changes: 17 additions & 0 deletions docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,23 @@ the server will return NODATA for A/AAAA queries for such names.
In PowerDNS Authoritative Server 4.0.x, this setting did not exist and
ALIAS was always expanded.

.. _setting-resolve-across-zones:

``resolve-across-zones``
------------------------

.. versionadded:: 5.0.0

- Boolean
- Default: yes

If this is enabled, CNAME records and other referrals will be resolved as long as their targets exist in any local backend.
Can be disabled to allow for different authorities managing zones in the same server instance.

Referrals not available in local backends are never resolved.
SVCB referrals are never resolved across zones.
ALIAS is not impacted by this setting.

.. _setting-forward-dnsupdate:

``forward-dnsupdate``
Expand Down
1 change: 1 addition & 0 deletions pdns/auth-main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ static void declareArguments()

::arg().setSwitch("expand-alias", "Expand ALIAS records") = "no";
::arg().set("outgoing-axfr-expand-alias", "Expand ALIAS records during outgoing AXFR") = "no";
::arg().setSwitch("resolve-across-zones", "Resolve CNAME targets and other referrals across local zones") = "yes";
::arg().setSwitch("8bit-dns", "Allow 8bit dns queries") = "no";
#ifdef HAVE_LUA_RECORDS
::arg().setSwitch("enable-lua-records", "Process LUA records for all zones (metadata overrides this)") = "no";
Expand Down
10 changes: 8 additions & 2 deletions pdns/packethandler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ PacketHandler::PacketHandler():B(g_programname), d_dk(&B)
++s_count;
d_doDNAME=::arg().mustDo("dname-processing");
d_doExpandALIAS = ::arg().mustDo("expand-alias");
d_doResolveAcrossZones = ::arg().mustDo("resolve-across-zones");
d_logDNSDetails= ::arg().mustDo("log-dns-details");
string fname= ::arg()["lua-prequery-script"];

Expand Down Expand Up @@ -1526,12 +1527,17 @@ std::unique_ptr<DNSPacket> PacketHandler::doQuestion(DNSPacket& p)
}
DLOG(g_log<<Logger::Error<<"We have authority, zone='"<<d_sd.qname<<"', id="<<d_sd.domain_id<<endl);

if (retargetcount == 0) {
r->qdomainzone = d_sd.qname;
} else if (!d_doResolveAcrossZones && r->qdomainzone != d_sd.qname) {
// We are following a retarget outside the initial zone. Config asked us not to do that.
goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
}

authSet.insert(d_sd.qname);
d_dnssec=(p.d_dnssecOk && d_dk.isSecuredZone(d_sd.qname));
doSigs |= d_dnssec;

if(!retargetcount) r->qdomainzone=d_sd.qname;

if(d_sd.qname==p.qdomain) {
if(!d_dk.isPresigned(d_sd.qname)) {
if(p.qtype.getCode() == QType::DNSKEY)
Expand Down
1 change: 1 addition & 0 deletions pdns/packethandler.hh
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ private:
bool d_logDNSDetails;
bool d_doDNAME;
bool d_doExpandALIAS;
bool d_doResolveAcrossZones;
bool d_dnssec{false};
SOAData d_sd;
std::unique_ptr<AuthLua4> d_pdl;
Expand Down
105 changes: 105 additions & 0 deletions regression-tests.auth-py/test_ResolveAcrossZones.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/usr/bin/env python

from __future__ import print_function

import dns

from authtests import AuthTest


class CrossZoneResolveBase(AuthTest):
_config_template = """
any-to-tcp=no
launch=bind
edns-subnet-processing=yes
"""
target_otherzone_ip = "192.0.2.2"
target_subzone_ip = "192.0.2.3"
_zones = {
"example.org": """
example.org. 3600 IN SOA {soa}
example.org. 3600 IN NS ns1.example.org.
example.org. 3600 IN NS ns2.example.org.
ns1.example.org. 3600 IN A {prefix}.10
ns2.example.org. 3600 IN A {prefix}.11
subzone.example.org. 3600 IN NS ns1.example.org.
subzone.example.org. 3600 IN NS ns2.example.org.
cname-otherzone.example.org. 3600 IN CNAME target.example.com.
cname-subzone.example.org. 3600 IN CNAME target.subzone.example.org.
""",
"subzone.example.org": """
subzone.example.org. 3600 IN SOA {soa}
target.subzone.example.org. 3600 IN A """
+ target_subzone_ip,
"example.com": """
example.com. 3600 IN SOA {soa}
example.com. 3600 IN NS ns1.example.com.
example.com. 3600 IN NS ns2.example.com.
ns1.example.com. 3600 IN A {prefix}.10
ns2.example.com. 3600 IN A {prefix}.11
target.example.com. 3600 IN A """
+ target_otherzone_ip,
}


class TestCrossZoneResolveOff(CrossZoneResolveBase):
_config_template = (
CrossZoneResolveBase._config_template
+ """
resolve-across-zones=no
"""
)

def impl_cname_only_test(self, qname, target):
expected_cname = dns.rrset.from_text(
qname, 0, dns.rdataclass.IN, "CNAME", target
)
query = dns.message.make_query(qname, "A")
res = self.sendUDPQuery(query)
self.assertRcodeEqual(res, dns.rcode.NOERROR)
assert res.answer == [expected_cname]
assert res.additional == []

def testCNAMEOtherZone(self):
self.impl_cname_only_test("cname-otherzone.example.org.", "target.example.com.")

def testCNAMESubZone(self):
self.impl_cname_only_test(
"cname-subzone.example.org.", "target.subzone.example.org."
)


class TestCrossZoneResolveOn(CrossZoneResolveBase):
_config_template = (
CrossZoneResolveBase._config_template
+ """
resolve-across-zones=yes
"""
)

def impl_cname_and_target_test(self, qname, target, target_ip):
expected_cname = dns.rrset.from_text(
qname, 0, dns.rdataclass.IN, "CNAME", target
)
expected_target = dns.rrset.from_text(
target, 0, dns.rdataclass.IN, "A", target_ip
)
query = dns.message.make_query(qname, "A")
res = self.sendUDPQuery(query)
self.assertRcodeEqual(res, dns.rcode.NOERROR)
assert res.answer == [expected_cname, expected_target]
assert res.additional == []

def testCNAMEOtherZone(self):
self.impl_cname_and_target_test(
"cname-otherzone.example.org.",
"target.example.com.",
self.target_otherzone_ip,
)

def testCNAMESubZone(self):
self.impl_cname_and_target_test(
"cname-subzone.example.org.",
"target.subzone.example.org.",
self.target_subzone_ip,
)

0 comments on commit 39a5e36

Please sign in to comment.