]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
rec: Implement Extended DNS Errors for RPZ hits
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 12 Nov 2020 15:51:38 +0000 (16:51 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 18 Nov 2020 09:08:55 +0000 (10:08 +0100)
pdns/filterpo.hh
pdns/pdns_recursor.cc
pdns/rec-lua-conf.cc
pdns/recursordist/docs/lua-config/rpz.rst
pdns/recursordist/docs/lua-scripting/dq.rst
pdns/recursordist/docs/settings.rst
regression-tests.recursor-dnssec/test_ExtendedErrors.py

index bc91ed9e4b3ae4d564c29f3ab744404c803b03b2..971aabd4760d48a0e419c06b79d8c0c93cd4589d 100644 (file)
@@ -79,6 +79,8 @@ public:
     /* shared by all the policies from a single zone */
     std::unordered_set<std::string> d_tags;
     std::string d_name;
+    std::string d_extendedErrorExtra;
+    boost::optional<uint16_t> d_extendedErrorCode{boost::none};
     Priority d_priority{maximumPriority};
     bool d_policyOverridesGettag{true};
   };
@@ -211,6 +213,15 @@ public:
     {
       d_zoneData->d_policyOverridesGettag = flag;
     }
+    void setExtendedErrorCode(uint16_t code)
+    {
+      d_zoneData->d_extendedErrorCode = code;
+    }
+    void setExtendedErrorExtra(const std::string& extra)
+    {
+      d_zoneData->d_extendedErrorExtra = extra;
+    }
+
     const std::string& getName() const
     {
       return d_zoneData->d_name;
index e9589c2bd3c61dea8be636ca63ccc6558366e7c5..24e2e27e58136f875cf7b7419511252503d22e80 100644 (file)
@@ -983,6 +983,11 @@ static PolicyResult handlePolicyHit(const DNSFilterEngine::Policy& appliedPolicy
     g_log << Logger::Warning << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype).getName() << appliedPolicy.getLogString() << endl;
   }
 
+  if (appliedPolicy.d_zoneData && appliedPolicy.d_zoneData->d_extendedErrorCode) {
+    dc->d_extendedErrorCode = *appliedPolicy.d_zoneData->d_extendedErrorCode;
+    dc->d_extendedErrorExtra = appliedPolicy.d_zoneData->d_extendedErrorExtra;
+  }
+
   switch (appliedPolicy.d_kind) {
 
   case DNSFilterEngine::PolicyKind::NoAction:
@@ -1670,6 +1675,10 @@ static void startDoResolve(void *p)
       dq.validationState = sr.getValidationState();
       appliedPolicy = sr.d_appliedPolicy;
       dc->d_policyTags = std::move(sr.d_policyTags);
+      if (appliedPolicy.d_type != DNSFilterEngine::PolicyType::None && appliedPolicy.d_zoneData && appliedPolicy.d_zoneData->d_extendedErrorCode) {
+        dc->d_extendedErrorCode = *appliedPolicy.d_zoneData->d_extendedErrorCode;
+        dc->d_extendedErrorExtra = appliedPolicy.d_zoneData->d_extendedErrorExtra;
+      }
 
       // During lookup, an NSDNAME or NSIP trigger was hit in RPZ
       if (res == -2) { // XXX This block should be macro'd, it is repeated post-resolve.
index 0481c8a0cc06b11d8ef02b8af125a0c4c852660c..c3e37db02e3244b99d1879cfb8ecc8d12b7b6c21 100644 (file)
@@ -51,14 +51,14 @@ typename C::value_type::second_type constGet(const C& c, const std::string& name
   return iter->second;
 }
 
-typedef std::unordered_map<std::string, boost::variant<bool, uint32_t, std::string, std::vector<std::pair<int, std::string>> > > rpzOptions_t;
+typedef std::unordered_map<std::string, boost::variant<bool, uint16_t, uint32_t, std::string, std::vector<std::pair<int, std::string>> > > rpzOptions_t;
 
-static void parseRPZParameters(rpzOptions_t& have, std::string& polName, boost::optional<DNSFilterEngine::Policy>& defpol, bool& defpolOverrideLocal, uint32_t& maxTTL, size_t& zoneSizeHint, std::unordered_set<std::string>& tags, bool& overridesGettag)
+static void parseRPZParameters(rpzOptions_t& have, std::shared_ptr<DNSFilterEngine::Zone>& zone, std::string& polName, boost::optional<DNSFilterEngine::Policy>& defpol, bool& defpolOverrideLocal, uint32_t& maxTTL)
 {
-  if(have.count("policyName")) {
+  if (have.count("policyName")) {
     polName = boost::get<std::string>(have["policyName"]);
   }
-  if(have.count("defpol")) {
+  if (have.count("defpol")) {
     defpol=DNSFilterEngine::Policy();
     defpol->d_kind = (DNSFilterEngine::PolicyKind)boost::get<uint32_t>(have["defpol"]);
     defpol->setName(polName);
@@ -76,20 +76,31 @@ static void parseRPZParameters(rpzOptions_t& have, std::string& polName, boost::
       defpolOverrideLocal = boost::get<bool>(have["defpolOverrideLocalData"]);
     }
   }
-  if(have.count("maxTTL")) {
+  if (have.count("maxTTL")) {
     maxTTL = boost::get<uint32_t>(have["maxTTL"]);
   }
-  if(have.count("zoneSizeHint")) {
-    zoneSizeHint = static_cast<size_t>(boost::get<uint32_t>(have["zoneSizeHint"]));
+  if (have.count("zoneSizeHint")) {
+    auto zoneSizeHint = static_cast<size_t>(boost::get<uint32_t>(have["zoneSizeHint"]));
+    if (zoneSizeHint > 0) {
+      zone->reserve(zoneSizeHint);
+    }
   }
   if (have.count("tags")) {
     const auto tagsTable = boost::get<std::vector<std::pair<int, std::string>>>(have["tags"]);
+    std::unordered_set<std::string> tags;
     for (const auto& tag : tagsTable) {
       tags.insert(tag.second);
     }
+    zone->setTags(std::move(tags));
   }
   if (have.count("overridesGettag")) {
-    overridesGettag = boost::get<bool>(have["overridesGettag"]);
+    zone->setPolicyOverridesGettag(boost::get<bool>(have["overridesGettag"]));
+  }
+  if (have.count("extendedErrorCode")) {
+    zone->setExtendedErrorCode(boost::get<uint16_t>(have["extendedErrorCode"]));
+    if (have.count("extendedErrorExtra")) {
+      zone->setExtendedErrorExtra(boost::get<std::string>(have["extendedErrorExtra"]));
+    }
   }
 }
 
@@ -244,20 +255,13 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
         std::string polName("rpzFile");
         std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
         uint32_t maxTTL = std::numeric_limits<uint32_t>::max();
-        bool overridesGettag = true;
-        if(options) {
+        zone->setPolicyOverridesGettag(true);
+        if (options) {
           auto& have = *options;
-          size_t zoneSizeHint = 0;
-          std::unordered_set<std::string> tags;
-          parseRPZParameters(have, polName, defpol, defpolOverrideLocal, maxTTL, zoneSizeHint, tags, overridesGettag);
-          if (zoneSizeHint > 0) {
-            zone->reserve(zoneSizeHint);
-          }
-          zone->setTags(std::move(tags));
+          parseRPZParameters(have, zone, polName, defpol, defpolOverrideLocal, maxTTL);
         }
         g_log<<Logger::Warning<<"Loading RPZ from file '"<<filename<<"'"<<endl;
         zone->setName(polName);
-        zone->setPolicyOverridesGettag(overridesGettag);
         loadRPZFromFile(filename, zone, defpol, defpolOverrideLocal, maxTTL);
         lci.dfe.addZone(zone);
         g_log<<Logger::Warning<<"Done loading RPZ from file '"<<filename<<"'"<<endl;
@@ -298,15 +302,8 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
 
         if (options) {
           auto& have = *options;
-          size_t zoneSizeHint = 0;
-          std::unordered_set<std::string> tags;
-          bool overridesGettag = true;
-          parseRPZParameters(have, polName, defpol, defpolOverrideLocal, maxTTL, zoneSizeHint, tags, overridesGettag);
-          if (zoneSizeHint > 0) {
-            zone->reserve(zoneSizeHint);
-          }
-          zone->setTags(std::move(tags));
-          zone->setPolicyOverridesGettag(overridesGettag);
+          zone->setPolicyOverridesGettag(true);
+          parseRPZParameters(have, zone, polName, defpol, defpolOverrideLocal, maxTTL);
 
           if(have.count("tsigname")) {
             tt.name=DNSName(toLower(boost::get<string>(have["tsigname"])));
index 73d7475642c5ff4aa337dead2ba986b5bc4e5bd5..d7668ba1b56b4684722a7fc941d75fdbea2fc10e 100644 (file)
@@ -111,6 +111,18 @@ defttl
 the TTL of the CNAME field to be synthesized for the default policy.
 The default is to use the zone's TTL,
 
+extendedErrorCode
+^^^^^^^^^^^^^^^^^
+.. versionadded:: 4.5.0
+
+An extended error code (:rfc:`8914`) to set on RPZ hits. See :ref:`extended-errors`.
+
+extendedErrorExtra
+^^^^^^^^^^^^^^^^^^
+.. versionadded:: 4.5.0
+
+An extended error extra text (:rfc:`8914`) to set on RPZ hits. See :ref:`extended-errors`.
+
 maxTTL
 ^^^^^^
 The maximum TTL value of the synthesized records, overriding a higher value from ``defttl`` or the zone. Default is unlimited.
index ae03421be5c64ddf985d4da96be4333c9e5373b1..f92cd2b205a54f5e737f5eb92f90be12c81b87ba 100644 (file)
@@ -11,6 +11,22 @@ The DNSQuestion object contains at least the following fields:
   An object that contains everything about the current query.
   This object has the following attributes:
 
+  .. attribute:: DNSQuestion.extendedErrorCode
+
+      .. versionadded:: 4.5.0
+
+      The current extended error code, if any. See :ref:`extended-errors`.
+
+  .. attribute:: DNSQuestion.extendedErrorExtra
+
+      .. versionadded:: 4.5.0
+
+      The current extended error extra text, as a string, if any. See :ref:`extended-errors`.
+
+  .. attribute:: DNSQuestion.qname
+
+      :class:`DNSName` of the name this query is for.
+
   .. attribute:: DNSQuestion.qname
 
       :class:`DNSName` of the name this query is for.
index 692a28ce45748972854ada7e77a9c17d7e026dee..e3055d03d6303d4df2eb013c5f4c6438f0709abd 100644 (file)
@@ -630,7 +630,7 @@ An entry called 'server1.home' will be stored as 'server1.home', regardless of t
 -  Boolean
 -  Default: no
 
-If set, the recursor will add an EDNS Extended Error to responses failing DNSSEC validation, explaining the failure.
+If set, the recursor will add an EDNS Extended Error (:rfc:`8914`) to responses failing DNSSEC validation, explaining the failure. Enabling this setting will also allow setting custom error codes from Lua or from a RPZ hit.
 
 .. _setting-forward-zones:
 
index d14672069fb65006038561747bcde632f0a9bfc4..357f762d2bfbb004bfcf724c1d839a706f85d572 100644 (file)
@@ -22,6 +22,9 @@ log-common-errors=yes
     _config_template = """
     extended-errors=yes
     """
+    _lua_config_file = """
+    rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", extendedErrorCode=15, extendedErrorExtra='Blocked by RPZ!'})
+    """ % (_confdir)
     _lua_dns_script_file = """
     function preresolve(dq)
       if dq.qname == newDN('fromlua.extended.') then
@@ -82,6 +85,13 @@ log-common-errors=yes
 
     @classmethod
     def generateRecursorConfig(cls, confdir):
+        rpzFilePath = os.path.join(confdir, 'zone.rpz')
+        with open(rpzFilePath, 'w') as rpzZone:
+            rpzZone.write("""$ORIGIN zone.rpz.
+@ 3600 IN SOA {soa}
+*.rpz.extended.zone.rpz. 60 IN CNAME .
+""".format(soa=cls._SOA))
+
         super(ExtendedErrorsRecursorTest, cls).generateRecursorConfig(confdir)
 
     def testNotIncepted(self):
@@ -111,7 +121,7 @@ log-common-errors=yes
             self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(7, b''))
 
     def testBogus(self):
-        qname = 'unknownalgorithm.bad-dnssec.wb.sidnlabs.nl.'
+        qname = 'bogussig.ok.bad-dnssec.wb.sidnlabs.nl.'
         query = dns.message.make_query(qname, 'A', want_dnssec=True)
 
         for method in ("sendUDPQuery", "sendTCPQuery"):
@@ -162,6 +172,19 @@ log-common-errors=yes
             self.assertEqual(res.options[0].otype, 15)
             self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(10, b'Extra text from Lua FFI!'))
 
+    def testRPZ(self):
+        qname = 'sub.rpz.extended.'
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query, timeout=5.0)
+            self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
+            self.assertEqual(res.edns, 0)
+            self.assertEqual(len(res.options), 1)
+            self.assertEqual(res.options[0].otype, 15)
+            self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(15, b'Blocked by RPZ!'))
+
     def testTooLarge(self):
         qname = 'toolarge.extended.'
         query = dns.message.make_query(qname, 'A', want_dnssec=True, payload=512)