Skip to content

Commit b3118e0

Browse files
PYTHON-2162 Remove support for ssl* URI options (#706)
1 parent f9bfd11 commit b3118e0

15 files changed

+249
-350
lines changed

doc/migrate-to-pymongo4.rst

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,40 @@ Removed the ``socketKeepAlive`` keyword argument to
7979
keepalive. For more information see:
8080
https://docs.mongodb.com/manual/faq/diagnostics/#does-tcp-keepalive-time-affect-mongodb-deployments
8181

82+
Renamed URI options
83+
...................
84+
85+
Several deprecated URI options have been renamed to the standardized
86+
option names defined in the
87+
`URI options specification <https://github.com/mongodb/specifications/blob/master/source/uri-options/uri-options.rst>`_.
88+
The old option names and their renamed equivalents are summarized in the table
89+
below. Some renamed options have different semantics from the option being
90+
replaced as noted in the 'Migration Notes' column.
91+
92+
+--------------------+-------------------------------+-------------------------------------------------------------+
93+
| Old URI Option | Renamed URI Option | Migration Notes |
94+
+====================+===============================+=============================================================+
95+
| ssl_pem_passphrase | tlsCertificateKeyFilePassword | - |
96+
+--------------------+-------------------------------+-------------------------------------------------------------+
97+
| ssl_ca_certs | tlsCAFile | - |
98+
+--------------------+-------------------------------+-------------------------------------------------------------+
99+
| ssl_crlfile | tlsCRLFile | - |
100+
+--------------------+-------------------------------+-------------------------------------------------------------+
101+
| ssl_match_hostname | tlsAllowInvalidHostnames | ``ssl_match_hostname=True`` is equivalent to |
102+
| | | ``tlsAllowInvalidHostnames=False`` and vice-versa. |
103+
+--------------------+-------------------------------+-------------------------------------------------------------+
104+
| ssl_cert_reqs | tlsAllowInvalidCertificates | Instead of ``ssl.CERT_NONE``, ``ssl.CERT_OPTIONAL`` |
105+
| | | and ``ssl.CERT_REQUIRED``, ``tlsAllowInvalidCertificates`` |
106+
| | | expects a boolean value - ``True`` is equivalent to |
107+
| | | ``ssl.CERT_NONE``, while ``False`` is equivalent to |
108+
| | | ``ssl.CERT_REQUIRED``. |
109+
+--------------------+-------------------------------+-------------------------------------------------------------+
110+
| ssl_certfile | tlsCertificateKeyFile | Instead of using ``ssl_certfile`` and ``ssl_keyfile`` |
111+
| | | to specify the certificate and private key files, |
112+
+--------------------+ | ``tlsCertificateKeyFile`` expects a single file containing |
113+
| ssl_keyfile | | both the client certificate and the private key. |
114+
+--------------------+-------------------------------+-------------------------------------------------------------+
115+
82116
MongoClient.fsync is removed
83117
............................
84118

pymongo/client_options.py

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -69,43 +69,52 @@ def _parse_read_concern(options):
6969

7070
def _parse_ssl_options(options):
7171
"""Parse ssl options."""
72-
use_ssl = options.get('ssl')
73-
if use_ssl is not None:
74-
validate_boolean('ssl', use_ssl)
75-
76-
certfile = options.get('ssl_certfile')
77-
keyfile = options.get('ssl_keyfile')
78-
passphrase = options.get('ssl_pem_passphrase')
79-
ca_certs = options.get('ssl_ca_certs')
80-
cert_reqs = options.get('ssl_cert_reqs')
81-
match_hostname = options.get('ssl_match_hostname', True)
82-
crlfile = options.get('ssl_crlfile')
83-
check_ocsp_endpoint = options.get('ssl_check_ocsp_endpoint', True)
84-
85-
ssl_kwarg_keys = [k for k in options
86-
if k.startswith('ssl_') and options[k]]
87-
if use_ssl is False and ssl_kwarg_keys:
88-
raise ConfigurationError("ssl has not been enabled but the "
89-
"following ssl parameters have been set: "
90-
"%s. Please set `ssl=True` or remove."
91-
% ', '.join(ssl_kwarg_keys))
92-
93-
if ssl_kwarg_keys and use_ssl is None:
94-
# ssl options imply ssl = True
95-
use_ssl = True
96-
97-
if use_ssl is True:
72+
use_tls = options.get('tls')
73+
if use_tls is not None:
74+
validate_boolean('tls', use_tls)
75+
76+
certfile = options.get('tlscertificatekeyfile')
77+
passphrase = options.get('tlscertificatekeyfilepassword')
78+
ca_certs = options.get('tlscafile')
79+
crlfile = options.get('tlscrlfile')
80+
allow_invalid_certificates = options.get('tlsallowinvalidcertificates', False)
81+
allow_invalid_hostnames = options.get('tlsallowinvalidhostnames', False)
82+
disable_ocsp_endpoint_check = options.get('tlsdisableocspendpointcheck', False)
83+
84+
enabled_tls_opts = []
85+
for opt in ('tlscertificatekeyfile', 'tlscertificatekeyfilepassword',
86+
'tlscafile', 'tlscrlfile'):
87+
# Any non-null value of these options implies tls=True.
88+
if opt in options and options[opt]:
89+
enabled_tls_opts.append(opt)
90+
for opt in ('tlsallowinvalidcertificates', 'tlsallowinvalidhostnames',
91+
'tlsdisableocspendpointcheck'):
92+
# A value of False for these options implies tls=True.
93+
if opt in options and not options[opt]:
94+
enabled_tls_opts.append(opt)
95+
96+
if enabled_tls_opts:
97+
if use_tls is None:
98+
# Implicitly enable TLS when one of the tls* options is set.
99+
use_tls = True
100+
elif not use_tls:
101+
# Error since tls is explicitly disabled but a tls option is set.
102+
raise ConfigurationError("TLS has not been enabled but the "
103+
"following tls parameters have been set: "
104+
"%s. Please set `tls=True` or remove."
105+
% ', '.join(enabled_tls_opts))
106+
107+
if use_tls:
98108
ctx = get_ssl_context(
99109
certfile,
100-
keyfile,
101110
passphrase,
102111
ca_certs,
103-
cert_reqs,
112+
allow_invalid_certificates,
104113
crlfile,
105-
match_hostname,
106-
check_ocsp_endpoint)
107-
return ctx, match_hostname
108-
return None, match_hostname
114+
allow_invalid_hostnames,
115+
disable_ocsp_endpoint_check)
116+
return ctx, allow_invalid_hostnames
117+
return None, allow_invalid_hostnames
109118

110119

111120
def _parse_pool_options(options):
@@ -127,14 +136,14 @@ def _parse_pool_options(options):
127136
compression_settings = CompressionSettings(
128137
options.get('compressors', []),
129138
options.get('zlibcompressionlevel', -1))
130-
ssl_context, ssl_match_hostname = _parse_ssl_options(options)
139+
ssl_context, tls_allow_invalid_hostnames = _parse_ssl_options(options)
131140
load_balanced = options.get('loadbalanced')
132141
return PoolOptions(max_pool_size,
133142
min_pool_size,
134143
max_idle_time_seconds,
135144
connect_timeout, socket_timeout,
136145
wait_queue_timeout,
137-
ssl_context, ssl_match_hostname,
146+
ssl_context, tls_allow_invalid_hostnames,
138147
_EventListeners(event_listeners),
139148
appname,
140149
driver,

pymongo/common.py

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@
3535
from pymongo.monitoring import _validate_event_listeners
3636
from pymongo.read_concern import ReadConcern
3737
from pymongo.read_preferences import _MONGOS_MODES, _ServerMode
38-
from pymongo.ssl_support import (validate_cert_reqs,
39-
validate_allow_invalid_certs)
4038
from pymongo.write_concern import DEFAULT_WRITE_CONCERN, WriteConcern
4139

4240
ORDERED_TYPES = (SON, OrderedDict)
@@ -585,18 +583,11 @@ def validate_tzinfo(dummy, value):
585583

586584

587585
# Dictionary where keys are the names of public URI options, and values
588-
# are lists of aliases for that option. Aliases of option names are assumed
589-
# to have been deprecated.
586+
# are lists of aliases for that option.
590587
URI_OPTIONS_ALIAS_MAP = {
591588
'journal': ['j'],
592589
'wtimeoutms': ['wtimeout'],
593590
'tls': ['ssl'],
594-
'tlsallowinvalidcertificates': ['ssl_cert_reqs'],
595-
'tlsallowinvalidhostnames': ['ssl_match_hostname'],
596-
'tlscrlfile': ['ssl_crlfile'],
597-
'tlscafile': ['ssl_ca_certs'],
598-
'tlscertificatekeyfile': ['ssl_certfile'],
599-
'tlscertificatekeyfilepassword': ['ssl_pem_passphrase'],
600591
}
601592

602593
# Dictionary where keys are the names of URI options, and values
@@ -626,18 +617,13 @@ def validate_tzinfo(dummy, value):
626617
'loadbalanced': validate_boolean_or_string,
627618
'serverselectiontimeoutms': validate_timeout_or_zero,
628619
'sockettimeoutms': validate_timeout_or_none_or_zero,
629-
'ssl_keyfile': validate_readable,
630620
'tls': validate_boolean_or_string,
631-
'tlsallowinvalidcertificates': validate_allow_invalid_certs,
632-
'ssl_cert_reqs': validate_cert_reqs,
633-
# Normalized to ssl_match_hostname which is the logical inverse of tlsallowinvalidhostnames
634-
'tlsallowinvalidhostnames': lambda *x: not validate_boolean_or_string(*x),
635-
'ssl_match_hostname': validate_boolean_or_string,
621+
'tlsallowinvalidcertificates': validate_boolean_or_string,
622+
'tlsallowinvalidhostnames': validate_boolean_or_string,
636623
'tlscafile': validate_readable,
637624
'tlscertificatekeyfile': validate_readable,
638625
'tlscertificatekeyfilepassword': validate_string_or_none,
639-
# Normalized to ssl_check_ocsp_endpoint which is the logical inverse of tlsdisableocspendpointcheck
640-
'tlsdisableocspendpointcheck': lambda *x: not validate_boolean_or_string(*x),
626+
'tlsdisableocspendpointcheck': validate_boolean_or_string,
641627
'tlsinsecure': validate_boolean_or_string,
642628
'w': validate_non_negative_int_or_basestring,
643629
'wtimeoutms': validate_non_negative_integer,
@@ -682,14 +668,7 @@ def validate_tzinfo(dummy, value):
682668
INTERNAL_URI_OPTION_NAME_MAP = {
683669
'j': 'journal',
684670
'wtimeout': 'wtimeoutms',
685-
'tls': 'ssl',
686-
'tlsallowinvalidcertificates': 'ssl_cert_reqs',
687-
'tlsallowinvalidhostnames': 'ssl_match_hostname',
688-
'tlscrlfile': 'ssl_crlfile',
689-
'tlscafile': 'ssl_ca_certs',
690-
'tlscertificatekeyfile': 'ssl_certfile',
691-
'tlscertificatekeyfilepassword': 'ssl_pem_passphrase',
692-
'tlsdisableocspendpointcheck': 'ssl_check_ocsp_endpoint',
671+
'ssl': 'tls',
693672
}
694673

695674
# Map from deprecated URI option names to a tuple indicating the method of
@@ -704,19 +683,6 @@ def validate_tzinfo(dummy, value):
704683
# option and/or recommend remedial action.
705684
'j': ('renamed', 'journal'),
706685
'wtimeout': ('renamed', 'wTimeoutMS'),
707-
'ssl_cert_reqs': ('renamed', 'tlsAllowInvalidCertificates'),
708-
'ssl_match_hostname': ('renamed', 'tlsAllowInvalidHostnames'),
709-
'ssl_crlfile': ('renamed', 'tlsCRLFile'),
710-
'ssl_ca_certs': ('renamed', 'tlsCAFile'),
711-
'ssl_certfile': ('removed', (
712-
'Instead of using ssl_certfile to specify the certificate file, '
713-
'use tlsCertificateKeyFile to pass a single file containing both '
714-
'the client certificate and the private key')),
715-
'ssl_keyfile': ('removed', (
716-
'Instead of using ssl_keyfile to specify the private keyfile, '
717-
'use tlsCertificateKeyFile to pass a single file containing both '
718-
'the client certificate and the private key')),
719-
'ssl_pem_passphrase': ('renamed', 'tlsCertificateKeyFilePassword'),
720686
}
721687

722688
# Augment the option validator map with pymongo-specific option information.

pymongo/mongo_client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,9 @@ def __init__(
456456
python 2.7.9+ (pypy 2.5.1+) and 3.3+. Defaults to ``None``.
457457
- `tlsDisableOCSPEndpointCheck`: (boolean) If ``True``, disables
458458
certificate revocation status checking via the OCSP responder
459-
specified on the server certificate. Defaults to ``False``.
459+
specified on the server certificate.
460+
``tlsDisableOCSPEndpointCheck=False`` implies ``tls=True``.
461+
Defaults to ``False``.
460462
- `ssl`: (boolean) Alias for ``tls``.
461463
462464
| **Read Concern options:**

pymongo/ocsp_support.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def _get_issuer_cert(cert, chain, trusted_ca_certs):
8888

8989
# Depending on the server's TLS library, the peer's cert chain may not
9090
# include the self signed root CA. In this case we check the user
91-
# provided tlsCAFile (ssl_ca_certs) for the issuer.
91+
# provided tlsCAFile for the issuer.
9292
# Remove once we use the verified peer cert chain in PYTHON-2147.
9393
if trusted_ca_certs:
9494
for candidate in trusted_ca_certs:

pymongo/pool.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ class PoolOptions(object):
261261
'__max_idle_time_seconds',
262262
'__connect_timeout', '__socket_timeout',
263263
'__wait_queue_timeout',
264-
'__ssl_context', '__ssl_match_hostname',
264+
'__ssl_context', '__tls_allow_invalid_hostnames',
265265
'__event_listeners', '__appname', '__driver', '__metadata',
266266
'__compression_settings', '__max_connecting',
267267
'__pause_enabled', '__server_api', '__load_balanced')
@@ -271,7 +271,7 @@ def __init__(self, max_pool_size=MAX_POOL_SIZE,
271271
max_idle_time_seconds=MAX_IDLE_TIME_SEC, connect_timeout=None,
272272
socket_timeout=None, wait_queue_timeout=WAIT_QUEUE_TIMEOUT,
273273
ssl_context=None,
274-
ssl_match_hostname=True,
274+
tls_allow_invalid_hostnames=False,
275275
event_listeners=None, appname=None, driver=None,
276276
compression_settings=None, max_connecting=MAX_CONNECTING,
277277
pause_enabled=True, server_api=None, load_balanced=None):
@@ -282,7 +282,7 @@ def __init__(self, max_pool_size=MAX_POOL_SIZE,
282282
self.__socket_timeout = socket_timeout
283283
self.__wait_queue_timeout = wait_queue_timeout
284284
self.__ssl_context = ssl_context
285-
self.__ssl_match_hostname = ssl_match_hostname
285+
self.__tls_allow_invalid_hostnames = tls_allow_invalid_hostnames
286286
self.__event_listeners = event_listeners
287287
self.__appname = appname
288288
self.__driver = driver
@@ -400,10 +400,10 @@ def ssl_context(self):
400400
return self.__ssl_context
401401

402402
@property
403-
def ssl_match_hostname(self):
403+
def tls_allow_invalid_hostnames(self):
404404
"""Call ssl.match_hostname if cert_reqs is not ssl.CERT_NONE.
405405
"""
406-
return self.__ssl_match_hostname
406+
return self.__tls_allow_invalid_hostnames
407407

408408
@property
409409
def event_listeners(self):
@@ -1047,7 +1047,7 @@ def _configured_socket(address, options):
10471047
_raise_connection_failure(address, exc, "SSL handshake failed: ")
10481048
if (ssl_context.verify_mode and not
10491049
getattr(ssl_context, "check_hostname", False) and
1050-
options.ssl_match_hostname):
1050+
not options.tls_allow_invalid_hostnames):
10511051
try:
10521052
ssl.match_hostname(sock.getpeercert(), hostname=host)
10531053
except _CertificateError:

pymongo/ssl_support.py

Lines changed: 9 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -39,51 +39,26 @@
3939
HAS_SNI = _ssl.HAS_SNI
4040
IPADDR_SAFE = _ssl.IS_PYOPENSSL or sys.version_info[:2] >= (3, 7)
4141
SSLError = _ssl.SSLError
42-
def validate_cert_reqs(option, value):
43-
"""Validate the cert reqs are valid. It must be None or one of the
44-
three values ``ssl.CERT_NONE``, ``ssl.CERT_OPTIONAL`` or
45-
``ssl.CERT_REQUIRED``.
46-
"""
47-
if value is None:
48-
return value
49-
if isinstance(value, str) and hasattr(_stdlibssl, value):
50-
value = getattr(_stdlibssl, value)
51-
52-
if value in (CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED):
53-
return value
54-
raise ValueError("The value of %s must be one of: "
55-
"`ssl.CERT_NONE`, `ssl.CERT_OPTIONAL` or "
56-
"`ssl.CERT_REQUIRED`" % (option,))
57-
58-
def validate_allow_invalid_certs(option, value):
59-
"""Validate the option to allow invalid certificates is valid."""
60-
# Avoid circular import.
61-
from pymongo.common import validate_boolean_or_string
62-
boolean_cert_reqs = validate_boolean_or_string(option, value)
63-
if boolean_cert_reqs:
64-
return CERT_NONE
65-
return CERT_REQUIRED
6642

6743
def get_ssl_context(*args):
6844
"""Create and return an SSLContext object."""
6945
(certfile,
70-
keyfile,
7146
passphrase,
7247
ca_certs,
73-
cert_reqs,
48+
allow_invalid_certificates,
7449
crlfile,
75-
match_hostname,
76-
check_ocsp_endpoint) = args
77-
verify_mode = CERT_REQUIRED if cert_reqs is None else cert_reqs
50+
allow_invalid_hostnames,
51+
disable_ocsp_endpoint_check) = args
52+
verify_mode = CERT_NONE if allow_invalid_certificates else CERT_REQUIRED
7853
ctx = _ssl.SSLContext(_ssl.PROTOCOL_SSLv23)
7954
# SSLContext.check_hostname was added in CPython 3.4.
8055
if hasattr(ctx, "check_hostname"):
8156
if _ssl.CHECK_HOSTNAME_SAFE and verify_mode != CERT_NONE:
82-
ctx.check_hostname = match_hostname
57+
ctx.check_hostname = not allow_invalid_hostnames
8358
else:
8459
ctx.check_hostname = False
8560
if hasattr(ctx, "check_ocsp_endpoint"):
86-
ctx.check_ocsp_endpoint = check_ocsp_endpoint
61+
ctx.check_ocsp_endpoint = not disable_ocsp_endpoint_check
8762
if hasattr(ctx, "options"):
8863
# Explicitly disable SSLv2, SSLv3 and TLS compression. Note that
8964
# up to date versions of MongoDB 2.4 and above already disable
@@ -95,20 +70,20 @@ def get_ssl_context(*args):
9570
ctx.options |= _ssl.OP_NO_RENEGOTIATION
9671
if certfile is not None:
9772
try:
98-
ctx.load_cert_chain(certfile, keyfile, passphrase)
73+
ctx.load_cert_chain(certfile, None, passphrase)
9974
except _ssl.SSLError as exc:
10075
raise ConfigurationError(
10176
"Private key doesn't match certificate: %s" % (exc,))
10277
if crlfile is not None:
10378
if _ssl.IS_PYOPENSSL:
10479
raise ConfigurationError(
105-
"ssl_crlfile cannot be used with PyOpenSSL")
80+
"tlsCRLFile cannot be used with PyOpenSSL")
10681
# Match the server's behavior.
10782
ctx.verify_flags = getattr(_ssl, "VERIFY_CRL_CHECK_LEAF", 0)
10883
ctx.load_verify_locations(crlfile)
10984
if ca_certs is not None:
11085
ctx.load_verify_locations(ca_certs)
111-
elif cert_reqs != CERT_NONE:
86+
elif verify_mode != CERT_NONE:
11287
ctx.load_default_certs()
11388
ctx.verify_mode = verify_mode
11489
return ctx
@@ -117,15 +92,6 @@ class SSLError(Exception):
11792
pass
11893
HAS_SNI = False
11994
IPADDR_SAFE = False
120-
def validate_cert_reqs(option, dummy):
121-
"""No ssl module, raise ConfigurationError."""
122-
raise ConfigurationError("The value of %s is set but can't be "
123-
"validated. The ssl module is not available"
124-
% (option,))
125-
126-
def validate_allow_invalid_certs(option, dummy):
127-
"""No ssl module, raise ConfigurationError."""
128-
return validate_cert_reqs(option, dummy)
12995

13096
def get_ssl_context(*dummy):
13197
"""No ssl module, raise ConfigurationError."""

0 commit comments

Comments
 (0)