]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
auth: allow turning off across-zone resolving
authorChris Hofstaedtler <chris.hofstaedtler@deduktiva.com>
Wed, 28 Aug 2024 09:52:31 +0000 (11:52 +0200)
committerChris Hofstaedtler <chris.hofstaedtler@deduktiva.com>
Tue, 19 Nov 2024 12:20:01 +0000 (13:20 +0100)
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.

docs/settings.rst
pdns/auth-main.cc
pdns/packethandler.cc
pdns/packethandler.hh
regression-tests.auth-py/test_ResolveAcrossZones.py [new file with mode: 0644]

index 35a65cfd9fcc3c84e1051a498c707917254f80f6..e1e2d974a33af969a4491bf46c42f88b6c36d4e9 100644 (file)
@@ -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``
index 72e05c9e6e7fd0b92b10172d6f8fad79468c4512..06d8742499696442200dddb8c656e98c87a01426 100644 (file)
@@ -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";
index d06e4724e127e8c153a22a5ec706e71affb02399..f2a570a274ba8f13848a705c9ac2001c9ddf1b28 100644 (file)
@@ -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"];
 
@@ -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)
index 716aa5a3df10871bba02a8625931293b16d35b5c..f1907721c9f8a33a37e75c5cef5f7895d0c4220b 100644 (file)
@@ -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;
diff --git a/regression-tests.auth-py/test_ResolveAcrossZones.py b/regression-tests.auth-py/test_ResolveAcrossZones.py
new file mode 100644 (file)
index 0000000..00207ef
--- /dev/null
@@ -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,
+        )