]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
rec: Document the new RPZ behaviour, objects and hooks
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 24 Aug 2020 13:59:23 +0000 (15:59 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 24 Aug 2020 15:29:41 +0000 (17:29 +0200)
.github/actions/spell-check/expect.txt
pdns/recursordist/docs/lua-config/rpz.rst
pdns/recursordist/docs/lua-scripting/hooks.rst
pdns/recursordist/docs/lua-scripting/index.rst
pdns/recursordist/docs/lua-scripting/policyevent.rst [new file with mode: 0644]

index 2775eb9554723edc7ba034e880f87e62b0eb2206..616ee9e8ebc8a3e9f85c6b93e646a8246fdd673f 100644 (file)
@@ -1472,6 +1472,7 @@ Poelov
 pointsize
 polarssl
 policyactions
+policyevent
 policykinds
 policyname
 pollmplexer
index 48836bb144b6b93d83ad6a894175e08407ede0d0..1e352468356fa2cf89c3b1238a3279f83085f581 100644 (file)
@@ -8,10 +8,23 @@ Response Policy Zone is an open standard developed by Paul Vixie (ISC and Farsig
 Frequently, Response Policy Zones get to be very large and change quickly, so it is customary to update them over IXFR.
 It allows the use of third-party feeds, and near real-time policy updates.
 
+Evaluation order
+----------------
+
 If multiple RPZs are loaded, they get consulted in the order they were
 defined in. It is however possible from Lua to make queries skip specific
 Response Policy Zones.
 
+The evaluation order of RPZ policies is not always straightforward. Before 4.4.0, the recursor first checked whether the source address of the client matched a "Client IP Address" filter
+in any RPZ zones, then if the qname matched a "QNAME" trigger. It would then start the regular resolution process and check whether any "NSDNAME" or "NSIP" rule was triggered, then after the resolution process was done check whether any of the final records matched a "Response IP Address" rule.
+It would stop as soon as a match was found and apply the requested decision immediately, unless the decision was a "passthru". In that last case it would resume the normal processing but would only evaluate the rules coming from a policy with a higher order than the one that matched.
+
+Since 4.4.0 the behaviour is a bit different, to better follow the RPZ specifications. The source address of the client is still checked first. Then the normal resolution process starts and the initial qname as well as any CNAME part of the chain starting from the qname is checked against "QNAME" rules. "NSDNAME" and "NSIP" rules are still checked during the remaining part of the process, and "Response IP Address" rules are applied to the final records in the end.
+This matches the precedence rules from the RPZ specifications that specify that "A policy rule match which occurs at an earlier stage of resolution is preferred to a policy rule match which occurs at a later stage".
+For compatibility reasons, 4.4.0 still evaluates policy rules of higher order after a "passthru", even though the section 5.1 of the RPZ specifications seems to deny that behaviour: "Recall that only one policy rule, from among all those matched at all stages of resolving a CNAME or DNAME chain, can affect the final response; this is true even if the selected rule has a PASSTHRU action".
+
+Note that "RPZ rules do not apply to synthetic data generated by using RPZ rules. For example, if RPZ supplies a CNAME pointing to a walled garden, RPZ policies will not be used while following that CNAME. If RPZ supplies local data giving a particular A record, RPZ policies will not apply to that response IP address", as stated in section 6.1 of the RPZ specifications.
+
 Configuring RPZ
 ---------------
 An RPZ can be loaded from file or slaved from a master. To load from file, use for example:
index ddd9c6664a75999837889784bac05c85d9a40b86..dc5061ebbdde007e2a7561ca4b568bfe858d7c25 100644 (file)
@@ -1,5 +1,6 @@
 Intercepting queries with Lua
 =============================
+
 To get a quick start, we have supplied a sample script that showcases all functionality described below.
 Please find it `here <https://github.com/PowerDNS/pdns/blob/master/pdns/recursordist/contrib/powerdns-example-script.lua>`_.
 
@@ -12,6 +13,7 @@ Queries can be intercepted in many places:
 -  after the resolving process failed to find a correct answer for a domain (:func:`nodata`, :func:`nxdomain`)
 -  after the whole process is done and an answer is ready for the client (:func:`postresolve`)
 -  before an outgoing query is made to an authoritative server (:func:`preoutquery`)
+-  after a filtering policy hit has occurred (:func:`policyEventFilter`)
 
 Writing Lua PowerDNS Recursor scripts
 -------------------------------------
@@ -169,6 +171,43 @@ Interception Functions
 
   :param DNSQuestion dq: The DNS question to handle
 
+.. function:: policyEventFilter(event)
+
+    .. versionadded:: 4.4.0
+
+  This hook is called when a filtering policy has been hit, before the decision has been applied, making it possible to change a policy decision by altering its content or to skip it entirely.
+  Using the :meth:`event:discardPolicy() <PolicyEvent:discardPolicy>` function, it is also possible to selectively disable one or more filtering policy, for example RPZ zones.
+  The return value indicates whether the policy hit should be completely ignored (true) or applied (false), possibly after editing the action to take in that latter case (see :ref:`modifyingpolicydecisions` below). when true is returned, the resolution process will resume as if the policy hit never took place.
+
+  As an example, to ignore the result of a policy hit for the example.com domain:
+
+  .. code-block:: Lua
+
+      function policyEventFilter(event)
+        if event.qname:equal("example.com") then
+          -- ignore that policy hit
+          return true
+        end
+        return false
+      end
+
+  To alter the decision of the policy hit instead:
+
+  .. code-block:: Lua
+
+      function policyEventFilter(event)
+        if event.qname:equal("example.com") then
+          -- replace the decision with a custom CNAME
+          event.appliedPolicy.policyKind = pdns.policykinds.Custom
+          event.appliedPolicy.policyCustom = "example.net"
+          -- returning false so that the hit is not ignored
+          return false
+        end
+        return false
+      end
+
+  :param :class:`PolicyEvent` event: The event to handle
+
 Semantics
 ^^^^^^^^^
 The functions must return ``true`` if they have taken over the query and wish that the nameserver should not proceed with its regular query-processing.
@@ -179,6 +218,8 @@ An interesting rcode is NXDOMAIN (3, or ``pdns.NXDOMAIN``), which specifies the
 
 The :func:`ipfilter` and :func:`preoutquery` hooks are different, in that :func:`ipfilter` can only return a true of false value, and that :func:`preoutquery` can also set rcode -3 to signify that the whole query should be terminated.
 
+The func:`policyEventFilter` has a different meaning as well, where returning true means that the policy hit should be ignored and normal processing should be resumed.
+
 A minimal sample script:
 
 .. code-block:: Lua
@@ -289,7 +330,7 @@ The PowerDNS Recursor has a :doc:`policy engine based on Response Policy Zones (
 Starting with version 4.0.1 of the recursor, it is possible to alter this decision inside the Lua hooks.
 
 If the decision is modified in a Lua hook, ``false`` should be returned, as the query is not actually handled by Lua so the decision is picked up by the Recursor.
-The result of the policy decision is checked after :func:`preresolve` and :func:`postresolve`.
+The result of the policy decision is checked after :func:`preresolve` and :func:`postresolve` before 4.4.0. Beginning with version 4.4.0, the policy decision is checked after :func:`preresolve` and any :func:`policyEventFilter` call instead.
 
 For example, if a decision is set to ``pdns.policykinds.NODATA`` by the policy engine and is unchanged in :func:`preresolve`, the query is replied to with a NODATA response immediately after :func:`preresolve`.
 
index 45f0ddc9083ee86b0a049346ef163a299906c835..d03a9541771b65fc69c56408b4587ecfec68c059 100644 (file)
@@ -22,6 +22,7 @@ For extra performance, a Just In Time compiled version of Lua called `LuaJIT <ht
     dnsrecord
     comboaddress
     netmask
+    policyevent
     statistics
     logging
     hooks
diff --git a/pdns/recursordist/docs/lua-scripting/policyevent.rst b/pdns/recursordist/docs/lua-scripting/policyevent.rst
new file mode 100644 (file)
index 0000000..4a3de04
--- /dev/null
@@ -0,0 +1,85 @@
+.. _scripting-policyevent:
+
+Policy Events
+=============
+
+Since 4.4.0, the Lua hook :func:`policyEventFilter` is called along with a :class:`PolicyEvent` object whenever a filtering policy matches.
+
+PolicyEvent class
+------------------
+
+.. class:: PolicyEvent
+
+  Represents an event related to a filtering policy.
+
+  .. method:: PolicyEvent:addPolicyTag(tag)
+
+     Add policyTag ``tag`` to the list of policyTags.
+
+     :param str tag: The tag to add
+
+  .. method:: PolicyEvent:getPolicyTags() -> {str}
+
+      Get the current policy tags as a table of strings.
+
+  .. method:: PolicyEvent:setPolicyTags(tags)
+
+      Set the policy tags to ``tags``, overwriting any existing policy tags.
+
+      :param {str} tags: The policy tags
+
+  .. method:: PolicyEvent:discardPolicy(policyname)
+
+     Skip the filtering policy (for example RPZ) named ``policyname`` for this query.
+
+     :param str policyname: The name of the policy to ignore.
+
+  .. attribute:: PolicyEvent.appliedPolicy
+
+    The decision that was made by the policy engine, see :ref:`modifyingpolicydecisions`.
+
+    .. attribute:: PolicyEvent.appliedPolicy.policyName
+
+      A string with the name of the policy.
+      Set by :ref:`policyName <rpz-policyName>` in the :func:`rpzFile` and :func:`rpzMaster` configuration items.
+      It is advised to overwrite this when modifying the :attr:`PolicyEvent.appliedPolicy.policyKind`
+
+    .. attribute:: PolicyEvent.appliedPolicy.policyAction
+
+        The action taken by the engine
+
+    .. attribute:: PolicyEvent.appliedPolicy.policyCustom
+
+        The CNAME content for the ``pdns.policyactions.Custom`` response, a string
+
+    .. attribute:: PolicyEvent.appliedPolicy.policyKind
+
+      The kind of policy response, there are several policy kinds:
+
+      -  ``pdns.policykinds.Custom`` will return a NoError, CNAME answer with the value specified in :attr:`PolicyEvent.appliedPolicy.policyCustom`
+      -  ``pdns.policykinds.Drop`` will simply cause the query to be dropped
+      -  ``pdns.policykinds.NoAction`` will continue normal processing of the query
+      -  ``pdns.policykinds.NODATA`` will return a NoError response with no value in the answer section
+      -  ``pdns.policykinds.NXDOMAIN`` will return a response with a NXDomain rcode
+      -  ``pdns.policykinds.Truncate`` will return a NoError, no answer, truncated response over UDP. Normal processing will continue over TCP
+
+    .. attribute:: PolicyEvent.appliedPolicy.policyTTL
+
+        The TTL in seconds for the ``pdns.policyactions.Custom`` response
+
+  .. attribute:: PolicyEvent.qname
+
+      :class:`DNSName` of the name the query is for.
+
+  .. attribute:: PolicyEvent.qtype
+
+      Type the query is for as an integer, can be compared against ``pdns.A``, ``pdns.AAAA``.
+
+  .. attribute:: PolicyEvent.isTcp
+
+      Whether the query was received over TCP.
+
+  .. attribute:: PolicyEvent.remote
+
+      :class:`ComboAddress` of the requestor.
+