From 0d6bb7cefe04b412e1a8138b0605628908e77b8b Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Fri, 9 Jul 2021 16:12:26 -0700 Subject: [PATCH] PYTHON-2798 Workaround windows cert issue with SSL_CERT_FILE --- .evergreen/run-tests.sh | 14 ++++++----- test/__init__.py | 24 ++++++++++++++++++- test/test_encryption.py | 48 +++++++++++++++++++++---------------- test/test_ssl.py | 53 ++++++++++++++++------------------------- 4 files changed, 79 insertions(+), 60 deletions(-) diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 00d095d727..d45e9d5235 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -146,13 +146,15 @@ if [ -n "$TEST_ENCRYPTION" ]; then . $DRIVERS_TOOLS/.evergreen/csfle/set-temp-creds.sh # Start the mock KMS servers. - if [ "$OS" != "Windows_NT" ]; then - pushd ${DRIVERS_TOOLS}/.evergreen/csfle - python -u lib/kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/expired.pem --port 8000 & - python -u lib/kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/wrong-host.pem --port 8001 & - trap 'kill $(jobs -p)' EXIT HUP - popd + if [ "$OS" = "Windows_NT" ]; then + # Remove after BUILD-13574. + python -m pip install certifi fi + pushd ${DRIVERS_TOOLS}/.evergreen/csfle + python -u lib/kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/expired.pem --port 8000 & + python -u lib/kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/wrong-host.pem --port 8001 & + trap 'kill $(jobs -p)' EXIT HUP + popd fi if [ -z "$DATA_LAKE" ]; then diff --git a/test/__init__.py b/test/__init__.py index fa03de3263..49d20ccfb8 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -49,7 +49,7 @@ from pymongo import common, message from pymongo.common import partition_node from pymongo.server_api import ServerApi -from pymongo.ssl_support import HAVE_SSL, validate_cert_reqs +from pymongo.ssl_support import HAVE_SSL, _ssl from pymongo.uri_parser import parse_uri from test.version import Version @@ -898,6 +898,10 @@ def setUpClass(cls): else: cls.credentials = {} + def patch_system_certs(self, ca_certs): + patcher = SystemCertsPatcher(ca_certs) + self.addCleanup(patcher.disable) + # Use assertRaisesRegex if available, otherwise use Python 2.7's # deprecated assertRaisesRegexp, with a 'p'. @@ -1043,3 +1047,21 @@ def clear_warning_registry(): for name, module in list(sys.modules.items()): if hasattr(module, "__warningregistry__"): setattr(module, "__warningregistry__", {}) + + +class SystemCertsPatcher(object): + def __init__(self, ca_certs): + if (ssl.OPENSSL_VERSION.lower().startswith('libressl') and + sys.platform == 'darwin' and not _ssl.IS_PYOPENSSL): + raise SkipTest( + "LibreSSL on OSX doesn't support setting CA certificates " + "using SSL_CERT_FILE environment variable.") + self.original_certs = os.environ.get('SSL_CERT_FILE') + # Tell OpenSSL where CA certificates live. + os.environ['SSL_CERT_FILE'] = ca_certs + + def disable(self): + if self.original_certs is None: + os.environ.pop('SSL_CERT_FILE') + else: + os.environ['SSL_CERT_FILE'] = self.original_certs diff --git a/test/test_encryption.py b/test/test_encryption.py index d79339d41b..6a71a5a424 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -19,7 +19,6 @@ import os import traceback import socket -import ssl import sys import textwrap import uuid @@ -50,10 +49,14 @@ WriteError) from pymongo.mongo_client import MongoClient from pymongo.operations import InsertOne -from pymongo.ssl_support import _ssl from pymongo.write_concern import WriteConcern +from test.test_ssl import CA_PEM -from test import unittest, IntegrationTest, PyMongoTestCase, client_context +from test import (unittest, + client_context, + IntegrationTest, + PyMongoTestCase, + SystemCertsPatcher) from test.utils import (TestCreator, camel_to_snake_args, OvertCommandListener, @@ -62,7 +65,26 @@ rs_or_single_client, wait_until) from test.utils_spec_runner import SpecRunner -from test.test_ssl import CA_PEM + +try: + import certifi + HAVE_CERTIFI = True +except ImportError: + HAVE_CERTIFI = False + +patcher = None + + +def setUpModule(): + if sys.platform == 'win32' and HAVE_CERTIFI: + # Remove after BUILD-13574. + global patcher + patcher = SystemCertsPatcher(certifi.where()) + + +def tearDownModule(): + if patcher: + patcher.disable() def get_client_opts(client): @@ -1629,25 +1651,11 @@ def test_bypassAutoEncryption(self): # https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#kms-tls-tests class TestKmsTLSProse(EncryptionIntegrationTest): - @unittest.skipIf(sys.platform == 'win32', - "Can't test system ca certs on Windows") - @unittest.skipIf(ssl.OPENSSL_VERSION.lower().startswith('libressl') and - sys.platform == 'darwin' and not _ssl.IS_PYOPENSSL, - "LibreSSL on OSX doesn't support setting CA certificates " - "using SSL_CERT_FILE environment variable.") @unittest.skipUnless(any(AWS_CREDS.values()), 'AWS environment credentials are not set') def setUp(self): - self.original_certs = os.environ.get('SSL_CERT_FILE') - def restore_certs(): - if self.original_certs is None: - os.environ.pop('SSL_CERT_FILE') - else: - os.environ['SSL_CERT_FILE'] = self.original_certs - # Tell OpenSSL where CA certificates live. - os.environ['SSL_CERT_FILE'] = CA_PEM - self.addCleanup(restore_certs) - + super(TestKmsTLSProse, self).setUp() + self.patch_system_certs(CA_PEM) self.client_encrypted = ClientEncryption( {'aws': AWS_CREDS}, 'keyvault.datakeys', self.client, OPTS) self.addCleanup(self.client_encrypted.close) diff --git a/test/test_ssl.py b/test/test_ssl.py index ee153db28a..65ed16968c 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -449,45 +449,32 @@ def test_validation_with_system_ca_certs(self): # --sslCAFile=/path/to/pymongo/test/certificates/ca.pem # --sslWeakCertificateValidation # - if sys.platform == "win32": - raise SkipTest("Can't test system ca certs on Windows.") - - if (ssl.OPENSSL_VERSION.lower().startswith('libressl') and - sys.platform == 'darwin' and not _ssl.IS_PYOPENSSL): - raise SkipTest( - "LibreSSL on OSX doesn't support setting CA certificates " - "using SSL_CERT_FILE environment variable.") - - # Tell OpenSSL where CA certificates live. - os.environ['SSL_CERT_FILE'] = CA_PEM - try: - with self.assertRaises(ConnectionFailure): - # Server cert is verified but hostname matching fails - connected(MongoClient('server', - ssl=True, - serverSelectionTimeoutMS=100, - **self.credentials)) - - # Server cert is verified. Disable hostname matching. + self.patch_system_certs(CA_PEM) + with self.assertRaises(ConnectionFailure): + # Server cert is verified but hostname matching fails connected(MongoClient('server', ssl=True, - ssl_match_hostname=False, serverSelectionTimeoutMS=100, **self.credentials)) - # Server cert and hostname are verified. - connected(MongoClient('localhost', - ssl=True, - serverSelectionTimeoutMS=100, - **self.credentials)) + # Server cert is verified. Disable hostname matching. + connected(MongoClient('server', + ssl=True, + ssl_match_hostname=False, + serverSelectionTimeoutMS=100, + **self.credentials)) - # Server cert and hostname are verified. - connected( - MongoClient( - 'mongodb://localhost/?ssl=true&serverSelectionTimeoutMS=100', - **self.credentials)) - finally: - os.environ.pop('SSL_CERT_FILE') + # Server cert and hostname are verified. + connected(MongoClient('localhost', + ssl=True, + serverSelectionTimeoutMS=100, + **self.credentials)) + + # Server cert and hostname are verified. + connected( + MongoClient( + 'mongodb://localhost/?ssl=true&serverSelectionTimeoutMS=100', + **self.credentials)) def test_system_certs_config_error(self): ctx = get_ssl_context(