diff --git a/pymongo/common.py b/pymongo/common.py index 23b9a7884..4d1c6b6d5 100644 --- a/pymongo/common.py +++ b/pymongo/common.py @@ -124,11 +124,10 @@ def validate_cert_reqs(option, value): if value is None: return value if HAS_SSL: + if isinstance(value, basestring) and hasattr(ssl, value): + value = getattr(ssl, value) if value in (ssl.CERT_NONE, ssl.CERT_OPTIONAL, ssl.CERT_REQUIRED): return value - elif isinstance(value, basestring) and hasattr(ssl, value) and \ - getattr(ssl, value) in (ssl.CERT_NONE, ssl.CERT_OPTIONAL, ssl.CERT_REQUIRED): - return getattr(ssl, value) raise ConfigurationError("The value of %s must be one of: " "`ssl.CERT_NONE`, `ssl.CERT_OPTIONAL` or " "`ssl.CERT_REQUIRED" % (option,)) diff --git a/test/__init__.py b/test/__init__.py index 24e42eb2a..e9e297f5a 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -20,7 +20,7 @@ import warnings import pymongo from nose.plugins.skip import SkipTest -from pymongo.errors import OperationFailure +from pymongo.errors import ConnectionFailure, OperationFailure # hostnames retrieved by MongoReplicaSetClient from isMaster will be of unicode # type in Python 2, so ensure these hostnames are unicodes, too. It makes tests @@ -42,30 +42,34 @@ db_pwd = unicode(os.environ.get("DB_PASSWORD", "password")) class AuthContext(object): def __init__(self): - self.client = pymongo.MongoClient(host, port) self.auth_enabled = False self.restricted_localhost = False try: - command_line = self.client.admin.command('getCmdLineOpts') - if self._server_started_with_auth(command_line): - self.auth_enabled = True - except OperationFailure, e: - msg = e.details.get('errmsg', '') - if e.code == 13 or 'unauthorized' in msg or 'login' in msg: - self.auth_enabled = True - self.restricted_localhost = True - else: - raise - # See if the user has already been set up. - try: - self.client.admin.authenticate(db_user, db_pwd) - self.user_provided = True - except OperationFailure, e: - msg = e.details.get('errmsg', '') - if e.code == 18 or 'auth fails' in msg: - self.user_provided = False - else: - raise + self.client = pymongo.MongoClient(host, port) + except ConnectionFailure: + self.client = None + else: + try: + command_line = self.client.admin.command('getCmdLineOpts') + if self._server_started_with_auth(command_line): + self.auth_enabled = True + except OperationFailure, e: + msg = e.details.get('errmsg', '') + if e.code == 13 or 'unauthorized' in msg or 'login' in msg: + self.auth_enabled = True + self.restricted_localhost = True + else: + raise + # See if the user has already been set up. + try: + self.client.admin.authenticate(db_user, db_pwd) + self.user_provided = True + except OperationFailure, e: + msg = e.details.get('errmsg', '') + if e.code == 18 or 'auth fails' in msg: + self.user_provided = False + else: + raise def _server_started_with_auth(self, command_line): # MongoDB >= 2.0 diff --git a/test/certificates/crl.pem b/test/certificates/crl.pem new file mode 100644 index 000000000..dce0a0fb3 --- /dev/null +++ b/test/certificates/crl.pem @@ -0,0 +1,10 @@ +-----BEGIN X509 CRL----- +MIIBazCB1QIBATANBgkqhkiG9w0BAQUFADCBkjELMAkGA1UEBhMCVVMxETAPBgNV +BAgMCE5ldyBZb3JrMRYwFAYDVQQHDA1OZXcgWW9yayBDaXR5MQ4wDAYDVQQKDAUx +MEdlbjEPMA0GA1UECwwGS2VybmVsMRowGAYDVQQDDBFNeSBDZXJ0IEF1dGhvcml0 +eTEbMBkGCSqGSIb3DQEJARYMcm9vdEBsYXphcnVzFw0xMjEyMTIxODQ3NDFaFw00 +MDA0MjgxODQ3NDFaoA4wDDAKBgNVHRQEAwIBCzANBgkqhkiG9w0BAQUFAAOBgQAu +PlPDGei2q6kdkoHe8vmDuts7Hm/o9LFbBmn0XUcfHisCJCPsJTyGCsgnfIiBcXJY +1LMKsQFnYGv28rE2ZPpFg2qNxL+6qUEzCvqaHLX9q1V0F+f8hHDxucNYu52oo/h0 +uNZxB1KPFI2PReG5d3oUYqJ2+EctKkrGtxSPzbN0gg== +-----END X509 CRL----- diff --git a/test/certificates/server.pem b/test/certificates/server.pem new file mode 100644 index 000000000..e5980d485 --- /dev/null +++ b/test/certificates/server.pem @@ -0,0 +1,34 @@ +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAK53miP9GczBWXnq +NxHwQkgVqsDuesjwJbWilMK4gf3fjnf2PN3qDpnGbZbPD0ij8975pIKtSPoDycFm +A8Mogip0yU2Lv2lL56CWthSBftOFDL2CWIsmuuURFXZPiVLtLytfI9oLASZFlywW +Cs83qEDTvdW8VoVhVsxV1JFDnpXLAgMBAAECgYBoGBgxrMt97UazhNkCrPT/CV5t +6lv8E7yMGMrlOyzkCkR4ssQyK3o2qbutJTGbR6czvIM5LKbD9Qqlh3ZrNHokWmTR +VQQpJxt8HwP5boQvwRHg9+KSGr4JvRko1qxFs9C7Bzjt4r9VxdjhwZPdy0McGI/z +yPXyQHjqBayrHV1EwQJBANorfCKeIxLhH3LAeUZuRS8ACldJ2N1kL6Ov43/v+0S/ +OprQeBTODuTds3sv7FCT1aYDTOe6JLNOwN2i4YVOMBsCQQDMuCozrwqftD17D06P +9+lRXUekY5kFBs5j28Xnl8t8jnuxsXtQUTru660LD0QrmDNSauhpEmlpJknicnGt +hmwRAkEA12MI6bBPlir0/jgxQqxI1w7mJqj8Vg27zpEuO7dzzLoyJHddpcSNBbwu +npaAakiZK42klj26T9+XHvjYRuAbMwJBAJ5WnwWEkGH/pUHGEAyYQdSVojDKe/MA +Vae0tzguFswK5C8GyArSGRPsItYYA7D4MlG/sGx8Oh2C6MiFndkJzBECQDcP1y4r +Qsek151t1zArLKH4gG5dQAeZ0Lc2VeC4nLMUqVwrHcZDdd1RzLlSaH3j1MekFVfT +6v6rrcNLEVbeuk4= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIC7jCCAlegAwIBAgIBCjANBgkqhkiG9w0BAQUFADCBkjELMAkGA1UEBhMCVVMx +ETAPBgNVBAgMCE5ldyBZb3JrMRYwFAYDVQQHDA1OZXcgWW9yayBDaXR5MQ4wDAYD +VQQKDAUxMEdlbjEPMA0GA1UECwwGS2VybmVsMRowGAYDVQQDDBFNeSBDZXJ0IEF1 +dGhvcml0eTEbMBkGCSqGSIb3DQEJARYMcm9vdEBsYXphcnVzMB4XDTEzMTIwNTEz +MjU0MFoXDTQxMDQyMTEzMjU0MFowajELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5l +dyBZb3JrMRYwFAYDVQQHDA1OZXcgWW9yayBDaXR5MQ4wDAYDVQQKDAUxMEdlbjEP +MA0GA1UECwwGS2VybmVsMQ8wDQYDVQQDDAZzZXJ2ZXIwgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBAK53miP9GczBWXnqNxHwQkgVqsDuesjwJbWilMK4gf3fjnf2 +PN3qDpnGbZbPD0ij8975pIKtSPoDycFmA8Mogip0yU2Lv2lL56CWthSBftOFDL2C +WIsmuuURFXZPiVLtLytfI9oLASZFlywWCs83qEDTvdW8VoVhVsxV1JFDnpXLAgMB +AAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJh +dGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQgCkKiZhUV9/Zo7RwYYwm2cNK6tzAf +BgNVHSMEGDAWgBQHQRk6n37FtyJOt7zV3+T8CbhkFjANBgkqhkiG9w0BAQUFAAOB +gQCbsfr+Q4pty4Fy38lSxoCgnbB4pX6+Ex3xyw5zxDYR3xUlb/uHBiNZ1dBrXBxU +ekU8dEvf+hx4iRDSW/C5N6BGnBBhCHcrPabo2bEEWKVsbUC3xchTB5rNGkvnMt9t +G9ol7vanuzjL3S8/2PB33OshkBH570CxqqPflQbdjwt9dg== +-----END CERTIFICATE----- diff --git a/test/test_common.py b/test/test_common.py index a765ac39f..e8935d85b 100644 --- a/test/test_common.py +++ b/test/test_common.py @@ -27,13 +27,11 @@ from bson.code import Code from bson.objectid import ObjectId from bson.son import SON from pymongo.connection import Connection -from pymongo import common from pymongo.mongo_client import MongoClient from pymongo.mongo_replica_set_client import MongoReplicaSetClient from pymongo.errors import ConfigurationError, OperationFailure from test import host, port, pair, version, skip_restricted_localhost from test.utils import catch_warnings, drop_collections -from ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED have_uuid = True try: @@ -522,20 +520,6 @@ class TestCommon(unittest.TestCase): finally: ctx.exit() - def test_validate_cert_reqs(self): - self.assertRaises(ConfigurationError, common.validate_cert_reqs, 'ssl_cert_reqs', 3) - self.assertRaises(ConfigurationError, common.validate_cert_reqs, 'ssl_cert_reqs', -1) - self.assertEqual(common.validate_cert_reqs('ssl_cert_reqs', None), None) - self.assertEqual(common.validate_cert_reqs('ssl_cert_reqs', CERT_NONE), CERT_NONE) - self.assertEqual(common.validate_cert_reqs('ssl_cert_reqs', CERT_OPTIONAL), CERT_OPTIONAL) - self.assertEqual(common.validate_cert_reqs('ssl_cert_reqs', CERT_REQUIRED), CERT_REQUIRED) - self.assertEqual(common.validate_cert_reqs('ssl_cert_reqs', 0), CERT_NONE) - self.assertEqual(common.validate_cert_reqs('ssl_cert_reqs', 1), CERT_OPTIONAL) - self.assertEqual(common.validate_cert_reqs('ssl_cert_reqs', 2), CERT_REQUIRED) - self.assertEqual(common.validate_cert_reqs('ssl_cert_reqs', 'CERT_NONE'), CERT_NONE) - self.assertEqual(common.validate_cert_reqs('ssl_cert_reqs', 'CERT_OPTIONAL'), CERT_OPTIONAL) - self.assertEqual(common.validate_cert_reqs('ssl_cert_reqs', 'CERT_REQUIRED'), CERT_REQUIRED) - if __name__ == "__main__": unittest.main() diff --git a/test/test_ssl.py b/test/test_ssl.py index d4aa56f8b..bfc4ed7cc 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -19,20 +19,20 @@ import socket import sys import unittest +sys.path[0:0] = [""] + try: from ssl import CertificateError except ImportError: # Backport. from pymongo.ssl_match_hostname import CertificateError -sys.path[0:0] = [""] - from urllib import quote_plus from nose.plugins.skip import SkipTest from pymongo import MongoClient, MongoReplicaSetClient -from pymongo.common import HAS_SSL +from pymongo.common import HAS_SSL, validate_cert_reqs from pymongo.errors import (ConfigurationError, ConnectionFailure, OperationFailure) @@ -52,9 +52,9 @@ MONGODB_X509_USERNAME = ( # To fully test this start a mongod instance (built with SSL support) like so: # mongod --dbpath /path/to/data/directory --sslOnNormalPorts \ -# --sslPEMKeyFile /path/to/mongo/jstests/libs/server.pem \ -# --sslCAFile /path/to/mongo/jstests/libs/ca.pem \ -# --sslCRLFile /path/to/mongo/jstests/libs/crl.pem \ +# --sslPEMKeyFile /path/to/pymongo/test/certificates/server.pem \ +# --sslCAFile /path/to/pymongo/test/certificates/ca.pem \ +# --sslCRLFile /path/to/pymongo/test/certificates/crl.pem \ # --sslWeakCertificateValidation # Also, make sure you have 'server' as an alias for localhost in /etc/hosts # @@ -85,17 +85,19 @@ if HAS_SSL: MongoClient(host, port, connectTimeoutMS=100, ssl=True) SIMPLE_SSL = True except ConnectionFailure: - # Is MongoDB configured with server.pem, ca.pem, and crl.pem from - # mongodb jstests/lib? - try: - MongoClient(host, port, connectTimeoutMS=100, ssl=True, - ssl_certfile=CLIENT_PEM) - CERT_SSL = True - except ConnectionFailure: - pass + pass - if CERT_SSL: - SERVER_IS_RESOLVABLE = is_server_resolvable() + # Is MongoDB configured with server.pem, ca.pem, and crl.pem from + # mongodb jstests/lib? + try: + MongoClient(host, port, connectTimeoutMS=100, ssl=True, + ssl_certfile=CLIENT_PEM) + CERT_SSL = True + except ConnectionFailure: + pass + + if CERT_SSL: + SERVER_IS_RESOLVABLE = is_server_resolvable() class TestClientSSL(unittest.TestCase): @@ -123,7 +125,6 @@ class TestClientSSL(unittest.TestCase): ssl_certfile=CLIENT_PEM) def test_config_ssl(self): - """Tests various ssl configurations""" self.assertRaises(ConfigurationError, MongoClient, ssl='foo') self.assertRaises(ConfigurationError, MongoClient, @@ -199,6 +200,39 @@ class TestClientSSL(unittest.TestCase): ssl_keyfile=CLIENT_PEM, ssl_certfile=CLIENT_PEM) + self.assertRaises( + ConfigurationError, validate_cert_reqs, 'ssl_cert_reqs', 3) + self.assertRaises( + ConfigurationError, validate_cert_reqs, 'ssl_cert_reqs', -1) + self.assertRaises( + ConfigurationError, validate_cert_reqs, 'ssl_cert_reqs', 'foo') + self.assertEqual( + validate_cert_reqs('ssl_cert_reqs', None), None) + self.assertEqual( + validate_cert_reqs('ssl_cert_reqs', ssl.CERT_NONE), + ssl.CERT_NONE) + self.assertEqual( + validate_cert_reqs('ssl_cert_reqs', ssl.CERT_OPTIONAL), + ssl.CERT_OPTIONAL) + self.assertEqual( + validate_cert_reqs('ssl_cert_reqs', ssl.CERT_REQUIRED), + ssl.CERT_REQUIRED) + self.assertEqual( + validate_cert_reqs('ssl_cert_reqs', 0), ssl.CERT_NONE) + self.assertEqual( + validate_cert_reqs('ssl_cert_reqs', 1), ssl.CERT_OPTIONAL) + self.assertEqual( + validate_cert_reqs('ssl_cert_reqs', 2), ssl.CERT_REQUIRED) + self.assertEqual( + validate_cert_reqs('ssl_cert_reqs', 'CERT_NONE'), + ssl.CERT_NONE) + self.assertEqual( + validate_cert_reqs('ssl_cert_reqs', 'CERT_OPTIONAL'), + ssl.CERT_OPTIONAL) + self.assertEqual( + validate_cert_reqs('ssl_cert_reqs', 'CERT_REQUIRED'), + ssl.CERT_REQUIRED) + class TestSSL(unittest.TestCase): @@ -234,9 +268,9 @@ class TestSSL(unittest.TestCase): # Expects the server to be running with the server.pem, ca.pem # and crl.pem provided in mongodb and the server tests eg: # - # --sslPEMKeyFile=jstests/libs/server.pem - # --sslCAFile=jstests/libs/ca.pem - # --sslCRLFile=jstests/libs/crl.pem + # --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem + # --sslCAFile=/path/to/pymongo/test/certificates/ca.pem + # --sslCRLFile=/path/to/pymongo/test/certificates/crl.pem # # Also requires an /etc/hosts entry where "server" is resolvable if not CERT_SSL: @@ -260,9 +294,9 @@ class TestSSL(unittest.TestCase): # Expects the server to be running with the server.pem, ca.pem # and crl.pem provided in mongodb and the server tests eg: # - # --sslPEMKeyFile=jstests/libs/server.pem - # --sslCAFile=jstests/libs/ca.pem - # --sslCRLFile=jstests/libs/crl.pem + # --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem + # --sslCAFile=/path/to/pymongo/test/certificates/ca.pem + # --sslCRLFile=/path/to/pymongo/test/certificates/crl.pem # # Also requires an /etc/hosts entry where "server" is resolvable if not CERT_SSL: @@ -286,9 +320,9 @@ class TestSSL(unittest.TestCase): # Expects the server to be running with the server.pem, ca.pem # and crl.pem provided in mongodb and the server tests eg: # - # --sslPEMKeyFile=jstests/libs/server.pem - # --sslCAFile=jstests/libs/ca.pem - # --sslCRLFile=jstests/libs/crl.pem + # --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem + # --sslCAFile=/path/to/pymongo/test/certificates/ca.pem + # --sslCRLFile=/path/to/pymongo/test/certificates/crl.pem # # Also requires an /etc/hosts entry where "server" is resolvable if not CERT_SSL: @@ -323,13 +357,39 @@ class TestSSL(unittest.TestCase): self.assertTrue(db.test.find_one()['ssl']) client.drop_database('pymongo_ssl_test') + def test_cert_ssl_uri_support(self): + # Expects the server to be running with the server.pem, ca.pem + # and crl.pem provided in mongodb and the server tests eg: + # + # --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem + # --sslCAFile=/path/to/pymongo/test/certificates/ca.pem + # --sslCRLFile=/path/to/pymongo/test/certificates/crl.pem + # + # Also requires an /etc/hosts entry where "server" is resolvable + if not CERT_SSL: + raise SkipTest("No mongod available over SSL with certs") + + if not SERVER_IS_RESOLVABLE: + raise SkipTest("No hosts entry for 'server'. Cannot validate " + "hostname in the certificate") + + uri_fmt = ("mongodb://server/?ssl=true&ssl_certfile=%s&ssl_cert_reqs" + "=%s&ssl_ca_certs=%s") + client = MongoClient(uri_fmt % (CLIENT_PEM, 'CERT_REQUIRED', CA_PEM)) + + db = client.pymongo_ssl_test + db.test.drop() + db.test.insert({'ssl': True}) + self.assertTrue(db.test.find_one()['ssl']) + client.drop_database('pymongo_ssl_test') + def test_cert_ssl_validation_optional(self): # Expects the server to be running with the server.pem, ca.pem # and crl.pem provided in mongodb and the server tests eg: # - # --sslPEMKeyFile=jstests/libs/server.pem - # --sslCAFile=jstests/libs/ca.pem - # --sslCRLFile=jstests/libs/crl.pem + # --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem + # --sslCAFile=/path/to/pymongo/test/certificates/ca.pem + # --sslCRLFile=/path/to/pymongo/test/certificates/crl.pem # # Also requires an /etc/hosts entry where "server" is resolvable if not CERT_SSL: @@ -369,9 +429,9 @@ class TestSSL(unittest.TestCase): # Expects the server to be running with the server.pem, ca.pem # and crl.pem provided in mongodb and the server tests eg: # - # --sslPEMKeyFile=jstests/libs/server.pem - # --sslCAFile=jstests/libs/ca.pem - # --sslCRLFile=jstests/libs/crl.pem + # --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem + # --sslCAFile=/path/to/pymongo/test/certificates/ca.pem + # --sslCRLFile=/path/to/pymongo/test/certificates/crl.pem if not CERT_SSL: raise SkipTest("No mongod available over SSL with certs") @@ -406,9 +466,9 @@ class TestSSL(unittest.TestCase): # and crl.pem provided in mongodb and the server tests as well as # --auth # - # --sslPEMKeyFile=jstests/libs/server.pem - # --sslCAFile=jstests/libs/ca.pem - # --sslCRLFile=jstests/libs/crl.pem + # --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem + # --sslCAFile=/path/to/pymongo/test/certificates/ca.pem + # --sslCRLFile=/path/to/pymongo/test/certificates/crl.pem # --auth if not CERT_SSL: raise SkipTest("No mongod available over SSL with certs")