]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add an option to allow sub-paths for DoH 9962/head
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 12 Jan 2021 16:21:48 +0000 (17:21 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 12 Jan 2021 16:21:48 +0000 (17:21 +0100)
That was the default before 1.5.0 and is very convenient in some setups.

pdns/dnsdist-lua.cc
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/doh.cc
pdns/doh.hh
regression-tests.dnsdist/test_DOH.py

index e8049beb345d33b7365db44cb5b26b4645d55c9e..24134965053dd6ab9698abfd648a823ebb674d4b 100644 (file)
@@ -2069,6 +2069,10 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
         frontend->d_internalPipeBufferSize = boost::get<int>((*vars)["internalPipeBufferSize"]);
       }
 
+      if (vars->count("exactPathMatching")) {
+        frontend->d_exactPathMatching = boost::get<bool>((*vars)["exactPathMatching"]);
+      }
+
       parseTLSConfig(frontend->d_tlsConfig, "addDOHLocal", vars);
     }
     g_dohlocals.push_back(frontend);
index a6f848dd42c7d87774165f7f6320a64fb07f371e..f12aa69d7fac6f61d2a43cb431f625490f54333a 100644 (file)
@@ -118,7 +118,10 @@ Listen Sockets
 
   .. versionchanged:: 1.5.0
     ``internalPipeBufferSize``, ``sendCacheControlHeaders``, ``sessionTimeout``, ``trustForwardedForHeader`` options added.
-    ``url`` now defaults to ``/dns-query`` instead of ``/``. Added ``tcpListenQueueSize`` parameter.
+    ``url`` now defaults to ``/dns-query`` instead of ``/``, and does exact matching instead of accepting sub-paths. Added ``tcpListenQueueSize`` parameter.
+
+  .. versionchanged:: 1.6.0
+    ``exactPathMatching`` option added.
 
   Listen on the specified address and TCP port for incoming DNS over HTTPS connections, presenting the specified X.509 certificate.
   If no certificate (or key) files are specified, listen for incoming DNS over HTTP connections instead.
@@ -127,7 +130,7 @@ Listen Sockets
                       The default port is 443.
   :param str certFile(s): The path to a X.509 certificate file in PEM format, or a list of paths to such files.
   :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones.
-  :param str-or-list urls: The path part of a URL, or a list of paths, to accept queries on. Any query with a path under one of these will be treated as a DoH query. The default is /dns-query.
+  :param str-or-list urls: The path part of a URL, or a list of paths, to accept queries on. Any query with a path matching exactly one of these will be treated as a DoH query (sub-paths can be accepted by setting the ``exactPathMatching`` to false). The default is /dns-query.
   :param table options: A table with key: value pairs with listen options.
 
   Options:
@@ -155,6 +158,7 @@ Listen Sockets
   * ``trustForwardedForHeader``: bool - Whether to parse any existing X-Forwarded-For header in the HTTP query and use the right-most value as the client source address and port, for ACL checks, rules, logging and so on. Default is false.
   * ``tcpListenQueueSize=SOMAXCONN``: int - Set the size of the listen queue. Default is ``SOMAXCONN``.
   * ``internalPipeBufferSize=0``: int - Set the size in bytes of the internal buffer of the pipes used internally to pass queries and responses between threads. Requires support for ``F_SETPIPE_SZ`` which is present in Linux since 2.6.35. The actual size might be rounded up to a multiple of a page size. 0 means that the OS default size is used.
+  * ``exactPathMatching=true``: bool - Whether to do exact path matching of the query path against the paths configured in ``urls`` (true, the default since 1.5.0) or to accepts sub-paths (false, and was the default before 1.5.0).
 
 .. function:: addTLSLocal(address, certFile(s), keyFile(s) [, options])
 
index 919db49bcfce6c4aad230cf056f5b9a69738c142..7b06c8225dc65f4f395a638ac193afeb50460d92 100644 (file)
@@ -831,13 +831,15 @@ static int doh_handler(h2o_handler_t *self, h2o_req_t *req)
         ++dsc->cs->tlsUnknownqueries;
     }
 
-    // would be nice to be able to use a pdns_string_view there, but we would need heterogeneous lookups
-    // (having string in the set and compare them to string_view, for example. Note that comparing
-    // two boost::string_view uses the pointer, not the content).
-    const std::string pathOnly(req->path_normalized.base, req->path_normalized.len);
-    if (dsc->paths.count(pathOnly) == 0) {
-      h2o_send_error_404(req, "Not Found", "there is no endpoint configured for this path", 0);
-      return 0;
+    if (dsc->df->d_exactPathMatching) {
+      // would be nice to be able to use a pdns_string_view there, but we would need heterogeneous lookups
+      // (having string in the set and compare them to string_view, for example. Note that comparing
+      // two boost::string_view uses the pointer, not the content).
+      const std::string pathOnly(req->path_normalized.base, req->path_normalized.len);
+      if (dsc->paths.count(pathOnly) == 0) {
+        h2o_send_error_404(req, "Not Found", "there is no endpoint configured for this path", 0);
+        return 0;
+      }
     }
 
     // would be nice to be able to use a pdns_string_view there,
@@ -1405,7 +1407,6 @@ void dohThread(ClientState* cs)
     dsc->df = cs->dohFrontend;
     dsc->h2o_config.server_name = h2o_iovec_init(df->d_serverTokens.c_str(), df->d_serverTokens.size());
 
-
     std::thread dnsdistThread(dnsdistclient, dsc->dohquerypair[1]);
     dnsdistThread.detach(); // gets us better error reporting
 
index 10ed9a1b6d224bcdcabb8630a6d88f6a59813116..09fcc71042fc5ea0d0fa931d8268b08c986158fc 100644 (file)
@@ -105,6 +105,9 @@ struct DOHFrontend
   uint32_t d_internalPipeBufferSize{0};
   bool d_sendCacheControlHeaders{true};
   bool d_trustForwardedForHeader{false};
+  /* whether we require tue query path to exactly match one of configured ones,
+     or accept everything below these paths. */
+  bool d_exactPathMatching{true};
 
   time_t getTicketsKeyRotationDelay() const
   {
index c7425432f95a34afd34864af00d9647f4174cded..b19d159f622006f8edc5cdb88962b7e2bcbd09f6 100644 (file)
@@ -621,6 +621,64 @@ class TestDOH(DNSDistDOHTest):
         self.assertIn('foo: bar', headers)
         self.assertNotIn(self._customResponseHeader2, headers)
 
+class TestDOHSubPaths(DNSDistDOHTest):
+
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _dohServerPort = 8443
+    _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+
+    addAction(AllRule(), SpoofAction("3.4.5.6"))
+
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/PowerDNS" }, {exactPathMatching=false})
+    """
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+
+    def testSubPath(self):
+        """
+        DOH: sub-path
+        """
+        name = 'sub-path.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.id = 0
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '3.4.5.6')
+        expectedResponse.answer.append(rrset)
+
+        # this path should match
+        (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL + 'PowerDNS', caFile=self._caCert, query=query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, expectedResponse)
+
+        expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+        expectedQuery.id = 0
+        expectedQuery.flags &= ~dns.flags.RD
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+
+        # this path is not in the URLs map and should lead to a 404
+        (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL + "NotPowerDNS", query, caFile=self._caCert, useQueue=False, rawResponse=True)
+        self.assertTrue(receivedResponse)
+        self.assertEquals(receivedResponse, b'not found')
+        self.assertEquals(self._rcode, 404)
+
+        # this path is below one in the URLs map and exactPathMatching is false, so we should be good
+        (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL + 'PowerDNS/something', caFile=self._caCert, query=query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, expectedResponse)
+
 class TestDOHAddingECS(DNSDistDOHTest):
 
     _serverKey = 'server.key'