SSL certificate verification PYTHON-466
This commit is contained in:
parent
41e2f8261a
commit
48046b2efd
@ -21,6 +21,12 @@ from pymongo.auth import MECHANISMS
|
||||
from pymongo.read_preferences import ReadPreference
|
||||
from pymongo.errors import ConfigurationError
|
||||
|
||||
HAS_SSL = True
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
HAS_SSL = False
|
||||
|
||||
|
||||
def raise_config_error(key, dummy):
|
||||
"""Raise ConfigurationError with the given key name."""
|
||||
@ -63,6 +69,33 @@ def validate_positive_integer(option, value):
|
||||
return val
|
||||
|
||||
|
||||
def validate_readable(option, value):
|
||||
"""Validates that 'value' is file-like and readable.
|
||||
"""
|
||||
# First make sure its a string py3.3 open(True, 'r') succeeds
|
||||
# Used in ssl cert checking due to poor ssl module error reporting
|
||||
value = validate_basestring(option, value)
|
||||
open(value, 'r').close()
|
||||
return value
|
||||
|
||||
|
||||
def validate_cert_reqs(option, value):
|
||||
"""Validate the cert reqs are valid. It must be None or one of the three
|
||||
values ``ssl.CERT_NONE``, ``ssl.CERT_OPTIONAL`` or ``ssl.CERT_REQUIRED``"""
|
||||
if value is None:
|
||||
return value
|
||||
if HAS_SSL:
|
||||
if value in (ssl.CERT_NONE, ssl.CERT_OPTIONAL, ssl.CERT_REQUIRED):
|
||||
return value
|
||||
raise ConfigurationError("The value of %s must be one of: "
|
||||
"`ssl.CERT_NONE`, `ssl.CERT_OPTIONAL` or "
|
||||
"`ssl.CERT_REQUIRED" % (option,))
|
||||
else:
|
||||
raise ConfigurationError("The value of %s is set but can't be "
|
||||
"validated. The ssl module is not available"
|
||||
% (option,))
|
||||
|
||||
|
||||
def validate_positive_integer_or_none(option, value):
|
||||
"""Validate that 'value' is a positive integer or None.
|
||||
"""
|
||||
@ -180,6 +213,10 @@ VALIDATORS = {
|
||||
'connecttimeoutms': validate_timeout_or_none,
|
||||
'sockettimeoutms': validate_timeout_or_none,
|
||||
'ssl': validate_boolean,
|
||||
'ssl_keyfile': validate_readable,
|
||||
'ssl_certfile': validate_readable,
|
||||
'ssl_cert_reqs': validate_cert_reqs,
|
||||
'ssl_ca_certs': validate_readable,
|
||||
'readpreference': validate_read_preference,
|
||||
'read_preference': validate_read_preference,
|
||||
'tag_sets': validate_tag_sets,
|
||||
|
||||
@ -135,8 +135,28 @@ class Connection(MongoClient):
|
||||
until :meth:`end_request()`
|
||||
- `slave_okay` or `slaveOk` (deprecated): Use `read_preference`
|
||||
instead.
|
||||
- `ssl_keyfile`: The private keyfile used to identify the local
|
||||
connection against mongod. If included with the ``certfile` then
|
||||
only the ``ssl_certfile`` is needed. Implies ``ssl=True``.
|
||||
- `ssl_certfile`: The certificate file used to identify the local
|
||||
connection against mongod. Implies ``ssl=True``.
|
||||
- `ssl_cert_reqs`: The parameter cert_reqs specifies whether a
|
||||
certificate is required from the other side of the connection,
|
||||
and whether it will be validated if provided. It must be one of the
|
||||
three values ``ssl.CERT_NONE`` (certificates ignored),
|
||||
``ssl.CERT_OPTIONAL`` (not required, but validated if provided), or
|
||||
``ssl.CERT_REQUIRED`` (required and validated). If the value of
|
||||
this parameter is not ``ssl.CERT_NONE``, then the ``ssl_ca_certs``
|
||||
parameter must point to a file of CA certificates.
|
||||
Implies ``ssl=True``.
|
||||
- `ssl_ca_certs`: The ca_certs file contains a set of concatenated
|
||||
"certification authority" certificates, which are used to validate
|
||||
certificates passed from the other end of the connection.
|
||||
Implies ``ssl=True``.
|
||||
|
||||
.. seealso:: :meth:`end_request`
|
||||
.. versionchanged:: 2.4.2+
|
||||
Added addtional ssl options
|
||||
.. versionchanged:: 2.3
|
||||
Added support for failover between mongos seed list members.
|
||||
.. versionchanged:: 2.2
|
||||
|
||||
@ -16,6 +16,11 @@
|
||||
|
||||
from bson.errors import *
|
||||
|
||||
try:
|
||||
from ssl import CertificateError
|
||||
except ImportError:
|
||||
from pymongo.ssl_match_hostname import CertificateError
|
||||
|
||||
|
||||
class PyMongoError(Exception):
|
||||
"""Base class for all PyMongo exceptions.
|
||||
@ -98,6 +103,7 @@ class InvalidURI(ConfigurationError):
|
||||
.. versionadded:: 1.5
|
||||
"""
|
||||
|
||||
|
||||
class UnsupportedOption(ConfigurationError):
|
||||
"""Exception for unsupported options.
|
||||
|
||||
|
||||
@ -26,6 +26,11 @@ from pymongo.errors import (AutoReconnect,
|
||||
OperationFailure,
|
||||
TimeoutError)
|
||||
|
||||
try:
|
||||
from ssl import match_hostname
|
||||
except ImportError:
|
||||
from pymongo.ssl_match_hostname import match_hostname
|
||||
|
||||
|
||||
def _index_list(key_or_list, direction=None):
|
||||
"""Helper to generate a list of (key, direction) pairs.
|
||||
@ -167,4 +172,3 @@ def shuffled(sequence):
|
||||
out = list(sequence)
|
||||
random.shuffle(out)
|
||||
return out
|
||||
|
||||
|
||||
@ -49,6 +49,7 @@ from pymongo import (auth,
|
||||
message,
|
||||
pool,
|
||||
uri_parser)
|
||||
from pymongo.common import HAS_SSL
|
||||
from pymongo.cursor_manager import CursorManager
|
||||
from pymongo.errors import (AutoReconnect,
|
||||
ConfigurationError,
|
||||
@ -60,6 +61,7 @@ from pymongo.errors import (AutoReconnect,
|
||||
|
||||
EMPTY = b("")
|
||||
|
||||
|
||||
def _partition_node(node):
|
||||
"""Split a host:port string returned from mongod/s into
|
||||
a (host, int(port)) pair needed for socket.connect().
|
||||
@ -163,11 +165,30 @@ class MongoClient(common.BaseObject):
|
||||
- `use_greenlets`: If ``True``, :meth:`start_request()` will ensure
|
||||
that the current greenlet uses the same socket for all
|
||||
operations until :meth:`end_request()`
|
||||
- `ssl_keyfile`: The private keyfile used to identify the local
|
||||
connection against mongod. If included with the ``certfile` then
|
||||
only the ``ssl_certfile`` is needed. Implies ``ssl=True``.
|
||||
- `ssl_certfile`: The certificate file used to identify the local
|
||||
connection against mongod. Implies ``ssl=True``.
|
||||
- `ssl_cert_reqs`: Specifies whether a certificate is required from
|
||||
the other side of the connection, and whether it will be validated
|
||||
if provided. It must be one of the three values ``ssl.CERT_NONE``
|
||||
(certificates ignored), ``ssl.CERT_OPTIONAL``
|
||||
(not required, but validated if provided), or ``ssl.CERT_REQUIRED``
|
||||
(required and validated). If the value of this parameter is not
|
||||
``ssl.CERT_NONE``, then the ``ssl_ca_certs`` parameter must point
|
||||
to a file of CA certificates. Implies ``ssl=True``.
|
||||
- `ssl_ca_certs`: The ca_certs file contains a set of concatenated
|
||||
"certification authority" certificates, which are used to validate
|
||||
certificates passed from the other end of the connection.
|
||||
Implies ``ssl=True``.
|
||||
|
||||
.. seealso:: :meth:`end_request`
|
||||
|
||||
.. mongodoc:: connections
|
||||
|
||||
.. versionchanged:: 2.4.2+
|
||||
Added addtional ssl options
|
||||
.. versionadded:: 2.4
|
||||
"""
|
||||
if host is None:
|
||||
@ -232,13 +253,35 @@ class MongoClient(common.BaseObject):
|
||||
|
||||
self.__net_timeout = options.get('sockettimeoutms')
|
||||
self.__conn_timeout = options.get('connecttimeoutms')
|
||||
self.__use_ssl = options.get('ssl', False)
|
||||
if self.__use_ssl and not pool.have_ssl:
|
||||
self.__use_ssl = options.get('ssl', None)
|
||||
self.__ssl_keyfile = options.get('ssl_keyfile', None)
|
||||
self.__ssl_certfile = options.get('ssl_certfile', None)
|
||||
self.__ssl_cert_reqs = options.get('ssl_cert_reqs', None)
|
||||
self.__ssl_ca_certs = options.get('ssl_ca_certs', None)
|
||||
|
||||
if self.__use_ssl and not HAS_SSL:
|
||||
raise ConfigurationError("The ssl module is not available. If you "
|
||||
"are using a python version previous to "
|
||||
"2.6 you must install the ssl package "
|
||||
"from PyPI.")
|
||||
|
||||
ssl_kwarg_keys = [k for k in kwargs.keys() if k.startswith('ssl_')]
|
||||
if self.__use_ssl == False and ssl_kwarg_keys:
|
||||
raise ConfigurationError("ssl has not been enabled but the "
|
||||
"following ssl parameters have been set: "
|
||||
"%s. Please set `ssl=True` or remove."
|
||||
% ', '.join(ssl_kwarg_keys))
|
||||
|
||||
if self.__ssl_cert_reqs and not self.__ssl_ca_certs:
|
||||
raise ConfigurationError("If `ssl_cert_reqs` is not "
|
||||
"`ssl.CERT_NONE` then you must "
|
||||
"include `ssl_ca_certs` to be able "
|
||||
"to validate the server.")
|
||||
|
||||
if ssl_kwarg_keys and self.__use_ssl is None:
|
||||
# ssl options imply ssl = True
|
||||
self.__use_ssl = True
|
||||
|
||||
self.__use_greenlets = options.get('use_greenlets', False)
|
||||
self.__pool = pool_class(
|
||||
None,
|
||||
@ -246,7 +289,11 @@ class MongoClient(common.BaseObject):
|
||||
self.__net_timeout,
|
||||
self.__conn_timeout,
|
||||
self.__use_ssl,
|
||||
use_greenlets=self.__use_greenlets)
|
||||
use_greenlets=self.__use_greenlets,
|
||||
ssl_keyfile=self.__ssl_keyfile,
|
||||
ssl_certfile=self.__ssl_certfile,
|
||||
ssl_cert_reqs=self.__ssl_cert_reqs,
|
||||
ssl_ca_certs=self.__ssl_ca_certs)
|
||||
|
||||
self.__document_class = document_class
|
||||
self.__tz_aware = common.validate_boolean('tz_aware', tz_aware)
|
||||
|
||||
@ -31,6 +31,7 @@ attribute-style access:
|
||||
Database(MongoReplicaSetClient([u'...', u'...']), u'test_database')
|
||||
"""
|
||||
|
||||
import atexit
|
||||
import datetime
|
||||
import socket
|
||||
import struct
|
||||
@ -38,7 +39,6 @@ import threading
|
||||
import time
|
||||
import warnings
|
||||
import weakref
|
||||
import atexit
|
||||
|
||||
from bson.py3compat import b
|
||||
from pymongo import (auth,
|
||||
@ -58,6 +58,9 @@ from pymongo.errors import (AutoReconnect,
|
||||
InvalidDocument,
|
||||
OperationFailure)
|
||||
|
||||
if common.HAS_SSL:
|
||||
import ssl
|
||||
|
||||
EMPTY = b("")
|
||||
MAX_BSON_SIZE = 4 * 1024 * 1024
|
||||
MAX_RETRY = 3
|
||||
@ -369,7 +372,26 @@ class MongoReplicaSetClient(common.BaseObject):
|
||||
precedence.
|
||||
- `port`: For compatibility with :class:`~mongo_client.MongoClient`.
|
||||
The default port number to use for hosts.
|
||||
- `ssl_keyfile`: The private keyfile used to identify the local
|
||||
connection against mongod. If included with the ``certfile` then
|
||||
only the ``ssl_certfile`` is needed. Implies ``ssl=True``.
|
||||
- `ssl_certfile`: The certificate file used to identify the local
|
||||
connection against mongod. Implies ``ssl=True``.
|
||||
- `ssl_cert_reqs`: Specifies whether a certificate is required from
|
||||
the other side of the connection, and whether it will be validated
|
||||
if provided. It must be one of the three values ``ssl.CERT_NONE``
|
||||
(certificates ignored), ``ssl.CERT_OPTIONAL``
|
||||
(not required, but validated if provided), or ``ssl.CERT_REQUIRED``
|
||||
(required and validated). If the value of this parameter is not
|
||||
``ssl.CERT_NONE``, then the ``ssl_ca_certs`` parameter must point
|
||||
to a file of CA certificates. Implies ``ssl=True``.
|
||||
- `ssl_ca_certs`: The ca_certs file contains a set of concatenated
|
||||
"certification authority" certificates, which are used to validate
|
||||
certificates passed from the other end of the connection.
|
||||
Implies ``ssl=True``.
|
||||
|
||||
.. versionchanged:: 2.4.2+
|
||||
Added addtional ssl options
|
||||
.. versionadded:: 2.4
|
||||
"""
|
||||
self.__opts = {}
|
||||
@ -437,16 +459,37 @@ class MongoReplicaSetClient(common.BaseObject):
|
||||
raise ConfigurationError("the replicaSet "
|
||||
"keyword parameter is required.")
|
||||
|
||||
|
||||
self.__net_timeout = self.__opts.get('sockettimeoutms')
|
||||
self.__conn_timeout = self.__opts.get('connecttimeoutms')
|
||||
self.__use_ssl = self.__opts.get('ssl', False)
|
||||
if self.__use_ssl and not pool.have_ssl:
|
||||
self.__use_ssl = self.__opts.get('ssl', None)
|
||||
self.__ssl_keyfile = self.__opts.get('ssl_keyfile', None)
|
||||
self.__ssl_certfile = self.__opts.get('ssl_certfile', None)
|
||||
self.__ssl_cert_reqs = self.__opts.get('ssl_cert_reqs', None)
|
||||
self.__ssl_ca_certs = self.__opts.get('ssl_ca_certs', None)
|
||||
|
||||
if self.__use_ssl and not common.HAS_SSL:
|
||||
raise ConfigurationError("The ssl module is not available. If you "
|
||||
"are using a python version previous to "
|
||||
"2.6 you must install the ssl package "
|
||||
"from PyPI.")
|
||||
|
||||
ssl_kwarg_keys = [k for k in kwargs.keys() if k.startswith('ssl_')]
|
||||
if self.__use_ssl == False and ssl_kwarg_keys:
|
||||
raise ConfigurationError("ssl has not been enabled but the "
|
||||
"following ssl parameters have been set: "
|
||||
"%s. Please set `ssl=True` or remove."
|
||||
% ', '.join(ssl_kwarg_keys))
|
||||
|
||||
if self.__ssl_cert_reqs and not self.__ssl_ca_certs:
|
||||
raise ConfigurationError("If `ssl_cert_reqs` is not "
|
||||
"`ssl.CERT_NONE` then you must "
|
||||
"include `ssl_ca_certs` to be able "
|
||||
"to validate the server.")
|
||||
|
||||
if ssl_kwarg_keys and self.__use_ssl is None:
|
||||
# ssl options imply ssl = True
|
||||
self.__use_ssl = True
|
||||
|
||||
super(MongoReplicaSetClient, self).__init__(**self.__opts)
|
||||
if self.slave_okay:
|
||||
warnings.warn("slave_okay is deprecated. Please "
|
||||
@ -717,8 +760,16 @@ class MongoReplicaSetClient(common.BaseObject):
|
||||
Returns (response, connection_pool, ping_time in seconds).
|
||||
"""
|
||||
connection_pool = self.pool_class(
|
||||
host, self.__max_pool_size, self.__net_timeout, self.__conn_timeout,
|
||||
self.__use_ssl, use_greenlets=self.__use_greenlets)
|
||||
host,
|
||||
self.__max_pool_size,
|
||||
self.__net_timeout,
|
||||
self.__conn_timeout,
|
||||
self.__use_ssl,
|
||||
use_greenlets=self.__use_greenlets,
|
||||
ssl_keyfile=self.__ssl_keyfile,
|
||||
ssl_certfile=self.__ssl_certfile,
|
||||
ssl_cert_reqs=self.__ssl_cert_reqs,
|
||||
ssl_ca_certs=self.__ssl_ca_certs)
|
||||
|
||||
if self.in_request():
|
||||
connection_pool.start_request()
|
||||
|
||||
@ -20,19 +20,13 @@ import threading
|
||||
import weakref
|
||||
|
||||
from pymongo import thread_util
|
||||
from pymongo.errors import ConnectionFailure, ConfigurationError
|
||||
from pymongo.common import HAS_SSL
|
||||
from pymongo.errors import (CertificateError, ConnectionFailure,
|
||||
ConfigurationError)
|
||||
from pymongo.helpers import match_hostname
|
||||
|
||||
|
||||
have_ssl = True
|
||||
try:
|
||||
if HAS_SSL:
|
||||
import ssl
|
||||
except ImportError:
|
||||
have_ssl = False
|
||||
|
||||
|
||||
NO_REQUEST = None
|
||||
NO_SOCKET_YET = -1
|
||||
|
||||
|
||||
if sys.platform.startswith('java'):
|
||||
from select import cpython_compatible_select as select
|
||||
@ -40,6 +34,10 @@ else:
|
||||
from select import select
|
||||
|
||||
|
||||
NO_REQUEST = None
|
||||
NO_SOCKET_YET = -1
|
||||
|
||||
|
||||
def _closed(sock):
|
||||
"""Return True if we know socket has been closed, False otherwise.
|
||||
"""
|
||||
@ -96,7 +94,8 @@ class SocketInfo(object):
|
||||
# http://bugs.jython.org/issue1057
|
||||
class Pool:
|
||||
def __init__(self, pair, max_size, net_timeout, conn_timeout, use_ssl,
|
||||
use_greenlets):
|
||||
use_greenlets, ssl_keyfile=None, ssl_certfile=None,
|
||||
ssl_cert_reqs=None, ssl_ca_certs=None):
|
||||
"""
|
||||
:Parameters:
|
||||
- `pair`: a (hostname, port) tuple
|
||||
@ -107,6 +106,23 @@ class Pool:
|
||||
- `use_greenlets`: bool, if True then start_request() assigns a
|
||||
socket to the current greenlet - otherwise it is assigned to the
|
||||
current thread
|
||||
- `ssl_keyfile`: The private keyfile used to identify the local
|
||||
connection against mongod. If included with the ``certfile` then
|
||||
only the ``ssl_certfile`` is needed. Implies ``ssl=True``.
|
||||
- `ssl_certfile`: The certificate file used to identify the local
|
||||
connection against mongod. Implies ``ssl=True``.
|
||||
- `ssl_cert_reqs`: Specifies whether a certificate is required from
|
||||
the other side of the connection, and whether it will be validated
|
||||
if provided. It must be one of the three values ``ssl.CERT_NONE``
|
||||
(certificates ignored), ``ssl.CERT_OPTIONAL``
|
||||
(not required, but validated if provided), or ``ssl.CERT_REQUIRED``
|
||||
(required and validated). If the value of this parameter is not
|
||||
``ssl.CERT_NONE``, then the ``ssl_ca_certs`` parameter must point
|
||||
to a file of CA certificates. Implies ``ssl=True``.
|
||||
- `ssl_ca_certs`: The ca_certs file contains a set of concatenated
|
||||
"certification authority" certificates, which are used to validate
|
||||
certificates passed from the other end of the connection.
|
||||
Implies ``ssl=True``.
|
||||
"""
|
||||
if use_greenlets and not thread_util.have_greenlet:
|
||||
raise ConfigurationError(
|
||||
@ -126,6 +142,14 @@ class Pool:
|
||||
self.net_timeout = net_timeout
|
||||
self.conn_timeout = conn_timeout
|
||||
self.use_ssl = use_ssl
|
||||
self.ssl_keyfile = ssl_keyfile
|
||||
self.ssl_certfile = ssl_certfile
|
||||
self.ssl_cert_reqs = ssl_cert_reqs
|
||||
self.ssl_ca_certs = ssl_ca_certs
|
||||
|
||||
if HAS_SSL and use_ssl and not ssl_cert_reqs:
|
||||
self.ssl_cert_reqs = ssl.CERT_NONE
|
||||
|
||||
self._ident = thread_util.create_ident(use_greenlets)
|
||||
|
||||
# Map self._ident.get() -> request socket
|
||||
@ -150,7 +174,8 @@ class Pool:
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
for sock_info in sockets: sock_info.close()
|
||||
for sock_info in sockets:
|
||||
sock_info.close()
|
||||
|
||||
def create_connection(self, pair):
|
||||
"""Connect to *pair* and return the socket object.
|
||||
@ -211,17 +236,28 @@ class Pool:
|
||||
return_socket() when you're done with it.
|
||||
"""
|
||||
sock = self.create_connection(pair)
|
||||
hostname = (pair or self.pair)[0]
|
||||
|
||||
if self.use_ssl:
|
||||
try:
|
||||
sock = ssl.wrap_socket(sock)
|
||||
sock = ssl.wrap_socket(sock,
|
||||
certfile=self.ssl_certfile,
|
||||
keyfile=self.ssl_keyfile,
|
||||
ca_certs=self.ssl_ca_certs,
|
||||
cert_reqs=self.ssl_cert_reqs)
|
||||
if self.ssl_cert_reqs:
|
||||
try:
|
||||
match_hostname(sock.getpeercert(), hostname)
|
||||
except CertificateError, e:
|
||||
raise ConnectionFailure("SSL certificate validation "
|
||||
"failed: %s" % e)
|
||||
except ssl.SSLError:
|
||||
sock.close()
|
||||
raise ConnectionFailure("SSL handshake failed. MongoDB may "
|
||||
"not be configured with SSL support.")
|
||||
|
||||
sock.settimeout(self.net_timeout)
|
||||
return SocketInfo(sock, self.pool_id, pair and pair[0] or self.pair[0])
|
||||
return SocketInfo(sock, self.pool_id, hostname)
|
||||
|
||||
def get_socket(self, pair=None):
|
||||
"""Get a socket from the pool.
|
||||
|
||||
@ -154,8 +154,26 @@ class ReplicaSetConnection(MongoReplicaSetClient):
|
||||
is no timeout. If both `network_timeout` and `socketTimeoutMS` are
|
||||
are specified `network_timeout` takes precedence, matching
|
||||
connection.Connection.
|
||||
- `ssl_keyfile`: The private keyfile used to identify the local
|
||||
connection against mongod. If included with the ``certfile` then
|
||||
only the ``ssl_certfile`` is needed. Implies ``ssl=True``.
|
||||
- `ssl_certfile`: The certificate file used to identify the local
|
||||
connection against mongod. Implies ``ssl=True``.
|
||||
- `ssl_cert_reqs`: Specifies whether a certificate is required from
|
||||
the other side of the connection, and whether it will be validated
|
||||
if provided. It must be one of the three values ``ssl.CERT_NONE``
|
||||
(certificates ignored), ``ssl.CERT_OPTIONAL``
|
||||
(not required, but validated if provided), or ``ssl.CERT_REQUIRED``
|
||||
(required and validated). If the value of this parameter is not
|
||||
``ssl.CERT_NONE``, then the ``ssl_ca_certs`` parameter must point
|
||||
to a file of CA certificates. Implies ``ssl=True``.
|
||||
- `ssl_ca_certs`: The ca_certs file contains a set of concatenated
|
||||
"certification authority" certificates, which are used to validate
|
||||
certificates passed from the other end of the connection.
|
||||
Implies ``ssl=True``.
|
||||
|
||||
|
||||
.. versionchanged:: 2.4.2+
|
||||
Added addtional ssl options
|
||||
.. versionchanged:: 2.3
|
||||
Added `tag_sets` and `secondary_acceptable_latency_ms` options.
|
||||
.. versionchanged:: 2.2
|
||||
|
||||
62
pymongo/ssl_match_hostname.py
Normal file
62
pymongo/ssl_match_hostname.py
Normal file
@ -0,0 +1,62 @@
|
||||
# Backport of the match_hostname logic introduced in python 3.2
|
||||
# http://svn.python.org/projects/python/branches/release32-maint/Lib/ssl.py
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class CertificateError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def _dnsname_to_pat(dn):
|
||||
pats = []
|
||||
for frag in dn.split(r'.'):
|
||||
if frag == '*':
|
||||
# When '*' is a fragment by itself, it matches a non-empty dotless
|
||||
# fragment.
|
||||
pats.append('[^.]+')
|
||||
else:
|
||||
# Otherwise, '*' matches any dotless fragment.
|
||||
frag = re.escape(frag)
|
||||
pats.append(frag.replace(r'\*', '[^.]*'))
|
||||
return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
|
||||
|
||||
|
||||
def match_hostname(cert, hostname):
|
||||
"""Verify that *cert* (in decoded format as returned by
|
||||
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules
|
||||
are mostly followed, but IP addresses are not accepted for *hostname*.
|
||||
|
||||
CertificateError is raised on failure. On success, the function
|
||||
returns nothing.
|
||||
"""
|
||||
if not cert:
|
||||
raise ValueError("empty or no certificate")
|
||||
dnsnames = []
|
||||
san = cert.get('subjectAltName', ())
|
||||
for key, value in san:
|
||||
if key == 'DNS':
|
||||
if _dnsname_to_pat(value).match(hostname):
|
||||
return
|
||||
dnsnames.append(value)
|
||||
if not san:
|
||||
# The subject is only checked when subjectAltName is empty
|
||||
for sub in cert.get('subject', ()):
|
||||
for key, value in sub:
|
||||
# XXX according to RFC 2818, the most specific Common Name
|
||||
# must be used.
|
||||
if key == 'commonName':
|
||||
if _dnsname_to_pat(value).match(hostname):
|
||||
return
|
||||
dnsnames.append(value)
|
||||
if len(dnsnames) > 1:
|
||||
raise CertificateError("hostname %r "
|
||||
"doesn't match either of %s"
|
||||
% (hostname, ', '.join(map(repr, dnsnames))))
|
||||
elif len(dnsnames) == 1:
|
||||
raise CertificateError("hostname %r "
|
||||
"doesn't match %r"
|
||||
% (hostname, dnsnames[0]))
|
||||
else:
|
||||
raise CertificateError("no appropriate commonName or "
|
||||
"subjectAltName fields were found")
|
||||
2
setup.py
2
setup.py
@ -259,6 +259,8 @@ if PY3:
|
||||
# are testing.
|
||||
# https://bitbucket.org/tarek/distribute/issue/233
|
||||
extra_opts["packages"].append("test")
|
||||
extra_opts['package_data'] = {"test": ["certificates/ca.pem",
|
||||
"certificates/client.pem"]}
|
||||
# Hack to make "python3.x setup.py nosetests" work in python 3
|
||||
# otherwise it won't run 2to3 before running the tests.
|
||||
if "nosetests" in sys.argv:
|
||||
|
||||
@ -14,12 +14,17 @@
|
||||
|
||||
"""Clean up databases after running `nosetests`.
|
||||
"""
|
||||
|
||||
from pymongo.errors import ConnectionFailure
|
||||
from test.test_client import get_client
|
||||
|
||||
|
||||
def teardown():
|
||||
c = get_client()
|
||||
try:
|
||||
c = get_client()
|
||||
except ConnectionFailure:
|
||||
# Tests where ssl=True can cause connection failures here.
|
||||
# Ignore and continue.
|
||||
return
|
||||
|
||||
c.drop_database("pymongo-pooling-tests")
|
||||
c.drop_database("pymongo_test")
|
||||
|
||||
18
test/certificates/ca.pem
Normal file
18
test/certificates/ca.pem
Normal file
@ -0,0 +1,18 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC9DCCAl2gAwIBAgIJAJeYVdtunBOmMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYD
|
||||
VQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENp
|
||||
dHkxDjAMBgNVBAoMBTEwR2VuMQ8wDQYDVQQLDAZLZXJuZWwxGjAYBgNVBAMMEU15
|
||||
IENlcnQgQXV0aG9yaXR5MRswGQYJKoZIhvcNAQkBFgxyb290QGxhemFydXMwHhcN
|
||||
MTIxMTI3MTkwMzM5WhcNMTMxMTI3MTkwMzM5WjCBkjELMAkGA1UEBhMCVVMxETAP
|
||||
BgNVBAgMCE5ldyBZb3JrMRYwFAYDVQQHDA1OZXcgWW9yayBDaXR5MQ4wDAYDVQQK
|
||||
DAUxMEdlbjEPMA0GA1UECwwGS2VybmVsMRowGAYDVQQDDBFNeSBDZXJ0IEF1dGhv
|
||||
cml0eTEbMBkGCSqGSIb3DQEJARYMcm9vdEBsYXphcnVzMIGfMA0GCSqGSIb3DQEB
|
||||
AQUAA4GNADCBiQKBgQDXHKZ5j5T969S5C/Gm6f2ah7gaik3zRzWm2ZoAcz/U6fBq
|
||||
rnha3bueXXBRWZ7d2HgN1a+JhjuYnffcdUSen9CFVxPiRCEgJmp2A8o90Kx5Bbcf
|
||||
7zHobDOGs1EF3PQ2RKgXEOUjKZ/LZDbGhClsIYCD4SdFhRMqUcxc2lQMsWEaNwID
|
||||
AQABo1AwTjAdBgNVHQ4EFgQUB0EZOp9+xbciTre81d/k/Am4ZBYwHwYDVR0jBBgw
|
||||
FoAUB0EZOp9+xbciTre81d/k/Am4ZBYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B
|
||||
AQUFAAOBgQB6aSQNTmD4gIQEcZiOXHJVpGOHeHBOxWteMFhcBpWvt0Cv8sqLZIVq
|
||||
x0eAC/tQFkAVEjT+T4S4UdtxgZ44RKCZPYI00qZsyz5bNoTE8kN/bmYNjyKMVFaG
|
||||
1tU+elCdOstzBLjY1aHG1oQzbyqgoiSIDpfzjlyK/tBpckFGCz6c6A==
|
||||
-----END CERTIFICATE-----
|
||||
32
test/certificates/client.pem
Normal file
32
test/certificates/client.pem
Normal file
@ -0,0 +1,32 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICkjCCAfsCCQCRlIP8LltShTANBgkqhkiG9w0BAQUFADCBkjELMAkGA1UEBhMC
|
||||
VVMxETAPBgNVBAgMCE5ldyBZb3JrMRYwFAYDVQQHDA1OZXcgWW9yayBDaXR5MQ4w
|
||||
DAYDVQQKDAUxMEdlbjEPMA0GA1UECwwGS2VybmVsMRowGAYDVQQDDBFNeSBDZXJ0
|
||||
IEF1dGhvcml0eTEbMBkGCSqGSIb3DQEJARYMcm9vdEBsYXphcnVzMB4XDTEyMTIx
|
||||
MDE4NTEzN1oXDTEzMTIxMDE4NTEzN1owgYcxCzAJBgNVBAYTAlVTMREwDwYDVQQI
|
||||
DAhOZXcgWW9yazEWMBQGA1UEBwwNTmV3IFlvcmsgQ2l0eTEOMAwGA1UECgwFMTBH
|
||||
ZW4xDzANBgNVBAsMBktlcm5lbDEPMA0GA1UEAwwGY2xpZW50MRswGQYJKoZIhvcN
|
||||
AQkBFgxyb290QGxhemFydXMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALX6
|
||||
DqSWRJBEJJRIRqG5X3cFHzse5jGIdV8fTqikaVitvuhs15z1njzfqBQZMJBCEvNb
|
||||
4eaenXJRMBDkEOcbfy6ah+ZLLqGFy7b6OxTROfx++3fTgsCAjBaIWvtGKNkwdcdM
|
||||
7PQ2jE5bL8vN/ufbH2sX451nVd+j6oAz0dTz7RvhAgMBAAEwDQYJKoZIhvcNAQEF
|
||||
BQADgYEAlOJmaiT3ZhUHfCgBQEjHUZ/mmMDbUrgq5ZfQSrW/r3c6u+k8s2LVqVut
|
||||
Qz3V8z2vSuIkaPZRgDESWhPisi7sihhbV6xm4YTQW4LDlrom41/SEQ5TLP+Vz4Uq
|
||||
avzrAdaQ6+zHbEB94TuWuE3vyWVIP0fT1PtzFjcOJUWzgjEIR7M=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALX6DqSWRJBEJJRI
|
||||
RqG5X3cFHzse5jGIdV8fTqikaVitvuhs15z1njzfqBQZMJBCEvNb4eaenXJRMBDk
|
||||
EOcbfy6ah+ZLLqGFy7b6OxTROfx++3fTgsCAjBaIWvtGKNkwdcdM7PQ2jE5bL8vN
|
||||
/ufbH2sX451nVd+j6oAz0dTz7RvhAgMBAAECgYEAmHRy+g5uSJLeNmBK1EiSIwtm
|
||||
e8hKP+s7scJvyrdbDpEZJG2zQWtA82zIynXECsdgSwOKQQRXkaNU6oG3a3bM19uY
|
||||
0CqFRb9EwOLIStp+CM5zLRGmUr73u/+JrBPUWWFJkJvINvTXt18CMnCmosTvygWB
|
||||
IBZqsuEXQ6JcejxzQ6UCQQDdVUNdE2JgHp1qrr5l8563dztcrfCxuVFtgsj6qnhd
|
||||
UrBAa388B9kn4yVAe2i55xFmtHsO9Bz3ViiDFO163SafAkEA0nq8PeZtcIlZ2c7+
|
||||
6/Vdw1uLE5APVG2H9VEZdaVvkwIIXo8WQfMwWo5MQyPjVyBhUGlDwnKa46AcuplJ
|
||||
2XMtfwJBAIDrMfKb4Ng13OEP6Yz+yvr4MxZ3plQOqlRMMn53HubUzB6pvpGbzKwE
|
||||
DWWyvDxUT/lvtKHwJJMYlz5KyUygVecCQHr50RBNmLW+2muDILiWlOD2lIyqh/pp
|
||||
QJ2Zc8mkDkuTTXaKHZQM1byjFXXI+yRFu/Xyeu+abFsAiqiPtXFCdVsCQHai+Ykv
|
||||
H3y0mUJmwBVP2fBE3GiTGlaadM0auZKu7/ad+yo7Hv8Kibacwibzrj9PjT3mFSSF
|
||||
vujX1oWOaxAMVbE=
|
||||
-----END PRIVATE KEY-----
|
||||
299
test/test_ssl.py
299
test/test_ssl.py
@ -14,20 +14,38 @@
|
||||
|
||||
"""Tests for SSL support."""
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import unittest
|
||||
sys.path[0:0] = [""]
|
||||
|
||||
from nose.plugins.skip import SkipTest
|
||||
|
||||
from pymongo import MongoClient, MongoReplicaSetClient
|
||||
from pymongo.common import HAS_SSL
|
||||
from pymongo.errors import ConfigurationError, ConnectionFailure
|
||||
|
||||
have_ssl = True
|
||||
try:
|
||||
if HAS_SSL:
|
||||
import ssl
|
||||
except ImportError:
|
||||
have_ssl = False
|
||||
|
||||
CERT_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
||||
'certificates')
|
||||
CLIENT_PEM = os.path.join(CERT_PATH, 'client.pem')
|
||||
CA_PEM = os.path.join(CERT_PATH, 'ca.pem')
|
||||
|
||||
|
||||
def has_server_host_entry():
|
||||
"""Returns True if 'server' is resolvable."""
|
||||
socket_timeout = socket.getdefaulttimeout()
|
||||
socket.setdefaulttimeout(1)
|
||||
try:
|
||||
socket.gethostbyname('server')
|
||||
has_server_host_entry = True
|
||||
except:
|
||||
has_server_host_entry = False
|
||||
socket.setdefaulttimeout(socket_timeout)
|
||||
return has_server_host_entry
|
||||
|
||||
|
||||
class TestSSL(unittest.TestCase):
|
||||
@ -37,19 +55,91 @@ class TestSSL(unittest.TestCase):
|
||||
raise SkipTest("Python 3.0.x has problems "
|
||||
"with SSL and socket timeouts.")
|
||||
|
||||
# MongoDB not configured for SSL?
|
||||
try:
|
||||
MongoClient(connectTimeoutMS=100, ssl=True)
|
||||
self.simple_ssl = True
|
||||
except ConnectionFailure:
|
||||
self.simple_ssl = False
|
||||
|
||||
# MongoDB configured with server.pem, ca.pem and crl.pem from
|
||||
# mongodb jstests/lib
|
||||
try:
|
||||
MongoClient(connectTimeoutMS=100, ssl=True,
|
||||
ssl_certfile=CLIENT_PEM)
|
||||
self.cert_ssl = True
|
||||
except ConnectionFailure:
|
||||
self.cert_ssl = False
|
||||
|
||||
def test_config_ssl(self):
|
||||
"""Tests various ssl configurations"""
|
||||
self.assertRaises(ConfigurationError, MongoClient, ssl='foo')
|
||||
self.assertRaises(ConfigurationError,
|
||||
MongoClient,
|
||||
ssl=False,
|
||||
ssl_certfile=CLIENT_PEM)
|
||||
self.assertRaises(TypeError, MongoClient, ssl=0)
|
||||
self.assertRaises(TypeError, MongoClient, ssl=5.5)
|
||||
self.assertRaises(TypeError, MongoClient, ssl=[])
|
||||
|
||||
self.assertRaises(ConfigurationError, MongoReplicaSetClient, ssl='foo')
|
||||
self.assertRaises(ConfigurationError,
|
||||
MongoReplicaSetClient,
|
||||
ssl=False,
|
||||
ssl_certfile=CLIENT_PEM)
|
||||
self.assertRaises(TypeError, MongoReplicaSetClient, ssl=0)
|
||||
self.assertRaises(TypeError, MongoReplicaSetClient, ssl=5.5)
|
||||
self.assertRaises(TypeError, MongoReplicaSetClient, ssl=[])
|
||||
|
||||
self.assertRaises(IOError, MongoClient, ssl_certfile="NoSuchFile")
|
||||
self.assertRaises(TypeError, MongoClient, ssl_certfile=True)
|
||||
self.assertRaises(TypeError, MongoClient, ssl_certfile=[])
|
||||
self.assertRaises(IOError, MongoClient, ssl_keyfile="NoSuchFile")
|
||||
self.assertRaises(TypeError, MongoClient, ssl_keyfile=True)
|
||||
self.assertRaises(TypeError, MongoClient, ssl_keyfile=[])
|
||||
|
||||
self.assertRaises(IOError,
|
||||
MongoReplicaSetClient,
|
||||
ssl_keyfile="NoSuchFile")
|
||||
self.assertRaises(IOError,
|
||||
MongoReplicaSetClient,
|
||||
ssl_certfile="NoSuchFile")
|
||||
self.assertRaises(TypeError, MongoReplicaSetClient, ssl_certfile=True)
|
||||
|
||||
# Test invalid combinations
|
||||
self.assertRaises(ConfigurationError,
|
||||
MongoClient,
|
||||
ssl=False,
|
||||
ssl_keyfile=CLIENT_PEM)
|
||||
self.assertRaises(ConfigurationError,
|
||||
MongoClient,
|
||||
ssl=False,
|
||||
ssl_certfile=CLIENT_PEM)
|
||||
self.assertRaises(ConfigurationError,
|
||||
MongoClient,
|
||||
ssl=False,
|
||||
ssl_keyfile=CLIENT_PEM,
|
||||
ssl_certfile=CLIENT_PEM)
|
||||
|
||||
self.assertRaises(ConfigurationError,
|
||||
MongoReplicaSetClient,
|
||||
ssl=False,
|
||||
ssl_keyfile=CLIENT_PEM)
|
||||
self.assertRaises(ConfigurationError,
|
||||
MongoReplicaSetClient,
|
||||
ssl=False,
|
||||
ssl_certfile=CLIENT_PEM)
|
||||
self.assertRaises(ConfigurationError,
|
||||
MongoReplicaSetClient,
|
||||
ssl=False,
|
||||
ssl_keyfile=CLIENT_PEM,
|
||||
ssl_certfile=CLIENT_PEM)
|
||||
|
||||
def test_no_ssl(self):
|
||||
if have_ssl:
|
||||
# Tests what happens when ssl is off on the server but you try to
|
||||
# connect to mongodb with ssl=True
|
||||
|
||||
if HAS_SSL:
|
||||
raise SkipTest(
|
||||
"The ssl module is available, can't test what happens "
|
||||
"without it."
|
||||
@ -60,26 +150,205 @@ class TestSSL(unittest.TestCase):
|
||||
self.assertRaises(ConfigurationError,
|
||||
MongoReplicaSetClient, ssl=True)
|
||||
|
||||
def test_simple_ops(self):
|
||||
if not have_ssl:
|
||||
def test_simple_ssl(self):
|
||||
# Expects the server to be running with ssl and with
|
||||
# no --sslPEMKeyFile or with --sslWeakCertificateValidation
|
||||
|
||||
if not HAS_SSL:
|
||||
raise SkipTest("The ssl module is not available.")
|
||||
|
||||
try:
|
||||
client = MongoClient(connectTimeoutMS=100, ssl=True)
|
||||
# MongoDB not configured for SSL?
|
||||
except ConnectionFailure:
|
||||
raise SkipTest("No mongod available over SSL")
|
||||
if not self.simple_ssl:
|
||||
raise SkipTest("No simple mongod available over SSL")
|
||||
|
||||
client = MongoClient(ssl=True)
|
||||
response = client.admin.command('ismaster')
|
||||
if 'setName' in response:
|
||||
client = MongoReplicaSetClient(replicaSet=response['setName'],
|
||||
w=len(response['hosts']),
|
||||
ssl=True)
|
||||
w=len(response['hosts']),
|
||||
ssl=True)
|
||||
|
||||
db = client.pymongo_ssl_test
|
||||
db.test.drop()
|
||||
self.assertTrue(db.test.insert({'ssl': True}))
|
||||
self.assertTrue(db.test.find_one()['ssl'])
|
||||
|
||||
def test_cert_ssl(self):
|
||||
# Expects the server to be running with the 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
|
||||
#
|
||||
# Also requires an /etc/hosts entry where "server" is resolvable
|
||||
|
||||
if not HAS_SSL:
|
||||
raise SkipTest("The ssl module is not available.")
|
||||
|
||||
if not self.cert_ssl:
|
||||
raise SkipTest("No mongod available over SSL with certs")
|
||||
|
||||
client = MongoClient(ssl=True, ssl_certfile=CLIENT_PEM)
|
||||
response = client.admin.command('ismaster')
|
||||
if 'setName' in response:
|
||||
client = MongoReplicaSetClient(replicaSet=response['setName'],
|
||||
w=len(response['hosts']),
|
||||
ssl=True, ssl_certfile=CLIENT_PEM)
|
||||
|
||||
db = client.pymongo_ssl_test
|
||||
db.test.drop()
|
||||
self.assertTrue(db.test.insert({'ssl': True}))
|
||||
self.assertTrue(db.test.find_one()['ssl'])
|
||||
|
||||
def test_cert_ssl_implicitly_set(self):
|
||||
# Expects the server to be running with the 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
|
||||
#
|
||||
# Also requires an /etc/hosts entry where "server" is resolvable
|
||||
|
||||
if not HAS_SSL:
|
||||
raise SkipTest("The ssl module is not available.")
|
||||
|
||||
if not self.cert_ssl:
|
||||
raise SkipTest("No mongod available over SSL with certs")
|
||||
|
||||
client = MongoClient(ssl_certfile=CLIENT_PEM)
|
||||
response = client.admin.command('ismaster')
|
||||
if 'setName' in response:
|
||||
client = MongoReplicaSetClient(replicaSet=response['setName'],
|
||||
w=len(response['hosts']),
|
||||
ssl_certfile=CLIENT_PEM)
|
||||
|
||||
db = client.pymongo_ssl_test
|
||||
db.test.drop()
|
||||
self.assertTrue(db.test.insert({'ssl': True}))
|
||||
self.assertTrue(db.test.find_one()['ssl'])
|
||||
|
||||
def test_cert_ssl_validation(self):
|
||||
# Expects the server to be running with the 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
|
||||
#
|
||||
# Also requires an /etc/hosts entry where "server" is resolvable
|
||||
|
||||
if not HAS_SSL:
|
||||
raise SkipTest("The ssl module is not available.")
|
||||
|
||||
if not self.cert_ssl:
|
||||
raise SkipTest("No mongod available over SSL with certs")
|
||||
|
||||
if not has_server_host_entry():
|
||||
raise SkipTest("No hosts entry for 'server' cannot validate "
|
||||
"hostname in the certificate")
|
||||
|
||||
client = MongoClient('server',
|
||||
ssl=True,
|
||||
ssl_certfile=CLIENT_PEM,
|
||||
ssl_cert_reqs=ssl.CERT_REQUIRED,
|
||||
ssl_ca_certs=CA_PEM)
|
||||
response = client.admin.command('ismaster')
|
||||
if 'setName' in response:
|
||||
client = MongoReplicaSetClient('server',
|
||||
replicaSet=response['setName'],
|
||||
w=len(response['hosts']),
|
||||
ssl=True,
|
||||
ssl_certfile=CLIENT_PEM,
|
||||
ssl_cert_reqs=ssl.CERT_REQUIRED,
|
||||
ssl_ca_certs=CA_PEM)
|
||||
|
||||
db = client.pymongo_ssl_test
|
||||
db.test.drop()
|
||||
self.assertTrue(db.test.insert({'ssl': True}))
|
||||
self.assertTrue(db.test.find_one()['ssl'])
|
||||
|
||||
def test_cert_ssl_validation_optional(self):
|
||||
# Expects the server to be running with the 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
|
||||
#
|
||||
# Also requires an /etc/hosts entry where "server" is resolvable
|
||||
|
||||
if not HAS_SSL:
|
||||
raise SkipTest("The ssl module is not available.")
|
||||
|
||||
if not self.cert_ssl:
|
||||
raise SkipTest("No mongod available over SSL with certs")
|
||||
|
||||
if not has_server_host_entry():
|
||||
raise SkipTest("No hosts entry for 'server' cannot validate "
|
||||
"hostname in the certificate")
|
||||
|
||||
client = MongoClient('server',
|
||||
ssl=True,
|
||||
ssl_certfile=CLIENT_PEM,
|
||||
ssl_cert_reqs=ssl.CERT_OPTIONAL,
|
||||
ssl_ca_certs=CA_PEM)
|
||||
response = client.admin.command('ismaster')
|
||||
if 'setName' in response:
|
||||
client = MongoReplicaSetClient('server',
|
||||
replicaSet=response['setName'],
|
||||
w=len(response['hosts']),
|
||||
ssl=True,
|
||||
ssl_certfile=CLIENT_PEM,
|
||||
ssl_cert_reqs=ssl.CERT_OPTIONAL,
|
||||
ssl_ca_certs=CA_PEM)
|
||||
|
||||
db = client.pymongo_ssl_test
|
||||
db.test.drop()
|
||||
self.assertTrue(db.test.insert({'ssl': True}))
|
||||
self.assertTrue(db.test.find_one()['ssl'])
|
||||
|
||||
def test_cert_ssl_validation_hostname_fail(self):
|
||||
# Expects the server to be running with the 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
|
||||
|
||||
if not HAS_SSL:
|
||||
raise SkipTest("The ssl module is not available.")
|
||||
|
||||
if not self.cert_ssl:
|
||||
raise SkipTest("No mongod available over SSL with certs")
|
||||
|
||||
client = MongoClient(ssl=True, ssl_certfile=CLIENT_PEM)
|
||||
response = client.admin.command('ismaster')
|
||||
singleServer = 'setName' not in response
|
||||
|
||||
if singleServer:
|
||||
try:
|
||||
MongoClient('localhost',
|
||||
ssl=True,
|
||||
ssl_certfile=CLIENT_PEM,
|
||||
ssl_cert_reqs=ssl.CERT_REQUIRED,
|
||||
ssl_ca_certs=CA_PEM)
|
||||
self.fail("Invalid hostname should have failed")
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
MongoReplicaSetClient('localhost',
|
||||
replicaSet=response['setName'],
|
||||
w=len(response['hosts']),
|
||||
ssl=True,
|
||||
ssl_certfile=CLIENT_PEM,
|
||||
ssl_cert_reqs=ssl.CERT_OPTIONAL,
|
||||
ssl_ca_certs=CA_PEM)
|
||||
self.fail("Invalid hostname should have failed")
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user