From: Remi Gacogne Date: Thu, 12 Nov 2020 15:51:38 +0000 (+0100) Subject: rec: Implement Extended DNS Errors for RPZ hits X-Git-Tag: auth-4.4.0-beta1~2^2~7 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=af231cebe66a26073feee3e1e293ba814b99e398;p=thirdparty%2Fpdns.git rec: Implement Extended DNS Errors for RPZ hits --- diff --git a/pdns/filterpo.hh b/pdns/filterpo.hh index bc91ed9e4b..971aabd476 100644 --- a/pdns/filterpo.hh +++ b/pdns/filterpo.hh @@ -79,6 +79,8 @@ public: /* shared by all the policies from a single zone */ std::unordered_set d_tags; std::string d_name; + std::string d_extendedErrorExtra; + boost::optional 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; diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index e9589c2bd3..24e2e27e58 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -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. diff --git a/pdns/rec-lua-conf.cc b/pdns/rec-lua-conf.cc index 0481c8a0cc..c3e37db02e 100644 --- a/pdns/rec-lua-conf.cc +++ b/pdns/rec-lua-conf.cc @@ -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> > > rpzOptions_t; +typedef std::unordered_map> > > rpzOptions_t; -static void parseRPZParameters(rpzOptions_t& have, std::string& polName, boost::optional& defpol, bool& defpolOverrideLocal, uint32_t& maxTTL, size_t& zoneSizeHint, std::unordered_set& tags, bool& overridesGettag) +static void parseRPZParameters(rpzOptions_t& have, std::shared_ptr& zone, std::string& polName, boost::optional& defpol, bool& defpolOverrideLocal, uint32_t& maxTTL) { - if(have.count("policyName")) { + if (have.count("policyName")) { polName = boost::get(have["policyName"]); } - if(have.count("defpol")) { + if (have.count("defpol")) { defpol=DNSFilterEngine::Policy(); defpol->d_kind = (DNSFilterEngine::PolicyKind)boost::get(have["defpol"]); defpol->setName(polName); @@ -76,20 +76,31 @@ static void parseRPZParameters(rpzOptions_t& have, std::string& polName, boost:: defpolOverrideLocal = boost::get(have["defpolOverrideLocalData"]); } } - if(have.count("maxTTL")) { + if (have.count("maxTTL")) { maxTTL = boost::get(have["maxTTL"]); } - if(have.count("zoneSizeHint")) { - zoneSizeHint = static_cast(boost::get(have["zoneSizeHint"])); + if (have.count("zoneSizeHint")) { + auto zoneSizeHint = static_cast(boost::get(have["zoneSizeHint"])); + if (zoneSizeHint > 0) { + zone->reserve(zoneSizeHint); + } } if (have.count("tags")) { const auto tagsTable = boost::get>>(have["tags"]); + std::unordered_set tags; for (const auto& tag : tagsTable) { tags.insert(tag.second); } + zone->setTags(std::move(tags)); } if (have.count("overridesGettag")) { - overridesGettag = boost::get(have["overridesGettag"]); + zone->setPolicyOverridesGettag(boost::get(have["overridesGettag"])); + } + if (have.count("extendedErrorCode")) { + zone->setExtendedErrorCode(boost::get(have["extendedErrorCode"])); + if (have.count("extendedErrorExtra")) { + zone->setExtendedErrorExtra(boost::get(have["extendedErrorExtra"])); + } } } @@ -244,20 +255,13 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de std::string polName("rpzFile"); std::shared_ptr zone = std::make_shared(); uint32_t maxTTL = std::numeric_limits::max(); - bool overridesGettag = true; - if(options) { + zone->setPolicyOverridesGettag(true); + if (options) { auto& have = *options; - size_t zoneSizeHint = 0; - std::unordered_set 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<setName(polName); - zone->setPolicyOverridesGettag(overridesGettag); loadRPZFromFile(filename, zone, defpol, defpolOverrideLocal, maxTTL); lci.dfe.addZone(zone); g_log< 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(have["tsigname"]))); diff --git a/pdns/recursordist/docs/lua-config/rpz.rst b/pdns/recursordist/docs/lua-config/rpz.rst index 73d7475642..d7668ba1b5 100644 --- a/pdns/recursordist/docs/lua-config/rpz.rst +++ b/pdns/recursordist/docs/lua-config/rpz.rst @@ -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. diff --git a/pdns/recursordist/docs/lua-scripting/dq.rst b/pdns/recursordist/docs/lua-scripting/dq.rst index ae03421be5..f92cd2b205 100644 --- a/pdns/recursordist/docs/lua-scripting/dq.rst +++ b/pdns/recursordist/docs/lua-scripting/dq.rst @@ -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. diff --git a/pdns/recursordist/docs/settings.rst b/pdns/recursordist/docs/settings.rst index 692a28ce45..e3055d03d6 100644 --- a/pdns/recursordist/docs/settings.rst +++ b/pdns/recursordist/docs/settings.rst @@ -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: diff --git a/regression-tests.recursor-dnssec/test_ExtendedErrors.py b/regression-tests.recursor-dnssec/test_ExtendedErrors.py index d14672069f..357f762d2b 100644 --- a/regression-tests.recursor-dnssec/test_ExtendedErrors.py +++ b/regression-tests.recursor-dnssec/test_ExtendedErrors.py @@ -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)