.. SPDX-License-Identifier: GPL-3.0-or-later

.. _mod-daf:

DNS Application Firewall
========================

This module is a high-level interface for other powerful filtering modules and DNS views. It provides an easy interface to apply and monitor DNS filtering rules and a persistent memory for them. It also provides a restful service interface and an HTTP interface.

Example configuration
---------------------

Firewall rules are declarative and consist of filters and actions. Filters have ``field operator operand`` notation (e.g. ``qname = example.com``), and may be chained using AND/OR keywords. Actions may or may not have parameters after the action name.

.. code-block:: lua

    -- Let's write some daft rules!
    modules = { 'daf' }

    -- Block all queries with QNAME = example.com
    daf.add('qname = example.com deny')

    -- Filters can be combined using AND/OR...
    -- Block all queries with QNAME match regex and coming from given subnet
    daf.add('qname ~ %w+.example.com AND src = 192.0.2.0/24 deny')

    -- We also can reroute addresses in response to alternate target
    -- This reroutes 192.0.2.1 to localhost
    daf.add('src = 127.0.0.0/8 reroute 192.0.2.1-127.0.0.1')

    -- Subnets work too, this reroutes a whole subnet
    -- e.g. 192.0.2.55 to 127.0.0.55
    daf.add('src = 127.0.0.0/8 reroute 192.0.2.0/24-127.0.0.0')

    -- This rewrites all A answers for 'example.com' from
    -- whatever the original address was to 127.0.0.2
    daf.add('src = 127.0.0.0/8 rewrite example.com A 127.0.0.2')

    -- Mirror queries matching given name to DNS logger
    daf.add('qname ~ %w+.example.com mirror 127.0.0.2')
    daf.add('qname ~ example-%d.com mirror 127.0.0.3@5353')

    -- Forward queries from subnet
    daf.add('src = 127.0.0.1/8 forward 127.0.0.1@5353')
    -- Forward to multiple targets
    daf.add('src = 127.0.0.1/8 forward 127.0.0.1@5353,127.0.0.2@5353')

    -- Truncate queries based on destination IPs
    daf.add('dst = 192.0.2.51 truncate')

    -- Disable a rule
    daf.disable(2)
    -- Enable a rule
    daf.enable(2)
    -- Delete a rule
    daf.del(2)

    -- Delete all rules and start from scratch
    daf.clear()

.. warning:: Only the first matching rule's action is executed.  Defining
   additional actions for the same matching rule, e.g.  ``src = 127.0.0.1/8``,
   will have no effect.

If you're not sure what firewall rules are in effect, see ``daf.rules``:

.. code-block:: text

    -- Show active rules
    > daf.rules
    [1] => {
        [rule] => {
            [count] => 42
            [id] => 1
            [cb] => function: 0x1a3eda38
        }
        [info] => qname = example.com AND src = 127.0.0.1/8 deny
        [policy] => function: 0x1a3eda38
    }
    [2] => {
        [rule] => {
            [suspended] => true
            [count] => 123522
            [id] => 2
            [cb] => function: 0x1a3ede88
        }
        [info] => qname ~ %w+.facebook.com AND src = 127.0.0.1/8 deny...
        [policy] => function: 0x1a3ede88
    }

Web interface
-------------

If you have :ref:`HTTP/2 <mod-http>` loaded, the firewall automatically loads as a snippet.
You can create, track, suspend and remove firewall rules from the web interface.
If you load both modules, you have to load `daf` after `http`.

RESTful interface
-----------------

The module also exports a RESTful API for operations over rule chains.


.. csv-table::
    :header: "URL", "HTTP Verb", "Action"

    "/daf", "GET", "Return JSON list of active rules."
    "/daf", "POST", "Insert new rule, rule string is expected in body. Returns rule information in JSON."
    "/daf/<id>", "GET", "Retrieve a rule matching given ID."
    "/daf/<id>", "DELETE", "Delete a rule matching given ID."
    "/daf/<id>/<prop>/<val>", "PATCH", "Modify given rule, for example /daf/3/active/false suspends rule 3."

This interface is used by the web interface for all operations, but you can also use it directly
for testing.

.. code-block:: bash

    # Get current rule set
    $ curl -s -X GET http://localhost:8453/daf | jq .
    {}

    # Create new rule
    $ curl -s -X POST -d "src = 127.0.0.1 pass" http://localhost:8453/daf | jq .
    {
      "count": 0,
      "active": true,
      "info": "src = 127.0.0.1 pass",
      "id": 1
    }

    # Disable rule
    $ curl -s -X PATCH http://localhost:8453/daf/1/active/false | jq .
    true

    # Retrieve a rule information
    $ curl -s -X GET http://localhost:8453/daf/1 | jq .
    {
      "count": 4,
      "active": true,
      "info": "src = 127.0.0.1 pass",
      "id": 1
    }

    # Delete a rule
    $ curl -s -X DELETE http://localhost:8453/daf/1 | jq .
    true
