From: Remi Gacogne Date: Wed, 2 Apr 2025 13:31:26 +0000 (+0200) Subject: dnsdist: Check identical frontends get the same STEK X-Git-Tag: dnsdist-2.0.0-alpha2~88^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0e8460c1fc9b9b3a5d0e1ff3196ab6a3ff53b468;p=thirdparty%2Fpdns.git dnsdist: Check identical frontends get the same STEK --- diff --git a/regression-tests.dnsdist/dnsdisttests.py b/regression-tests.dnsdist/dnsdisttests.py index cc9bd25b9f..53c97b04b4 100644 --- a/regression-tests.dnsdist/dnsdisttests.py +++ b/regression-tests.dnsdist/dnsdisttests.py @@ -663,7 +663,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase): return sock @classmethod - def openTLSConnection(cls, port, serverName, caCert=None, timeout=2.0, alpn=[]): + def openTLSConnection(cls, port, serverName, caCert=None, timeout=2.0, alpn=[], sslctx=None, session=None): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) if timeout: @@ -671,10 +671,11 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase): # 2.7.9+ if hasattr(ssl, 'create_default_context'): - sslctx = ssl.create_default_context(cafile=caCert) - if len(alpn)> 0 and hasattr(sslctx, 'set_alpn_protocols'): - sslctx.set_alpn_protocols(alpn) - sslsock = sslctx.wrap_socket(sock, server_hostname=serverName) + if not sslctx: + sslctx = ssl.create_default_context(cafile=caCert) + if len(alpn)> 0 and hasattr(sslctx, 'set_alpn_protocols'): + sslctx.set_alpn_protocols(alpn) + sslsock = sslctx.wrap_socket(sock, server_hostname=serverName, session=session) else: sslsock = ssl.wrap_socket(sock, ca_certs=caCert, cert_reqs=ssl.CERT_REQUIRED) diff --git a/regression-tests.dnsdist/test_TLSSessionResumption.py b/regression-tests.dnsdist/test_TLSSessionResumption.py index 7f41c79491..b46eded2cb 100644 --- a/regression-tests.dnsdist/test_TLSSessionResumption.py +++ b/regression-tests.dnsdist/test_TLSSessionResumption.py @@ -2,7 +2,9 @@ import base64 import dns import os +import requests import shutil +import ssl import subprocess import tempfile import time @@ -297,3 +299,92 @@ class TestTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest): # reload from file 2, the latest session should resume self.sendConsoleCommand("getTLSFrontend(0):loadTicketsKeys('/tmp/ticketKeys.2')") self.assertTrue(self.checkSessionResumed('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert, '/tmp/session.dot.2', '/tmp/session.dot.2', allowNoTicket=True)) + +class TestTLSSessionResumptionDOTIdenticalFrontends(DNSDistTest): + """ + Check that identical frontends share the same TLS session ticket encryption key + """ + _webTimeout = 2.0 + _webServerPort = pickAvailablePort() + _webServerBasicAuthPassword = 'secret' + _webServerAPIKey = 'apisecret' + _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM=' + _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso=' + _serverKey = 'server.key' + _serverCert = 'server.chain' + _serverName = 'tls.tests.dnsdist.org' + _caCert = 'ca.pem' + _tlsServerPort = pickAvailablePort() + _numberOfIdenticalFrontends = 10 + _config_template = "" + _config_params = [] + _yaml_config_template = """--- +webserver: + listen_address: "127.0.0.1:%d" + password: "%s" + api_key: "%s" + acl: + - 127.0.0.0/8 +backends: + - address: "127.0.0.1:%d" + protocol: "Do53" +binds: + - listen_address: "127.0.0.1:%d" + reuseport: true + protocol: "DoT" + threads: %d + tls: + certificates: + - certificate: "%s" + key: "%s" + provider: "openssl" + """ + _yaml_config_params = ['_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed', '_testServerPort', '_tlsServerPort', '_numberOfIdenticalFrontends', '_serverCert', '_serverKey'] + + def testTLSSessionResumptionAcrossIdenticalFrontends(self): + """ + TCP Session Resumption: Check that identical frontends share the same STEK + """ + name = 'identical-frontends.tls-session-resumption.tests.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + response = dns.message.make_response(query) + + session = None + sslctx = ssl.create_default_context(cafile=self._caCert) + nb_connections = self._numberOfIdenticalFrontends * 20 + for idx in range(nb_connections): + conn = self.openTLSConnection(self._tlsServerPort, self._serverName, self._caCert, timeout=1, sslctx=sslctx, session=session) + self.sendTCPQueryOverConnection(conn, query, response=response, timeout=1) + (receivedQuery, receivedResponse) = self.recvTCPResponseOverConnection(conn, useQueue=True, timeout=1) + receivedQuery.id = query.id + self.assertEqual(receivedQuery, query) + self.assertEqual(receivedResponse, response) + if idx == 0: + self.assertFalse(conn.session_reused) + session = conn.session + else: + self.assertTrue(conn.session_reused) + + headers = {'x-api-key': self._webServerAPIKey} + url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost' + r = requests.get(url, headers=headers, timeout=self._webTimeout) + self.assertTrue(r) + self.assertEqual(r.status_code, 200) + self.assertTrue(r.json()) + content = r.json() + self.assertTrue(len(content['frontends']), self._numberOfIdenticalFrontends + 2) + + new_sessions = 0 + resumed_sessions = 0 + tls_frontends_seen = {} + for frontend in content['frontends']: + if frontend['type'] != 'TCP (DNS over TLS)': + continue + new_sessions = new_sessions + int(frontend['tlsNewSessions']) + resumed_sessions = resumed_sessions + int(frontend['tlsResumptions']) + if int(frontend['tlsNewSessions']) > 0 or int(frontend['tlsResumptions']) > 0: + tls_frontends_seen[frontend['id']] = True + + self.assertEqual(new_sessions, 1) + self.assertEqual(resumed_sessions, nb_connections - new_sessions) + self.assertGreater(len(tls_frontends_seen), 1)