diff --git a/pymongo/pool.py b/pymongo/pool.py index 75cdc4706..d418354c1 100644 --- a/pymongo/pool.py +++ b/pymongo/pool.py @@ -201,10 +201,18 @@ class Pool: # match_hostname directly disable this explicitly. if hasattr(self.ssl_ctx, "check_hostname"): self.ssl_ctx.check_hostname = False + # load_cert_chain and load_verify_locations can fail + # if the file contents are invalid. if ssl_certfile is not None: - self.ssl_ctx.load_cert_chain(ssl_certfile, ssl_keyfile) + try: + self.ssl_ctx.load_cert_chain(ssl_certfile, ssl_keyfile) + except ssl.SSLError: + pass if ssl_ca_certs is not None: - self.ssl_ctx.load_verify_locations(ssl_ca_certs) + try: + self.ssl_ctx.load_verify_locations(ssl_ca_certs) + except ssl.SSLError: + pass # PROTOCOL_TLS_CLIENT sets verify_mode to CERT_REQUIRED so # we always have to set this explicitly. if ssl_cert_reqs is not None: diff --git a/test/__init__.py b/test/__init__.py index b66b3d9b4..94d367edf 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -76,11 +76,11 @@ pair = '%s:%d' % (host, port) class AuthContext(object): - def __init__(self): + def __init__(self, **kwargs): self.auth_enabled = False self.restricted_localhost = False try: - self.client = pymongo.MongoClient(host, port) + self.client = pymongo.MongoClient(host, port, **kwargs) except ConnectionFailure: self.client = None else: diff --git a/test/test_ssl.py b/test/test_ssl.py index 05e4c1c05..c182b9242 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -36,8 +36,8 @@ from pymongo.common import HAS_SSL, validate_cert_reqs from pymongo.errors import (ConfigurationError, ConnectionFailure, OperationFailure) -from test import host, port, pair, version -from test.utils import server_started_with_auth, remove_all_users +from test import host, port, pair, version, db_user, db_pwd, AuthContext +from test.utils import remove_all_users CERT_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), @@ -99,6 +99,13 @@ if HAS_SSL: if CERT_SSL: SERVER_IS_RESOLVABLE = is_server_resolvable() + if SIMPLE_SSL or CERT_SSL: + ssl_auth_ctx = AuthContext(ssl=True, ssl_certfile=CLIENT_PEM) + if ssl_auth_ctx.auth_enabled: + ssl_auth_ctx.add_user_and_log_in() + else: + ssl_auth_ctx = None + class TestClientSSL(unittest.TestCase): @@ -245,6 +252,9 @@ class TestSSL(unittest.TestCase): raise SkipTest("Python 3.0.x has problems " "with SSL and socket timeouts.") + if ssl_auth_ctx and ssl_auth_ctx.auth_enabled: + raise SkipTest("Can't test with auth enabled.") + def test_simple_ssl(self): # Expects the server to be running with ssl and with # no --sslPEMKeyFile or with --sslWeakCertificateValidation @@ -486,6 +496,17 @@ class TestSSL(unittest.TestCase): MongoClient(uri + "&ssl_match_hostname=false") + +class TestX509Auth(unittest.TestCase): + + def setUp(self): + if not HAS_SSL: + raise SkipTest("The ssl module is not available.") + + if sys.version.startswith('3.0'): + raise SkipTest("Python 3.0.x has problems " + "with SSL and socket timeouts.") + def test_mongodb_x509_auth(self): # Expects the server to be running with the server.pem, ca.pem # and crl.pem provided in mongodb and the server tests as well as @@ -498,50 +519,64 @@ class TestSSL(unittest.TestCase): if not CERT_SSL: raise SkipTest("No mongod available over SSL with certs") - client = MongoClient(host, port, ssl=True, ssl_certfile=CLIENT_PEM) - if not version.at_least(client, (2, 5, 3, -1)): + if not version.at_least(ssl_auth_ctx.client, (2, 5, 3, -1)): raise SkipTest("MONGODB-X509 tests require MongoDB 2.5.3 or newer") - if not server_started_with_auth(client): + if not ssl_auth_ctx.auth_enabled: raise SkipTest('Authentication is not enabled on server') + + # This does two things: + # 1. Ensures we test against both client classes. + # 2. Ensures the tests pass regardless of what replica + # set member became primary before the tests run. + ismaster = ssl_auth_ctx.client.admin.command('ismaster') + if 'setName' in ismaster: + get_client = lambda hst: MongoReplicaSetClient( + hst, + ssl=True, + ssl_certfile=CLIENT_PEM, + replicaSet=ismaster['setName']) + else: + get_client = lambda hst: MongoClient( + hst, + ssl=True, + ssl_certfile=CLIENT_PEM) + + client = get_client(pair) + client.admin.authenticate(db_user, db_pwd) # Give admin all necessary privileges. client['$external'].add_user(MONGODB_X509_USERNAME, roles=[ {'role': 'readWriteAnyDatabase', 'db': 'admin'}, {'role': 'userAdminAnyDatabase', 'db': 'admin'}]) + client.admin.logout() coll = client.pymongo_test.test self.assertRaises(OperationFailure, coll.count) self.assertTrue(client.admin.authenticate(MONGODB_X509_USERNAME, mechanism='MONGODB-X509')) self.assertTrue(coll.remove()) + client.admin.logout() + uri = ('mongodb://%s@%s:%d/?authMechanism=' 'MONGODB-X509' % (quote_plus(MONGODB_X509_USERNAME), host, port)) # SSL options aren't supported in the URI... - self.assertTrue(MongoClient(uri, ssl=True, ssl_certfile=CLIENT_PEM)) + self.assertTrue(get_client(uri)) # Should require a username uri = ('mongodb://%s:%d/?authMechanism=MONGODB-X509' % (host, port)) - client_bad = MongoClient(uri, ssl=True, ssl_certfile=CLIENT_PEM) + client_bad = get_client(uri) self.assertRaises(OperationFailure, client_bad.pymongo_test.test.remove) # Auth should fail if username and certificate do not match uri = ('mongodb://%s@%s:%d/?authMechanism=' 'MONGODB-X509' % (quote_plus("not the username"), host, port)) - self.assertRaises(ConfigurationError, MongoClient, uri, - ssl=True, ssl_certfile=CLIENT_PEM) + self.assertRaises(ConfigurationError, get_client, uri) + self.assertRaises(OperationFailure, client.admin.authenticate, "not the username", mechanism="MONGODB-X509") - # Invalid certificate (using CA certificate as client certificate) - uri = ('mongodb://%s@%s:%d/?authMechanism=' - 'MONGODB-X509' % (quote_plus(MONGODB_X509_USERNAME), host, port)) - self.assertRaises(ConnectionFailure, MongoClient, uri, - ssl=True, ssl_certfile=CA_PEM) - self.assertRaises(ConnectionFailure, MongoClient, pair, - ssl=True, ssl_certfile=CA_PEM) - # Cleanup + client.admin.authenticate(db_user, db_pwd) remove_all_users(client['$external']) - client['$external'].logout() if __name__ == "__main__": unittest.main()