PYTHON-842 - SSL URI config support.

This commit cleans up and builds on the work in commit
cc943f176c.
This commit is contained in:
Bernie Hackett 2015-04-14 10:59:00 -07:00
parent cc943f176c
commit eda1e771f6
6 changed files with 167 additions and 76 deletions

View File

@ -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,))

View File

@ -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

10
test/certificates/crl.pem Normal file
View File

@ -0,0 +1,10 @@
-----BEGIN X509 CRL-----
MIIBazCB1QIBATANBgkqhkiG9w0BAQUFADCBkjELMAkGA1UEBhMCVVMxETAPBgNV
BAgMCE5ldyBZb3JrMRYwFAYDVQQHDA1OZXcgWW9yayBDaXR5MQ4wDAYDVQQKDAUx
MEdlbjEPMA0GA1UECwwGS2VybmVsMRowGAYDVQQDDBFNeSBDZXJ0IEF1dGhvcml0
eTEbMBkGCSqGSIb3DQEJARYMcm9vdEBsYXphcnVzFw0xMjEyMTIxODQ3NDFaFw00
MDA0MjgxODQ3NDFaoA4wDDAKBgNVHRQEAwIBCzANBgkqhkiG9w0BAQUFAAOBgQAu
PlPDGei2q6kdkoHe8vmDuts7Hm/o9LFbBmn0XUcfHisCJCPsJTyGCsgnfIiBcXJY
1LMKsQFnYGv28rE2ZPpFg2qNxL+6qUEzCvqaHLX9q1V0F+f8hHDxucNYu52oo/h0
uNZxB1KPFI2PReG5d3oUYqJ2+EctKkrGtxSPzbN0gg==
-----END X509 CRL-----

View File

@ -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-----

View File

@ -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()

View File

@ -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")