]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Fix race and test and check subject of client cert and add PEM test
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Thu, 5 Feb 2026 10:34:06 +0000 (11:34 +0100)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Mon, 16 Feb 2026 07:51:41 +0000 (08:51 +0100)
Signed-off-by: Otto Moerbeek <otto.moerbeek@open-xchange.com>
regression-tests.recursor-dnssec/recursortests.py
regression-tests.recursor-dnssec/test_DoT.py

index fbfe330b8afbf2be35af0133ecbf3c58d840cf72..96f9a1afb2b0a468da12a45010e981f5e53fd14a 100644 (file)
@@ -1355,6 +1355,9 @@ distributor-threads={threads}
           cert = conn.getpeercert()
           if cert is None:
               raise AssertionError("Client certificate expected, got none")
+          if cert['subject'][0][0][1] != 'client.tests.powerdns.com':
+              print(cert)
+              raise AssertionError('Unexpected subject in cert')
 
       (datalen,) = struct.unpack("!H", data)
       data = conn.recv(datalen)
index 2c2e55ce20088fbb559d98774afa2e4bbafe6184..bc2a8bf8c087129d6cc29412232c7cb7f3ba56d2 100644 (file)
@@ -4,6 +4,7 @@ import os
 import subprocess
 import ssl
 import threading
+import time
 from queue import Queue
 
 
@@ -301,7 +302,9 @@ class DoTWithLocalResponderTests(RecursorTest):
             tlsContext.sni_callback = cls.sniCallback
 
         if cls._clientCert:
-            tlsContext.verify_mode = ssl.CERT_REQUIRED
+            # we check ourselves in the TCP handler if needed, if we would set cert_REQUIRED comms
+            # just fail and that type of problem is harder to diagnose
+            tlsContext.verify_mode = ssl.CERT_OPTIONAL
             tlsContext.load_verify_locations(cafile="ca.pem")
 
         print("Launching TLS responder..")
@@ -309,6 +312,15 @@ class DoTWithLocalResponderTests(RecursorTest):
         cls._TLSResponder.daemon = True
         cls._TLSResponder.start()
 
+    @classmethod
+    def tearDownResponders(cls):
+        cls._backgroundThreads[cls._TLSResponder.native_id] = False
+        count = 0
+        while count < 20 and len(cls._backgroundThreads) != 0:
+            print(f'Waiting for background responder thread to exit {count}...')
+            time.sleep(0.1)
+            count = count + 1
+
     def checkOnlyTLSResponderHit(self, numberOfTLSQueries=1):
         self.assertNotIn('UDP Responder', self._responsesCounter)
         self.assertNotIn('TCP Responder', self._responsesCounter)
@@ -381,12 +393,82 @@ webservice:
             'dot-outqueries': 1
         })
 
-class DoTOKWithClientCertOpenSSLTest(DoTWithLocalResponderTests):
+class DoTOKWithClientCertPEMTest(DoTWithLocalResponderTests):
+    """
+    This tests DoT to responder with openssl validation using a proper CA store for the locally generated cert
+    """
+
+    _confdir = 'DoTOKWithClientCertPEM'
+    _wsPort = 8042
+    _wsTimeout = 2
+    _wsPassword = 'secretpassword'
+    _apiKey = 'secretapikey'
+    _clientCert = True
+    _config_template = """
+dnssec:
+    validation: off
+outgoing:
+    dot_to_auth_names: [powerdns.com]
+    tls_configurations:
+    - name: dotwithverifyopensslandclientcert
+      ca_store: 'ca.pem'
+      subject_name: tls.tests.powerdns.com
+      subnets: ['127.0.0.1']
+      validate_certificate: true
+      verbose_logging: true
+      client_certificate: client.pem
+      client_certificate_key: client.key
+recursor:
+    forward_zones_recurse:
+      - zone: powerdns.com
+        forwarders: ['127.0.0.1:853']
+    devonly_regression_test_mode: true
+webservice:
+    webserver: true
+    port: %d
+    address: 127.0.0.1
+    password: %s
+    api_key: %s
+    """ % (_wsPort, _wsPassword, _apiKey)
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        super(DoTOKWithClientCertPEMTest, cls).generateRecursorYamlConfig(confdir, False)
+
+    def testUDP(self):
+        """
+        Outgoing TLS: UDP query is sent via TLS
+        """
+        name = 'udp.outgoing-tls.test.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        expectedResponse = dns.message.make_response(query, True)
+        rrset = dns.rrset.from_text(name,
+                                    15,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        expectedResponse.answer.append(rrset)
+
+        currentCount = 0
+        if 'TLS Responder' in self._responsesCounter:
+            currentCount = self._responsesCounter['TLS Responder']
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, expectedResponse)
+
+        # there was one TCP query
+        self.checkOnlyTLSResponderHit(currentCount + 1)
+        self.checkMetrics({
+            'dot-outqueries': 1
+        })
+
+class DoTOKWithClientCertPKCS12Test(DoTWithLocalResponderTests):
     """
     This tests DoT to responder with openssl validation using a proper CA store for the locally generated cert
     """
 
-    _confdir = 'DoTOKWithClientCertOpenSSL'
+    _confdir = 'DoTOKWithClientCertPKCS12'
     _wsPort = 8042
     _wsTimeout = 2
     _wsPassword = 'secretpassword'
@@ -421,7 +503,7 @@ webservice:
 
     @classmethod
     def generateRecursorConfig(cls, confdir):
-        super(DoTOKWithClientCertOpenSSLTest, cls).generateRecursorYamlConfig(confdir, False)
+        super(DoTOKWithClientCertPKCS12Test, cls).generateRecursorYamlConfig(confdir, False)
 
     def testUDP(self):
         """
@@ -450,7 +532,7 @@ webservice:
         self.checkMetrics({
             'dot-outqueries': 1
         })
-
+        
 class DoTOKGnuTLSTest(DoTWithLocalResponderTests):
     """
     This tests DoT to responder with gnutls validation using a proper CA store for the locally generated cert