summaryrefslogtreecommitdiffstats
path: root/modules/policy/README.rst
blob: 202aabab7aa3236fbdc72ebaa22e4787f089e125 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
.. SPDX-License-Identifier: GPL-3.0-or-later

.. default-domain:: py
.. module:: policy

.. _mod-policy:


Query policies
==============

This module can block, rewrite, or alter inbound queries based on user-defined policies. It does not affect queries generated by the resolver itself, e.g. when following CNAME chains etc.

Each policy *rule* has two parts: a *filter* and an *action*. A *filter* selects which queries will be affected by the policy, and *action* which modifies queries matching the associated filter.

Typically a rule is defined as follows: ``filter(action(action parameters), filter parameters)``. For example, a filter can be ``suffix`` which matches queries whose suffix part is in specified set, and one of possible actions is :any:`policy.DENY`, which denies resolution. These are combined together into ``policy.suffix(policy.DENY, {todname('badguy.example.')})``. The rule is effective when it is added into rule table using ``policy.add()``, please see examples below.

This module is enabled by default because it implements mandatory :rfc:`6761` logic.
When no rule applies to a query, built-in rules for `special-use <https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml>`_ and `locally-served <http://www.iana.org/assignments/locally-served-dns-zones>`_ domain names are applied.
These rules can be overridden by action :any:`policy.PASS`.  For debugging purposes you can also add ``modules.unload('policy')`` to your config to unload the module.


Filters
-------
A *filter* selects which queries will be affected by specified Actions_. There are several policy filters available in the ``policy.`` table:

.. function:: all(action)

   Always applies the action.

.. function:: pattern(action, pattern)

   Applies the action if query name matches a `Lua regular expression <http://lua-users.org/wiki/PatternsTutorial>`_.

.. function:: suffix(action, suffix_table)

   Applies the action if query name suffix matches one of suffixes in the table (useful for "is domain in zone" rules).

   .. code-block:: lua

      policy.add(policy.suffix(policy.DENY, policy.todnames({'example.com', 'example.net'})))

.. note:: For speed this filter requires domain names in DNS wire format, not textual representation, so each label in the name must be prefixed with its length. Always use convenience function :func:`policy.todnames` for automatic conversion from strings! For example:

.. _IDN:

.. note:: Non-ASCII is not supported.

   Knot Resolver does not provide any convenience support for IDN.
   Therefore everywhere (all configuration, logs, RPZ files) you need to deal with the
   `xn\-\- forms <https://en.wikipedia.org/wiki/Internationalized_domain_name#Example_of_IDNA_encoding>`_
   of domain name labels, instead of directly using unicode characters.

.. function:: domains(action, domain_table)

   Like :func:`policy.suffix` match, but the queried name must match exactly, not just its suffix.

.. function:: suffix_common(action, suffix_table[, common_suffix])

  :param action: action if the pattern matches query name
  :param suffix_table: table of valid suffixes
  :param common_suffix: common suffix of entries in suffix_table

  Like :func:`policy.suffix` match, but you can also provide a common suffix of all matches for faster processing (nil otherwise).
  This function is faster for small suffix tables (in the order of "hundreds").

.. :noindex: function:: rpz(default_action, path, [watch])

   Implements a subset of `Response Policy Zone` (RPZ_) stored in zonefile format.  See below for details: :func:`policy.rpz`.

It is also possible to define custom filter function with any name.

.. function:: custom_filter(state, query)

   :param state: Request processing state :c:type:`kr_layer_state`, typically not used by filter function.
   :param query: Incoming DNS query as :c:type:`kr_query` structure.
   :return: An `action <#actions>`_ function or ``nil`` if filter did not match.

   Typically filter function is generated by another function, which allows easy parametrization - this technique is called `closure <https://www.lua.org/pil/6.1.html>`_. An practical example of such filter generator is:

.. code-block:: lua

   function match_query_type(action, target_qtype)
       return function (state, query)
           if query.stype == target_qtype then
               -- filter matched the query, return action function
               return action
           else
               -- filter did not match, continue with next filter
               return nil
           end
       end
   end

This custom filter can be used as any other built-in filter.
For example this applies our custom filter and executes action :any:`policy.DENY` on all queries of type `HINFO`:

.. code-block:: lua

   -- custom filter which matches HINFO queries, action is policy.DENY
   policy.add(match_query_type(policy.DENY, kres.type.HINFO))


.. _mod-policy-actions:

Actions
-------
An *action* is a function which modifies DNS request, and is either of type *chain* or *non-chain*:

  * `Non-chain actions`_ modify state of the request and stop rule processing. An example of such action is :ref:`forwarding`.
  * `Chain actions`_ modify state of the request and allow other rules to evaluate and act on the same request. One such example is :func:`policy.MIRROR`.

Non-chain actions
^^^^^^^^^^^^^^^^^

Following actions stop the policy matching on the query, i.e. other rules are not evaluated once rule with following actions matches:

.. py:attribute:: PASS

   Let the query pass through; it's useful to make exceptions before wider rules. For example:

   More specific whitelist rule must precede generic blacklist rule:

   .. code-block:: lua

    -- Whitelist 'good.example.com'
    policy.add(policy.pattern(policy.PASS, todname('good.example.com.')))
    -- Block all names below example.com
    policy.add(policy.suffix(policy.DENY, {todname('example.com.')}))

.. py:attribute:: DENY

   Deny existence of names matching filter, i.e. reply NXDOMAIN authoritatively.

.. function:: DENY_MSG(message, [extended_error=kres.extended_error.BLOCKED])

   Deny existence of a given domain and add explanatory message. NXDOMAIN reply
   contains an additional explanatory message as TXT record in the additional
   section.

   You may override the extended DNS error to provide the user with more
   information. By default, ``BLOCKED`` is returned to indicate the domain is
   blocked due to the internal policy of the operator. Other suitable error
   codes are ``CENSORED`` (for externally imposed policy reasons) or
   ``FILTERED`` (for blocking requested by the client). For more information,
   please refer to :rfc:`8914`.

.. py:attribute:: DROP

   Terminate query resolution and return SERVFAIL to the requestor.

.. py:attribute:: REFUSE

   Terminate query resolution and return REFUSED to the requestor.

.. py:attribute:: NO_ANSWER

   Terminate query resolution and do not return any answer to the requestor.

   .. warning:: During normal operation, an answer should always be returned.
      Deliberate query drops are indistinguishable from packet loss and may
      cause problems as described in :rfc:`8906`. Only use :any:`NO_ANSWER`
      on very specific occasions, e.g. as a defense mechanism during an attack,
      and prefer other actions (e.g. :any:`DROP` or :any:`REFUSE`) for normal
      operation.

.. py:attribute:: TC

   Force requestor to use TCP. It sets truncated bit (*TC*) in response to true if the request came through UDP, which will force standard-compliant clients to retry the request over TCP.

.. function:: REROUTE({subnet = target, ...})

   Reroute IP addresses in response matching given subnet to given target, e.g. ``{['192.0.2.0/24'] = '127.0.0.0'}`` will rewrite '192.0.2.55' to '127.0.0.55', see :ref:`renumber module <mod-renumber>` for more information. See :func:`policy.add` and do not forget to specify that this is *postrule*. Quick example:

   .. code-block:: lua

      -- this policy is enforced on answers
      -- therefore we have to use 'postrule'
      -- (the "true" at the end of policy.add)
      policy.add(policy.all(policy.REROUTE({['192.0.2.0/24'] = '127.0.0.0'})), true)

.. function:: ANSWER({ type = { rdata=data, [ttl=1] } }, [nodata=false])

   Overwrite Resource Records in responses with specified values.

      * type
        - RR type to be replaced, e.g. ``[kres.type.A]`` or `numeric value <https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4>`_.
      * rdata
        - RR data in DNS wire format, i.e. binary form specific for given RR type. Set of multiple RRs can be specified as table ``{ rdata1, rdata2, ... }``. Use helper function :func:`kres.str2ip` to generate wire format for A and AAAA records. Wire format for other record types can be generated with :func:`kres.parse_rdata`.
      * ttl
        - TTL in seconds. Default: 1 second.
      * nodata
        - If type requested by client is not configured in this policy:

          - ``true``: Return empty answer (`NODATA`).
          - ``false``: Ignore this policy and continue processing other rules.

          Default: ``false``.

   .. code-block:: lua

      -- policy to change IPv4 address and TTL for example.com
      policy.add(
          policy.domains(
              policy.ANSWER(
                  { [kres.type.A] = { rdata=kres.str2ip('192.0.2.7'), ttl=300 } }
              ), { todname('example.com') }))
      -- policy to generate two TXT records (specified in binary format) for example.net
      policy.add(
          policy.domains(
              policy.ANSWER(
                  { [kres.type.TXT] = { rdata={'\005first', '\006second'}, ttl=5 } }
              ), { todname('example.net') }))


   .. function:: kres.parse_rdata({str, ...})

      Parse string representation of RTYPE and RDATA into RDATA wire format. Expects
      a table of string(s) and returns a table of wire data.

      .. code-block:: lua

         -- create wire format RDATA that can be passed to policy.ANSWER
         kres.parse_rdata({'SVCB 1 resolver.example. alpn=dot'})
         kres.parse_rdata({
            'SVCB 1 resolver.example. alpn=dot ipv4hint=192.0.2.1 ipv6hint=2001:db8::1',
            'SVCB 2 resolver.example. mandatory=key65380 alpn=h2 key65380=/dns-query{?dns}',
         })

More complex non-chain actions are described in their own chapters, namely:

  * :ref:`forwarding`
  * `Response Policy Zones`_

Chain actions
^^^^^^^^^^^^^

Following actions act on request and then processing continue until first non-chain action (specified in the previous section) is triggered:

.. function:: MIRROR(ip_address)

   Send copy of incoming DNS queries to a given IP address using DNS-over-UDP and continue resolving them as usual. This is useful for sanity testing new versions of DNS resolvers.

   .. code-block:: lua

      policy.add(policy.all(policy.MIRROR('127.0.0.2')))

.. function:: FLAGS(set, clear)

   Set and/or clear some flags for the query.  There can be multiple flags to set/clear.  You can just pass a single flag name (string) or a set of names. Flag names correspond to :c:type:`kr_qflags` structure.  Use only if you know what you are doing.


.. _mod-policy-logging:

Actions for extra logging
^^^^^^^^^^^^^^^^^^^^^^^^^

These are also "chain" actions, i.e. they don't stop processing the policy rule list.
Similarly to other actions, they apply during whole processing of the client's request,
i.e. including any sub-queries.

The log lines from these policy actions are tagged by extra ``[reqdbg]`` prefix,
and they are produced regardless of your :func:`log_level()` setting.
They are marked as ``debug`` level, so e.g. with journalctl command you can use ``-p info`` to skip them.

.. warning::  Beware of producing too much logs.

   These actions are not suitable for use on a large fraction of resolver's requests.
   The extra logs have significant performance impact and might also overload your logging system
   (or get rate-limited by it).
   You can use `Filters`_ to further limit on which requests this happens.

.. py:attribute:: DEBUG_ALWAYS

   Print debug-level logging for this request.
   That also includes messages from client (:any:`REQTRACE`), upstream servers (:any:`QTRACE`), and stats about interesting records at the end.

   .. code-block:: lua

      -- debug requests that ask for flaky.example.net or below
      policy.add(policy.suffix(policy.DEBUG_ALWAYS,
          policy.todnames({'flaky.example.net'})))

.. py:attribute:: DEBUG_CACHE_MISS

   Same as :any:`DEBUG_ALWAYS` but only if the request required information which was not available locally, i.e. requests which forced resolver to ask upstream server(s).
   Intended usage is for debugging problems with remote servers.

.. py:function:: DEBUG_IF(test_function)

   :param test_function: Function with single argument of type :c:type:`kr_request` which returns ``true`` if debug logs for that request should be generated and ``false`` otherwise.

   Same as :any:`DEBUG_ALWAYS` but only logs if the test_function says so.

   .. note:: ``test_function`` is evaluated only when request is finished.
        As a result all debug logs this request must be collected,
        and at the end they get either printed or thrown away.

   Example usage which gathers verbose logs for all requests in subtree ``dnssec-failed.org.`` and prints debug logs for those finishing in a different state than ``kres.DONE`` (most importantly ``kres.FAIL``, see :c:type:`kr_layer_state`).

   .. code-block:: lua

      policy.add(policy.suffix(
          policy.DEBUG_IF(function(req)
                              return (req.state ~= kres.DONE)
                          end),
          policy.todnames({'dnssec-failed.org.'})))

.. py:attribute:: QTRACE

   Pretty-print DNS responses from upstream servers (or cache) into logs.
   It's useful for debugging weird DNS servers.

   If you do not use ``QTRACE`` in combination with ``DEBUG*``,
   you additionally need either ``log_groups({'iterat'})`` (possibly with other groups)
   or ``log_level('debug')`` to see the output in logs.

.. py:attribute:: REQTRACE

   Pretty-print DNS requests from clients into the verbose log. It's useful for debugging weird DNS clients.
   It makes most sense together with :ref:`mod-view` (enabling per-client)
   and probably with verbose logging those request (e.g. use :any:`DEBUG_ALWAYS` instead).

.. py:attribute:: IPTRACE

   Log how the request arrived.
   Most notably, this includes the client's IP address, so beware of privacy implications.

   .. code-block:: lua

        -- example usage in configuration
        policy.add(policy.all(policy.IPTRACE))
        -- you might want to combine it with some other logs, e.g.
        policy.add(policy.all(policy.DEBUG_ALWAYS))

   .. code-block:: text

        -- example log lines from IPTRACE:
        [reqdbg][policy][57517.00] request packet arrived from ::1#37931 to ::1#00853 (TCP + TLS)
        [reqdbg][policy][65538.00] request packet arrived internally


Custom actions
^^^^^^^^^^^^^^

.. function:: custom_action(state, request)

   :param state: Request processing state :c:type:`kr_layer_state`.
   :param request: Current DNS request as :c:type:`kr_request` structure.
   :return: Returning a new :c:type:`kr_layer_state` prevents evaluating other policy rules. Returning ``nil`` creates a `chain action <#actions>`_ and allows to continue evaluating other rules.

   This is real example of an action function:

.. code-block:: lua

   -- Custom action which generates fake A record
   local ffi = require('ffi')
   local function fake_A_record(state, req)
       local answer = req:ensure_answer()
       if answer == nil then return nil end
       local qry = req:current()
       if qry.stype ~= kres.type.A then
           return state
       end
       ffi.C.kr_pkt_make_auth_header(answer)
       answer:rcode(kres.rcode.NOERROR)
       answer:begin(kres.section.ANSWER)
       answer:put(qry.sname, 900, answer:qclass(), kres.type.A, '\192\168\1\3')
       return kres.DONE
   end

This custom action can be used as any other built-in action.
For example this applies our *fake A record action* and executes it on all queries in subtree ``example.net``:

.. code-block:: lua

   policy.add(policy.suffix(fake_A_record, policy.todnames({'example.net'})))

The action function can implement arbitrary logic so it is possible to implement complex heuristics, e.g. to deflect `Slow drip DNS attacks <https://secure64.com/water-torture-slow-drip-dns-ddos-attack>`_ or gray-list resolution of misbehaving zones.

.. warning:: The policy module currently only looks at whole DNS requests.  The rules won't be re-applied e.g. when following CNAMEs.

.. _forwarding:

Forwarding
----------

Forwarding action alters behavior for cache-miss events. If an information is missing in the local cache the resolver will *forward* the query to *another DNS resolver* for resolution (instead of contacting authoritative servers directly). DNS answers from the remote resolver are then processed locally and sent back to the original client.

Actions :func:`policy.FORWARD`, :func:`policy.TLS_FORWARD` and :func:`policy.STUB` accept up to four IP addresses at once and the resolver will automatically select IP address which statistically responds the fastest.

.. function:: FORWARD(ip_address)
              FORWARD({ ip_address, [ip_address, ...] })

   Forward cache-miss queries to specified IP addresses (without encryption), DNSSEC validate received answers and cache them. Target IP addresses are expected to be DNS resolvers.

   .. code-block:: lua

      -- Forward all queries to public resolvers https://www.nic.cz/odvr
      policy.add(policy.all(
         policy.FORWARD(
             {'2001:148f:fffe::1', '2001:148f:ffff::1',
              '185.43.135.1', '193.14.47.1'})))

   A variant which uses encrypted DNS-over-TLS transport is called :func:`policy.TLS_FORWARD`, please see section :ref:`tls-forwarding`.

.. function:: STUB(ip_address)
              STUB({ ip_address, [ip_address, ...] })

   Similar to :func:`policy.FORWARD` but *without* attempting DNSSEC validation.
   Each request may be either answered from cache or simply sent to one of the IPs with proxying back the answer.

   This mode does not support encryption and should be used only for `Replacing part of the DNS tree`_.
   Use :func:`policy.FORWARD` mode if possible.

   .. code-block:: lua

      -- Answers for reverse queries about the 192.168.1.0/24 subnet
      -- are to be obtained from IP address 192.0.2.1 port 5353
      -- This disables DNSSEC validation!
      policy.add(policy.suffix(
          policy.STUB('192.0.2.1@5353'),
          {todname('1.168.192.in-addr.arpa')}))

.. note:: By default, forwarding targets must support
   `EDNS <https://en.wikipedia.org/wiki/Extension_mechanisms_for_DNS>`_ and
   `0x20 randomization <https://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00>`_.
   See example in `Replacing part of the DNS tree`_.

.. warning::
   Limiting forwarding actions by filters (e.g. :func:`policy.suffix`) may have unexpected consequences.
   Notably, forwarders can inject *any* records into your cache
   even if you "restrict" them to an insignificant DNS subtree --
   except in cases where DNSSEC validation applies, of course.

   The behavior is probably best understood through the fact
   that filters and actions are completely decoupled.
   The forwarding actions have no clue about why they were executed,
   e.g. that the user wanted to restrict the forwarder only to some subtree.
   The action just selects some set of forwarders to process this whole request from the client,
   and during that processing it might need some other "sub-queries" (e.g. for validation).
   Some of those might not've passed the intended filter,
   but policy rule-set only applies once per client's request.

.. _tls-forwarding:

Forwarding over TLS protocol (DNS-over-TLS)
-------------------------------------------
.. function:: TLS_FORWARD( { {ip_address, authentication}, [...] } )

   Same as :func:`policy.FORWARD` but send query over DNS-over-TLS protocol (encrypted).
   Each target IP address needs explicit configuration how to validate
   TLS certificate so each IP address is configured by pair:
   ``{ip_address, authentication}``. See sections below for more details.


Policy :func:`policy.TLS_FORWARD` allows you to forward queries using `Transport Layer Security`_ protocol, which hides the content of your queries from an attacker observing the network traffic. Further details about this protocol can be found in :rfc:`7858` and `IETF draft dprive-dtls-and-tls-profiles`_.

Queries affected by :func:`policy.TLS_FORWARD` will always be resolved over TLS connection. Knot Resolver does not implement fallback to non-TLS connection, so if TLS connection cannot be established or authenticated according to the configuration, the resolution will fail.

To test this feature you need to either :ref:`configure Knot Resolver as DNS-over-TLS server <tls-server-config>`, or pick some public DNS-over-TLS server. Please see `DNS Privacy Project`_ homepage for list of public servers.

.. note:: Some public DNS-over-TLS providers may apply rate-limiting which
   makes their service incompatible with Knot Resolver's TLS forwarding.
   Notably, `Google Public DNS
   <https://developers.google.com/speed/public-dns/docs/dns-over-tls>`_ doesn't
   work as of 2019-07-10.

When multiple servers are specified, the one with the lowest round-trip time is used.

CA+hostname authentication
^^^^^^^^^^^^^^^^^^^^^^^^^^
Traditional PKI authentication requires server to present certificate with specified hostname, which is issued by one of trusted CAs. Example policy is:

.. code-block:: lua

   policy.TLS_FORWARD({
       {'2001:DB8::d0c', hostname='res.example.com'}})

- ``hostname`` must be a valid domain name matching server's certificate.  It will also be sent to the server as SNI_.
- ``ca_file`` optionally contains a path to a CA certificate (or certificate bundle) in `PEM format`_.
  If you omit that, the system CA certificate store will be used instead (usually sufficient).
  A list of paths is also accepted, but all of them must be valid PEMs.

Key-pinned authentication
^^^^^^^^^^^^^^^^^^^^^^^^^
Instead of CAs, you can specify hashes of accepted certificates in ``pin_sha256``.
They are in the usual format -- base64 from sha256.
You may still specify ``hostname`` if you want SNI_ to be sent.

.. _tls-examples:

TLS Examples
^^^^^^^^^^^^

.. code-block:: lua

   modules = { 'policy' }
   -- forward all queries over TLS to the specified server
   policy.add(policy.all(policy.TLS_FORWARD({{'192.0.2.1', pin_sha256='YQ=='}})))
   -- for brevity, other TLS examples omit policy.add(policy.all())
   -- single server authenticated using its certificate pin_sha256
   policy.TLS_FORWARD({{'192.0.2.1', pin_sha256='YQ=='}})  -- pin_sha256 is base64-encoded
   -- single server authenticated using hostname and system-wide CA certificates
   policy.TLS_FORWARD({{'192.0.2.1', hostname='res.example.com'}})
   -- single server using non-standard port
   policy.TLS_FORWARD({{'192.0.2.1@443', pin_sha256='YQ=='}})  -- use @ or # to specify port
   -- single server with multiple valid pins (e.g. anycast)
   policy.TLS_FORWARD({{'192.0.2.1', pin_sha256={'YQ==', 'Wg=='}})
   -- multiple servers, each with own authenticator
   policy.TLS_FORWARD({ -- please note that { here starts list of servers
       {'192.0.2.1', pin_sha256='Wg=='},
       -- server must present certificate issued by specified CA and hostname must match
       {'2001:DB8::d0c', hostname='res.example.com', ca_file='/etc/knot-resolver/tlsca.crt'}
   })

Forwarding to multiple targets
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

With the use of :func:`policy.slice` function, it is possible to split the
entire DNS namespace into distinct slices. When used in conjunction with
:func:`policy.TLS_FORWARD`, it's possible to forward different queries to
different targets.

.. function:: slice(slice_func, action[, action[, ...])

  :param slice_func: slicing function that returns index based on query
  :param action: action to be performed for the slice

  This function splits the entire domain space into multiple slices (determined
  by the number of provided ``actions``). A ``slice_func`` is called to determine
  which slice a query belongs to. The corresponding ``action`` is then executed.


.. function:: slice_randomize_psl(seed = os.time() / (3600 * 24 * 7))

  :param seed: seed for random assignment

  The function initializes and returns a slicing function, which
  deterministically assigns ``query`` to a slice based on the query name.

  It utilizes the `Public Suffix List`_ to ensure domains under the same
  registrable domain end up in a single slice. (see example below)

  ``seed`` can be used to re-shuffle the slicing algorithm when the slicing
  function is initialized. By default, the assignment is re-shuffled after one
  week (when resolver restart / reloads config). To force a stable
  distribution, pass a fixed value. To re-shuffle on every resolver restart,
  use ``os.time()``.

  The following example demonstrates a distribution among 3 slices::

    slice 1/3:
    example.com
    a.example.com
    b.example.com
    x.b.example.com
    example3.com

    slice 2/3:
    example2.co.uk

    slice 3/3:
    example.co.uk
    a.example.co.uk

These two functions can be used together to forward queries for names
in different parts of DNS name space to different target servers:

.. code-block:: lua

   policy.add(policy.slice(
       policy.slice_randomize_psl(),
       policy.TLS_FORWARD({{'192.0.2.1', hostname='res.example.com'}}),
       policy.TLS_FORWARD({
           -- multiple servers can be specified for a single slice
           -- the one with lowest round-trip time will be used
           {'193.17.47.1', hostname='odvr.nic.cz'},
           {'185.43.135.1', hostname='odvr.nic.cz'},
       })
   ))

.. note:: The privacy implications of using this feature aren't clear. Since
   websites often make requests to multiple domains, these might be forwarded
   to different targets. This could result in decreased privacy (e.g. when the
   remote targets are both logging or otherwise processing your DNS traffic).
   The intended use-case is to use this feature with semi-trusted resolvers
   which claim to do no logging (such as those listed on `dnsprivacy.org
   <https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Test+Servers>`_), to
   decrease the potential exposure of your DNS data to a malicious resolver
   operator.

.. _dns-graft:

Replacing part of the DNS tree
------------------------------

Following procedure applies only to domains which have different content
publicly and internally. For example this applies to "your own" top-level domain
``example.`` which does not exist in the public (global) DNS namespace.

Dealing with these internal-only domains requires extra configuration because
DNS was designed as "single namespace" and local modifications like adding
your own TLD break this assumption.

.. warning:: Use of internal names which are not delegated from the public DNS
             *is causing technical problems* with caching and DNSSEC validation
             and generally makes DNS operation more costly.
             We recommend **against** using these non-delegated names.

To make such internal domain available in your resolver it is necessary to
*graft* your domain onto the public DNS namespace,
but *grafting* creates new issues:

These *grafted* domains will be rejected by DNSSEC validation
because such domains are technically indistinguishable from an spoofing attack
against the public DNS.
Therefore, if you trust the remote resolver which hosts the internal-only domain,
and you trust your link to it, you need to use the :func:`policy.STUB` policy
instead of :func:`policy.FORWARD` to disable DNSSEC validation for those
*grafted* domains.

.. code-block:: lua
   :caption: Example configuration grafting domains onto public DNS namespace

   extraTrees = policy.todnames(
       {'faketldtest.',
        'sld.example.',
        'internal.example.com.',
        '2.0.192.in-addr.arpa.'  -- this applies to reverse DNS tree as well
        })
   -- Beware: the rule order is important, as policy.STUB is not a chain action.
   -- Flags: for "dumb" targets disabling EDNS can help (below) as DNSSEC isn't
   -- validated anyway; in some of those cases adding 'NO_0X20' can also help,
   -- though it also lowers defenses against off-path attacks on communication
   -- between the two servers.
   -- With kresd <= 5.5.3 you also needed 'NO_CACHE' flag to avoid unintentional
   -- NXDOMAINs that could sometimes happen due to aggressive DNSSEC caching.
   policy.add(policy.suffix(policy.FLAGS({'NO_EDNS'}), extraTrees))
   policy.add(policy.suffix(policy.STUB({'2001:db8::1'}), extraTrees))

Response policy zones
---------------------
  .. warning::

     There is no published Internet Standard for RPZ_ and implementations vary.
     At the moment Knot Resolver supports limited subset of RPZ format and deviates
     from implementation in BIND. Nevertheless it is good enough
     for blocking large lists of spam or advertising domains.



  The RPZ file format is basically a DNS zone file with *very special* semantics.
  For example:

  .. code-block:: none

     ; left hand side          ; TTL and class  ; right hand side
     ; encodes RPZ trigger     ; ignored        ; encodes action
     ; (i.e. filter)
     blocked.domain.example    600 IN           CNAME .           ; block main domain
     *.blocked.domain.example  600 IN           CNAME .           ; block subdomains

  The only "trigger" supported in Knot Resolver is query name,
  i.e. left hand side must be a domain name which triggers the action specified
  on the right hand side.

  Subset of possible RPZ actions is supported, namely:

  .. csv-table::
   :header: "RPZ Right Hand Side", "Knot Resolver Action", "BIND Compatibility"

   "``.``", "``action`` is used", "compatible if ``action`` is :any:`policy.DENY`"
   "``*.``", ":func:`policy.ANSWER`", "yes"
   "``rpz-passthru.``", ":any:`policy.PASS`", "yes"
   "``rpz-tcp-only.``", ":any:`policy.TC`", "yes"
   "``rpz-drop.``", ":any:`policy.DROP`", "no [#]_"
   "fake A/AAAA", ":func:`policy.ANSWER`", "yes"
   "fake CNAME", "not supported", "no"

  .. [#] Our :any:`policy.DROP` returns *SERVFAIL* answer (for historical reasons).


  .. note::

     To debug which domains are affected by RPZ (or other policy actions), you can enable the ``policy`` log group:

     .. code-block:: lua

        log_groups({'policy'})

     See also :ref:`non-ASCII support note <IDN>`.


.. function:: rpz(action, path, [watch = true])

  :param action: the default action for match in the zone; typically you want :any:`policy.DENY`
  :param path: path to zone file
  :param watch: boolean, if true, the file will be reloaded on file change

  Enforce RPZ_ rules. This can be used in conjunction with published blocklist feeds.
  The RPZ_ operation is well described in this `Jan-Piet Mens's post`_,
  or the `Pro DNS and BIND`_ book.

  For example, we can store the example snippet with domain ``blocked.domain.example``
  (above) into file ``/etc/knot-resolver/blocklist.rpz`` and configure resolver to
  answer with *NXDOMAIN* plus the specified additional text to queries for this domain:

  .. code-block:: lua

     policy.add(
         policy.rpz(policy.DENY_MSG('domain blocked by your resolver operator'),
                    '/etc/knot-resolver/blocklist.rpz',
                    true))

  Resolver will reload RPZ file at run-time if the RPZ file changes.
  Recommended RPZ update procedure is to store new blocklist in a new file
  (*newblocklist.rpz*) and then rename the new file to the original file name
  (*blocklist.rpz*). This avoids problems where resolver might attempt
  to re-read an incomplete file.



Additional properties
---------------------

Most properties (actions, filters) are described above.

.. function:: add(rule, postrule)

  :param rule: added rule, i.e. ``policy.pattern(policy.DENY, '[0-9]+\2cz')``
  :param postrule: boolean, if true the rule will be evaluated on answer instead of query
  :return: rule description

  Add a new policy rule that is executed either or queries or answers, depending on the ``postrule`` parameter. You can then use the returned rule description to get information and unique identifier for the rule, as well as match count.

  .. code-block:: lua

     -- mirror all queries, keep handle so we can retrieve information later
     local rule = policy.add(policy.all(policy.MIRROR('127.0.0.2')))
     -- we can print statistics about this rule any time later
     print(string.format('id: %d, matched queries: %d', rule.id, rule.count)

.. function:: del(id)

  :param id: identifier of a given rule returned by :func:`policy.add`
  :return: boolean ``true`` if rule was deleted, ``false`` otherwise

  Remove a rule from policy list.

.. function:: todnames({name, ...})

   :param: names table of domain names in textual format

   Returns table of domain names in wire format converted from strings.

   .. code-block:: lua

      -- Convert single name
      assert(todname('example.com') == '\7example\3com\0')
      -- Convert table of names
      policy.todnames({'example.com', 'me.cz'})
      { '\7example\3com\0', '\2me\2cz\0' }


.. _RPZ: https://dnsrpz.info/
.. _`PEM format`: https://en.wikipedia.org/wiki/Privacy-enhanced_Electronic_Mail
.. _`Pro DNS and BIND`: http://www.zytrax.com/books/dns/ch7/rpz.html
.. _`Jan-Piet Mens's post`: http://jpmens.net/2011/04/26/how-to-configure-your-bind-resolvers-to-lie-using-response-policy-zones-rpz/
.. _`Transport Layer Security`: https://en.wikipedia.org/wiki/Transport_Layer_Security
.. _`DNS Privacy Project`: https://dnsprivacy.org/
.. _`IETF draft dprive-dtls-and-tls-profiles`: https://tools.ietf.org/html/draft-ietf-dprive-dtls-and-tls-profiles
.. _SNI: https://en.wikipedia.org/wiki/Server_Name_Indication
.. _`Public Suffix List`: https://publicsuffix.org