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

.. _tls-server-config:

DoT and DoH (encrypted DNS)
---------------------------

.. warning::

   It is important to understand **limits of encrypting only DNS traffic**.
   Relevant security analysis can be found in article
   *Simran Patil and Nikita Borisov. 2019. What can you learn from an IP?*
   See `slides <https://irtf.org/anrw/2019/slides-anrw19-final44.pdf>`_
   or `the article itself <https://dl.acm.org/authorize?N687437>`_.

DoT and DoH encrypt DNS traffic with Transport Layer Security (TLS) protocol
and thus protects DNS traffic from certain types of attacks.

You can learn more about DoT and DoH and their implementation in Knot Resolver
in `this article
<https://en.blog.nic.cz/2020/11/25/encrypted-dns-in-knot-resolver-dot-and-doh/>`_.

.. _dns-over-tls:

DNS-over-TLS (DoT)
^^^^^^^^^^^^^^^^^^

DNS-over-TLS server (:rfc:`7858`) can be configured using ``tls`` kind in
:func:`net.listen()`.  It is enabled on localhost by default.

For certificate configuration, refer to :ref:`dot-doh-config-options`.

.. _dns-over-https:

DNS-over-HTTPS (DoH)
^^^^^^^^^^^^^^^^^^^^

.. note:: Knot Resolver currently offers two DoH implementations. It is
   recommended to use this new implementation, which is more reliable, scalable
   and has fewer dependencies. Make sure to use ``doh2`` kind in
   :func:`net.listen()` to select this implementation.

.. tip:: Independent information about political controversies around the
   DoH deployment by default can be found in blog posts `DNS Privacy at IETF
   104 <http://www.potaroo.net/ispcol/2019-04/angst.html>`_ and `More DOH
   <http://www.potaroo.net/ispcol/2019-04/moredoh.html>`_ by Geoff Huston and
   `Centralised DoH is bad for Privacy, in 2019 and beyond
   <https://labs.ripe.net/Members/bert_hubert/centralised-doh-is-bad-for-privacy-in-2019-and-beyond>`_
   by Bert Hubert.

DNS-over-HTTPS server (:rfc:`8484`) can be configured using ``doh2`` kind in
:func:`net.listen()`.

This implementation supports HTTP/2 (:rfc:`7540`). Queries can be sent to the
``/dns-query`` endpoint, e.g.:

.. code-block:: bash

	$ kdig @127.0.0.1 +https www.knot-resolver.cz AAAA

**Only TLS version 1.3 (or higher) is supported with DNS-over-HTTPS.** The
additional considerations for TLS 1.2 required by HTTP/2 are not implemented
(:rfc:`7540#section-9.2`).

.. warning:: Take care when configuring your server to listen on well known
   HTTPS port. If an unrelated HTTPS service is running on the same port with
   REUSEPORT enabled, you will end up with both services malfunctioning.

.. _dot-doh-config-options:

Configuration options for DoT and DoH
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. note:: These settings affect both DNS-over-TLS and DNS-over-HTTPS (except
   the legacy implementation).

A self-signed certificate is generated by default. For serious deployments
it is strongly recommended to configure your own TLS certificates signed
by a trusted CA. This is done using function :c:func:`net.tls()`.

.. function:: net.tls([cert_path], [key_path])

   When called with path arguments, the function loads the server TLS
   certificate and private key for DoT and DoH.

   When called without arguments, the command returns the currently configured paths.

   Example output:

   .. code-block:: lua

      > net.tls("/etc/knot-resolver/server-cert.pem", "/etc/knot-resolver/server-key.pem")
      > net.tls()  -- print configured paths
      [cert_file] => '/etc/knot-resolver/server-cert.pem'
      [key_file] => '/etc/knot-resolver/server-key.pem'

   .. tip:: The certificate files aren't automatically reloaded on change. If
      you update the certificate files, e.g. using ACME, you have to either
      restart the service(s) or call this function again using
      :ref:`control-sockets`.

.. function:: net.tls_sticket_secret([string with pre-shared secret])

   Set secret for TLS session resumption via tickets, by :rfc:`5077`.

   The server-side key is rotated roughly once per hour.
   By default or if called without secret, the key is random.
   That is good for long-term forward secrecy, but multiple kresd instances
   won't be able to resume each other's sessions.

   If you provide the same secret to multiple instances, they will be able to resume
   each other's sessions *without* any further communication between them.
   This synchronization works only among instances having the same endianness
   and time_t structure and size (`sizeof(time_t)`).

   **For good security** the secret must have enough entropy to be hard to guess,
   and it should still be occasionally rotated manually and securely forgotten,
   to reduce the scope of privacy leak in case the
   `secret leaks eventually <https://en.wikipedia.org/wiki/Forward_secrecy>`_.

   .. warning:: **Setting the secret is probably too risky with TLS <= 1.2**.
      GnuTLS stable release supports TLS 1.3 since 3.6.3 (summer 2018).
      Therefore setting the secrets should be considered experimental for now
      and might not be available on your system.

.. function:: net.tls_sticket_secret_file([string with path to a file containing pre-shared secret])

   The same as :func:`net.tls_sticket_secret`,
   except the secret is read from a (binary) file.

.. function:: net.tls_padding([true | false])

   Get/set EDNS(0) padding of answers to queries that arrive over TLS
   transport.  If set to `true` (the default), it will use a sensible
   default padding scheme, as implemented by libknot if available at
   compile time.  If set to a numeric value >= 2 it will pad the
   answers to nearest *padding* boundary, e.g. if set to `64`, the
   answer will have size of a multiple of 64 (64, 128, 192, ...).  If
   set to `false` (or a number < 2), it will disable padding entirely.

Configuration options for DoH
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. function:: net.doh_headers([string or table of strings])

   Selects the headers to be exposed. These headers and their values are
   available in ``request.qsource.headers``. Comparison
   is case-insensitive and pseudo-headers are supported as well.

   The following snippet can be used in the lua module to access headers
   ``:method`` and ``user-agent``:

   .. code-block:: lua

      net.doh_headers({':method', 'user-agent'})

      ...

      for i = 1, tonumber(req.qsource.headers.len) do
        local name = ffi.string(req.qsource.headers.at[i - 1].name)
        local value = ffi.string(req.qsource.headers.at[i - 1].value)
        print(name, value)
      end
