From 3406ec2da63f7436f9b62b89de83696523d5efe3 Mon Sep 17 00:00:00 2001 From: Jaromir Wysoglad Date: Wed, 27 May 2026 08:46:30 +0000 Subject: [PATCH] Add TLS version parameters for start_wsgi_server This adds the tls_min_version and tls_max_version parameters for the start_wsgi_server (and start_http_server), allowing to restrict the min/max TLS version negotiated for accessing the server. New unit tests under TestWsgiTLS were added, which test the version parameters as well as the rest of TLS capabilities for the start_wsgi_server (TLS on/off, mTLS), since those previously didn't have any test coverage. Signed-off-by: Jaromir Wysoglad --- prometheus_client/exposition.py | 18 +++- tests/certs/client-cert.pem | 17 ++++ tests/certs/client-key.pem | 28 ++++++ tests/certs/server-ca.pem | 19 +++++ tests/certs/server-cert.pem | 19 +++++ tests/certs/server-key.pem | 28 ++++++ tests/test_exposition.py | 146 +++++++++++++++++++++++++++++++- 7 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 tests/certs/client-cert.pem create mode 100644 tests/certs/client-key.pem create mode 100644 tests/certs/server-ca.pem create mode 100644 tests/certs/server-cert.pem create mode 100644 tests/certs/server-key.pem diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py index 2d402a0f..5ddba226 100644 --- a/prometheus_client/exposition.py +++ b/prometheus_client/exposition.py @@ -196,6 +196,8 @@ def _get_ssl_ctx( cafile: Optional[str] = None, capath: Optional[str] = None, client_auth_required: bool = False, + tls_min_version: ssl.TLSVersion = ssl.TLSVersion.MINIMUM_SUPPORTED, + tls_max_version: ssl.TLSVersion = ssl.TLSVersion.MAXIMUM_SUPPORTED ) -> ssl.SSLContext: """Load context supports SSL.""" ssl_cxt = ssl.SSLContext(protocol=protocol) @@ -227,6 +229,9 @@ def _get_ssl_ctx( raise exc_type(f"Cannot load server certificate file {certfile!r} or " f"its private key file {keyfile!r}: {msg}") + ssl_cxt.minimum_version = tls_min_version + ssl_cxt.maximum_version = tls_max_version + return ssl_cxt @@ -240,6 +245,8 @@ def start_wsgi_server( client_capath: Optional[str] = None, protocol: int = ssl.PROTOCOL_TLS_SERVER, client_auth_required: bool = False, + tls_min_version: ssl.TLSVersion = ssl.TLSVersion.MINIMUM_SUPPORTED, + tls_max_version: ssl.TLSVersion = ssl.TLSVersion.MAXIMUM_SUPPORTED ) -> Tuple[WSGIServer, threading.Thread]: """Starts a WSGI server for prometheus metrics as a daemon thread.""" @@ -250,7 +257,16 @@ class TmpServer(ThreadingWSGIServer): app = make_wsgi_app(registry) httpd = make_server(addr, port, app, TmpServer, handler_class=_SilentHandler) if certfile and keyfile: - context = _get_ssl_ctx(certfile, keyfile, protocol, client_cafile, client_capath, client_auth_required) + context = _get_ssl_ctx( + certfile, + keyfile, + protocol, + client_cafile, + client_capath, + client_auth_required, + tls_min_version, + tls_max_version + ) httpd.socket = context.wrap_socket(httpd.socket, server_side=True) t = threading.Thread(target=httpd.serve_forever) t.daemon = True diff --git a/tests/certs/client-cert.pem b/tests/certs/client-cert.pem new file mode 100644 index 00000000..5a054199 --- /dev/null +++ b/tests/certs/client-cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICrzCCAZcCFCVu7nbOAxRNKBYa2cl22rdRCtvfMA0GCSqGSIb3DQEBCwUAMBIx +EDAOBgNVBAMMB1Rlc3QgQ0EwHhcNMjYwNTI2MTQyMTU0WhcNMzYwNTIzMTQyMTU0 +WjAWMRQwEgYDVQQDDAt0ZXN0LWNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAJM2/f+8BBKjAlSF/9eiuB2444A2g6V007U5shZhBuPC9cNDxGKM +W1WT3QsgvxOdagdaANkpufqHcYixgFhx/v3lSEzlzd3uXyFMOiK7BdiPsctkqlWZ +VGIuUPpWwvJHWS4R5V1nYNCVsgyZB9XGThl7IknQzBK+tkY2GepqPQXyx1/AP7aB +AlTVBx3r7jTWvkrzvAdrcevrjhOOJUbPmgoiiEGSQeZSMvkdLERujvu5Y3wno2Mg +vcHJxCJwZ5y0RakmTzyAZLHke9lMavgt9F5yEA8G/8SnnXy6HrUp6B6I8Z1eLnof +b3mjUwiGxqDwEVBQHfMtOH6uC7ZE6zbNB1cCAwEAATANBgkqhkiG9w0BAQsFAAOC +AQEAJBchyhT2iyg42qi3uUE1NeCcEb/gM82LeihZbDd38ItUdU7TFqk7wEwsUNJk +k1uwNFVlyMGbHD1IvCAS4L8l/9uPaDG4DmLZ42shFRCaABNEFlKtGPa+YNuhFJ5z +DZKaLaJp8BKpvmoH+iPmsoCDlADwWmLgbdeFBGnHRuOnJBSmEEjQFrnz3jKrX6Lk ++IxVX5Rdp9xOKHBJkj99mgseEYZQk2YFFBCzHX7NNl6wBk/usKJoJeaOPhl9eOGK +VaUOfEdO5NuTRf9nPOORzqFtW3ErNjNjPjKN8VppHtXhRO6dWsmzGnmjVChxoZWC +H0rRJtGcab5HWf94laJilCj7Cw== +-----END CERTIFICATE----- diff --git a/tests/certs/client-key.pem b/tests/certs/client-key.pem new file mode 100644 index 00000000..e218a006 --- /dev/null +++ b/tests/certs/client-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCTNv3/vAQSowJU +hf/XorgduOOANoOldNO1ObIWYQbjwvXDQ8RijFtVk90LIL8TnWoHWgDZKbn6h3GI +sYBYcf795UhM5c3d7l8hTDoiuwXYj7HLZKpVmVRiLlD6VsLyR1kuEeVdZ2DQlbIM +mQfVxk4ZeyJJ0MwSvrZGNhnqaj0F8sdfwD+2gQJU1Qcd6+401r5K87wHa3Hr644T +jiVGz5oKIohBkkHmUjL5HSxEbo77uWN8J6NjIL3BycQicGectEWpJk88gGSx5HvZ +TGr4LfRechAPBv/Ep518uh61KegeiPGdXi56H295o1MIhsag8BFQUB3zLTh+rgu2 +ROs2zQdXAgMBAAECggEAATafUlJzkCRtelKJFiG+YGmr37HTVPOeY8HVe29noKH0 +kkbxNpoPOKiEK7l53wiu8oo7M+RZpucjOEFfnEWtmIchbkIoomR6vpSubVHa+FAl +jYEcvEw2u1ZuuW7Uotg+s8KsVXWVgTKdVJLq/cfpezaeGjtRK0hiH+MF71OFLD2I +UoszlVbTI9FAP+xwuFSJO4xyOirz2VmqgYvQd+qTuuPU2ZjPHFbBUXm6JDpchGJk +WdPp/7qEWKFwDufvgkA5rCFxwsiReQ9HfOS2f4l+7eg2uyjXAClYTt/lYq9PK1Ut +sk/R1Gq5C4S8G0f04Jk8J2bQKS57oRALfaJEps5LUQKBgQDPCPID4w9TnAhWoHtR +L5ps02KLi52sw9F3EVedVX2BjMM/jvRwtzg8I8iaWAE1iL0t8lDQRxbUcgNyWRvi +0/WG/2IESVlciqd4XuITLthj1PDIpIM2iCjQZpZKDqe9bVRPx/AY+UNV1aLSCEbF +xGS+uYoQRGpmiSYnRaQzIzgn0QKBgQC2CDOD1/1sEVFbfsWJTaAM3YjsQ1I5mXFI +HhoWpMKBUogWBXp9dzO4Ae/iRo0QviVUUY2bHlJjCoaQ0FzuiieZIhbOwHG2Qtf3 +JzmUaOSMecwsTeM05XHciwY+sWU/Udw7EzDhVpOHPZR31LKeapchUJGnofnOdkcY +zaHEwiuupwKBgQC6I0bD698Zws1UZRC6G1xxv1N4NtxaOewXawYktHoUgaQBftuS +g4gRufJfogPkR74ekx/JQkDqXF9w7WC+/OZgqzdKt0+afia3eEc2DAYNK6QYIKC/ +5IcdZz5z8t0o2CTXXeEl8uVxRJQQ1dQbdslFGLdijMBE08XzxQ8t0tpoIQKBgH09 +U0QovME3gQQ0SnBXKgDwAp6bCt16RshZfZWKshAL2nlcN5RPCRRWsNa7t56HVGOY +4JaS3BgsS70ivm2YO/pNy+df3FyLzM7M+/6x1F0aB3GL/QCNxDL6q8dCgeh4x88V +OxIuYL4xjg6MFoCL0YMoTa5J8PctxWi5Qc1/0lINAoGASyTZT8emfSDW0+kpqiYw +y+4ftFxqYPAVCf2IWGeQL8TrfkxUiJ7r4Pu5VK9nuYvMR/u4mvnJSG6F1NuJhxzY +4kUnoOnPhITLZjUvNE/xQEuhiJndiehZgSj0JAU6MqGa4pZOxZfqPAfhEF62b5wx +6Wlh7JxQAM+6agEfM3/OS3Y= +-----END PRIVATE KEY----- diff --git a/tests/certs/server-ca.pem b/tests/certs/server-ca.pem new file mode 100644 index 00000000..bbcada29 --- /dev/null +++ b/tests/certs/server-ca.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBTCCAe2gAwIBAgIUMrjGc/qUt+rpFb14OvBFePSMQRIwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yNjA1MjYxNDIxNTNaFw0zNjA1MjMx +NDIxNTNaMBIxEDAOBgNVBAMMB1Rlc3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDFC2wPWOqoFHIYXASjL06yUnG0SlqLqw4oZphdj/q4pbyYPcva +QKI8m4u7Tq/l8JQmbd0sHqGMVYLmACX5ygkppzepz/bVgDeij7RztUgDjJwvUxAC +SEAss0dcE19P57j5ad24xmyV2iP0RK7oXnjapDrH1fhqvIyfybqRxt+50NODRh1t +z471240lDBPOG3ReRZ06dYEpzYaq3PQPatPJnaLGOmsf2NQ8sETTK35vcTMZrXsr +vzrftUCKn4DRyyZ58GE1VpevbVi8z/vHzWBYpRcHTZvfnOz12ijCd2wvnEtTu8TO ++GZS5j84KSF4AI7FlhDMPAS3/dhSLzXgnd4lAgMBAAGjUzBRMB0GA1UdDgQWBBRi +IztvE2ErRLmziv1XxxHCbism8DAfBgNVHSMEGDAWgBRiIztvE2ErRLmziv1XxxHC +bism8DAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAjBA50Oadg +Fx6d/cxzHCEd29BluqxjfzYc4TAeTP4NSQPWKXM7BgIqZbHDS/IAK/Xfd1raVCm5 +yv2v4Pe+0fTkezUk5uZjtgZoH5+o4aQL9GdbLO93F4rxzZhpoY92iaXAsoDEntRO +YyDnxLna6csiH4hyvr6Q8Yih/lDysw7DB1jozkFeZtX4ZsVFpsDnYLa5OQjJErpw +9GCM0NEzEW6HlqblsAuBv3DHavUAzfR4obD+Md60BRMxwC5Otl63sS99y81ycs2S +ffW0rLDtgB9hShCXBNeZkGsPrLwBr00nK7bvGaZwOU0Ysuxg+elP3cOXD2UcW7lc +Q+ZBinkQEFpB +-----END CERTIFICATE----- diff --git a/tests/certs/server-cert.pem b/tests/certs/server-cert.pem new file mode 100644 index 00000000..e1f11670 --- /dev/null +++ b/tests/certs/server-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDEjCCAfqgAwIBAgIUJW7uds4DFE0oFhrZyXbat1EK294wDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yNjA1MjYxNDIxNTNaFw0zNjA1MjMx +NDIxNTNaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAOl3Gnz9y+OHr3MIetuX7a/Bvo/lYkwTnmQpT4YC1ycalM0+ +M4wCewrbpn5RoNbH4j+oB6URuiLWlhk3SP9hOwbVbt4rOyCaulAVa5G0B46eqkG6 +ZDi9EWkt+L7Zvfwe+MbdG25xmRumkvc6iv8b+/0mM3t5cm3E8BHpwPi6kfoFmFHn +qREDfI4406tBkbSYUkRQL8qZvSWMo5HgIYmgrkKiWuZNV4bNYKHUOyaOqWvgn8En +ZxrlGt2ezHif/SFz+EaYJZkjBDJ5rMbwxl1BAVZKpSubKt5U59zXLkuachZF5sAY +sEd/LoZxF23/qP5y16C4mVzBYO/0z2udNOW9YAcCAwEAAaNeMFwwGgYDVR0RBBMw +EYIJbG9jYWxob3N0hwR/AAABMB0GA1UdDgQWBBSUEOHINPcDxtBsfNF5xSTUSqfF +vDAfBgNVHSMEGDAWgBRiIztvE2ErRLmziv1XxxHCbism8DANBgkqhkiG9w0BAQsF +AAOCAQEALxRf0TSusmJXO9pj5t3Njxc6VS+Ts/MnmE1NTloCCkVMEfYYzqROWHME +LOCg2YSnqX6S6Gwk3zjBSuT7aA4SNQ3lD9HndRYa5k+6/6qunnz5Q/g205GJ97us +HqkvdDjLE7lGmM5pIVjoyeMOWiQ6+EOtMt0CmL0nfqJ0DsDUZHVB7NB+MW20EVmC +XiXr52SuvKHDIms3QFkZWOi+scOKleQnvEVU7VqrQamKNtf8fxxGa3/AvjLLJ1ra +q9eB590eajBDdg50FttYLwyA/yb6cqrfIMfrHRj3R//yE2avtUkrKN6FgCtqgpoa +ZsIk6qEmFQWUTglyLwhk0f6m21FKAA== +-----END CERTIFICATE----- diff --git a/tests/certs/server-key.pem b/tests/certs/server-key.pem new file mode 100644 index 00000000..68f2b805 --- /dev/null +++ b/tests/certs/server-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDpdxp8/cvjh69z +CHrbl+2vwb6P5WJME55kKU+GAtcnGpTNPjOMAnsK26Z+UaDWx+I/qAelEboi1pYZ +N0j/YTsG1W7eKzsgmrpQFWuRtAeOnqpBumQ4vRFpLfi+2b38HvjG3RtucZkbppL3 +Oor/G/v9JjN7eXJtxPAR6cD4upH6BZhR56kRA3yOONOrQZG0mFJEUC/Kmb0ljKOR +4CGJoK5ColrmTVeGzWCh1Dsmjqlr4J/BJ2ca5Rrdnsx4n/0hc/hGmCWZIwQyeazG +8MZdQQFWSqUrmyreVOfc1y5LmnIWRebAGLBHfy6GcRdt/6j+cteguJlcwWDv9M9r +nTTlvWAHAgMBAAECggEAJ2/ZlxyOGPC+J+naSwbWfTZ2mLsQSDaWLmg2CTaonm/k +i+kCbxeqLjLdZIAoca+RHdyl8fHVJfZmo3rNx2nmvShHkprt4XuRll6P7axiDGrr +6q9wJ490hfZgiuigKZsXvgvyis0ApoWUVNPcT+yru978mlJxDG7UeMoqMTne18NQ +jKw8zTPrcDnSqxdzNs9hGcM/RYDAnNM1jFqnmJpJF9nTMf9gDOYQMJCb7yUZVOj/ +ccyc2AjTdp6corXZpSqiYHz4UwfZ0Wvf7BXwBAOxdYnM6qlZXc+RKZJYnlUbSNHZ +IRkIZXsRILAIgpL1fAhMAQodyFMjNU9wQAkva6j49QKBgQD8ohxWR3nKg9KXhSTU +7sv1E5WzUCEAX5gJTSRx9S8lAAotGEByaTO/pWcYtpo+jXukukERhDdnsxR7SJAf +7jOYLUQgqFgZXD/U7vaCNuYoRG0E1MwSqZnVIXOpZW2z6j0PwcbXLJVeLDpDctn1 +Ga7VvYiv7/KuvRSQfkSOrH8USwKBgQDsk5lLSOQb7Ke53gfHjeGMU4646JcNDFnD +hWtXQujABwQZmSDWudCvsLWwDr0O0kUDqDcPCEMhbNYo388DwowzHnE35Tzmzo5D +R/YZ+Mh+UuW+e5gLxdmn7Z1xENKft/4ceOkeBBExQumZYYsaNVA7znzotaPSjRfH +J36QHsG1tQKBgQC9NMBaUf/CD4ZaWqpiG1J/gyJ8AEgnGnEojjD8dC/R2zzD10T1 +KxtJrhwPozrUHGx8y83Ny6MfNDzjtE3UzDayAzzh5JLOs4tO84WFso4fnFe15ZXN +aF5BBGO2e7N0qrr+oRdFsitQM3mTaGIashiCFghYFDJCcnQDX74CyOgIDwKBgCht +JHHf99LpwtOZJF0uWo9/K9FfNYiuRpyJrQkRTvKZgFLbfugSgp2zJaj7K8Vfmxl/ +4kC4WbhZf9MmQ5rR4OFPX2t8ycZrH5ZRsrVHdQNZKRc+yYGhgosWqKPMiyFt8Idv +Be7yJPn1BDQInhuRZq+BnoipmV/+akTG8/Kuvs1NAoGALLN5lPRdZdTvjoiougt9 +MxqGfBR9H8PfAo/Eu8Et5Otln3P1Vl3SgeiwDGVb59avfBQ5N4UecTHMp/2jbxOw +w/AzvF9LMLtXKdyqnOeBfP2xgbEZ9chLeoePEYkATpQfgjs7qmzK+mwZin5EyjFa +tqn7AnX5AnDRtPIC10Z05rA= +-----END PRIVATE KEY----- diff --git a/tests/test_exposition.py b/tests/test_exposition.py index a3c97820..1885480f 100644 --- a/tests/test_exposition.py +++ b/tests/test_exposition.py @@ -1,9 +1,11 @@ import gzip from http.server import BaseHTTPRequestHandler, HTTPServer import os +import ssl import threading import time import unittest +import urllib import pytest @@ -16,7 +18,7 @@ from prometheus_client.core import GaugeHistogramMetricFamily, Timestamp from prometheus_client.exposition import ( basic_auth_handler, choose_encoder, default_handler, MetricsHandler, - passthrough_redirect_handler, tls_auth_handler, + passthrough_redirect_handler, start_wsgi_server, tls_auth_handler, ) import prometheus_client.openmetrics.exposition as openmetrics @@ -633,6 +635,148 @@ def test_prom_no_version(self): self.assert_is_prom(exp) +class TestWsgiTLS(unittest.TestCase): + def setUp(self): + self.certs_dir = os.path.join( + os.path.dirname(os.path.realpath(__file__)), 'certs' + ) + self.httpd = None + self.t = None + + def tearDown(self): + if self.httpd: + self.httpd.shutdown() + self.httpd.server_close() + self.t.join() + + def _assert_tls_connection( + self, + server_kwargs, + use_server_tls=True, + client_tls_kwargs=None, + request_tls_version=ssl.TLSVersion.TLSv1_3, + expect_exception=None + ): + self.httpd, self.t = start_wsgi_server(port=0, **server_kwargs) + port = self.httpd.server_address[1] + + if use_server_tls: + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.minimum_version = request_tls_version + ctx.maximum_version = request_tls_version + ctx.load_verify_locations( + os.path.join(self.certs_dir, "server-ca.pem") + ) + + if client_tls_kwargs is not None: + ctx.load_cert_chain(**client_tls_kwargs) + + url = f"https://localhost:{port}/metrics" + else: + ctx = None + url = f"http://localhost:{port}/metrics" + + if expect_exception is not None: + self.assertRaises( + expect_exception, + urllib.request.urlopen, + url, + context=ctx + ) + else: + response = urllib.request.urlopen(url, context=ctx) + self.assertEqual(response.status, 200) + + def test_tls_disabled(self): + self._assert_tls_connection(server_kwargs={}, use_server_tls=False) + + def test_tls_enabled(self): + server_kwargs = { + "certfile": os.path.join(self.certs_dir, "server-cert.pem"), + "keyfile": os.path.join(self.certs_dir, "server-key.pem"), + } + self._assert_tls_connection(server_kwargs) + + def test_tls_untrusted_server_cert_raises(self): + server_kwargs = { + "certfile": os.path.join(self.certs_dir, "cert.pem"), + "keyfile": os.path.join(self.certs_dir, "key.pem"), + } + self._assert_tls_connection( + server_kwargs, + expect_exception=urllib.error.URLError + ) + + def test_tls_versions_configured_correctly(self): + server_kwargs = { + "certfile": os.path.join(self.certs_dir, "server-cert.pem"), + "keyfile": os.path.join(self.certs_dir, "server-key.pem"), + "tls_min_version": ssl.TLSVersion.TLSv1_2, + "tls_max_version": ssl.TLSVersion.TLSv1_3, + } + self._assert_tls_connection( + server_kwargs, + request_tls_version=ssl.TLSVersion.TLSv1_2 + ) + + def test_tls_using_lower_version_than_min_raises(self): + server_kwargs = { + "certfile": os.path.join(self.certs_dir, "server-cert.pem"), + "keyfile": os.path.join(self.certs_dir, "server-key.pem"), + "tls_min_version": ssl.TLSVersion.TLSv1_3, + } + self._assert_tls_connection( + server_kwargs, + request_tls_version=ssl.TLSVersion.TLSv1_2, + expect_exception=urllib.error.URLError + ) + + def test_tls_using_higher_version_than_max_raises(self): + server_kwargs = { + "certfile": os.path.join(self.certs_dir, "server-cert.pem"), + "keyfile": os.path.join(self.certs_dir, "server-key.pem"), + "tls_max_version": ssl.TLSVersion.TLSv1_2, + } + self._assert_tls_connection( + server_kwargs, + request_tls_version=ssl.TLSVersion.TLSv1_3, + expect_exception=urllib.error.URLError + ) + + def test_mtls_enabled(self): + server_kwargs = { + "certfile": os.path.join(self.certs_dir, "server-cert.pem"), + "keyfile": os.path.join(self.certs_dir, "server-key.pem"), + "client_auth_required": True, + "client_cafile": os.path.join(self.certs_dir, "server-ca.pem"), + } + client_tls_kwargs = { + "certfile": os.path.join(self.certs_dir, "client-cert.pem"), + "keyfile": os.path.join(self.certs_dir, "client-key.pem") + } + self._assert_tls_connection( + server_kwargs, + client_tls_kwargs=client_tls_kwargs + ) + + def test_mtls_untrusted_client_cert_raises(self): + server_kwargs = { + "certfile": os.path.join(self.certs_dir, "server-cert.pem"), + "keyfile": os.path.join(self.certs_dir, "server-key.pem"), + "client_auth_required": True, + "client_cafile": os.path.join(self.certs_dir, "server-cert.pem"), + } + client_tls_kwargs = { + "certfile": os.path.join(self.certs_dir, "cert.pem"), + "keyfile": os.path.join(self.certs_dir, "key.pem") + } + self._assert_tls_connection( + server_kwargs, + client_tls_kwargs=client_tls_kwargs, + expect_exception=ssl.SSLError + ) + + @pytest.mark.parametrize("scenario", [ { "name": "empty string",