Skip to content

PYTHON-2162 Remove support for ssl* URI options #706

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fd50ca1
PYTHON-2702 Removed deprecated names for renamed URI options
prashantmital Aug 11, 2021
ae1358a
Remove ssl_ca_certs
prashantmital Aug 12, 2021
d285d3d
Remove ssl_crlfile
prashantmital Aug 12, 2021
6f24cdb
Remove ssl_match_hostname
prashantmital Aug 12, 2021
ebd1d4d
Remove ssl_cert_reqs
prashantmital Aug 12, 2021
93cbf38
Remove ssl_certfile and ssl_keyfile
prashantmital Aug 12, 2021
e236f8b
remove unused ssl option cruft
prashantmital Aug 12, 2021
deca485
Rename OCSP option to tls*
prashantmital Aug 12, 2021
1c4303b
Remove cruft
prashantmital Aug 12, 2021
b0f197b
Update migration guide
prashantmital Aug 12, 2021
f5fdc9a
Fix failing tests
prashantmital Aug 12, 2021
8449cc6
ensure semantics for security options dont change
prashantmital Aug 16, 2021
5847213
normalize before validation
prashantmital Aug 16, 2021
4f2f350
fix tls option handling
prashantmital Aug 17, 2021
566f027
fix handling tlsallowinvalidcertificates
prashantmital Aug 18, 2021
045dd73
Merge branch 'master' into PYTHON-2702/removed-deprecated-URI-options
prashantmital Aug 18, 2021
ec12ee6
fix typo
prashantmital Aug 18, 2021
7aec629
review changes
prashantmital Aug 18, 2021
ef822ce
review changes
prashantmital Aug 19, 2021
5eeffb1
fix test_normalize_options test
prashantmital Aug 19, 2021
8ed1aef
fix ssl tests
prashantmital Aug 19, 2021
b5924c4
get DNS test passing
prashantmital Aug 19, 2021
e8db6f0
review changes
prashantmital Aug 19, 2021
c393433
debug failures - 1
prashantmital Aug 19, 2021
1f2f6ba
debug failures - 2
prashantmital Aug 19, 2021
48daa20
debug failures - 3
prashantmital Aug 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions doc/migrate-to-pymongo4.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,40 @@ Removed the ``socketKeepAlive`` keyword argument to
keepalive. For more information see:
https://docs.mongodb.com/manual/faq/diagnostics/#does-tcp-keepalive-time-affect-mongodb-deployments

Renamed URI options
...................

Several deprecated URI options have been renamed to the standardized
option names defined in the
`URI options specification <https://github.com/mongodb/specifications/blob/master/source/uri-options/uri-options.rst>`_.
The old option names and their renamed equivalents are summarized in the table
below. Some renamed options have different semantics from the option being
replaced as noted in the 'Migration Notes' column.

+--------------------+-------------------------------+-------------------------------------------------------------+
| Old URI Option | Renamed URI Option | Migration Notes |
+====================+===============================+=============================================================+
| ssl_pem_passphrase | tlsCertificateKeyFilePassword | - |
+--------------------+-------------------------------+-------------------------------------------------------------+
| ssl_ca_certs | tlsCAFile | - |
+--------------------+-------------------------------+-------------------------------------------------------------+
| ssl_crlfile | tlsCRLFile | - |
+--------------------+-------------------------------+-------------------------------------------------------------+
| ssl_match_hostname | tlsAllowInvalidHostnames | ``ssl_match_hostname=True`` is equivalent to |
| | | ``tlsAllowInvalidHostnames=False`` and vice-versa. |
+--------------------+-------------------------------+-------------------------------------------------------------+
| ssl_cert_reqs | tlsAllowInvalidCertificates | Instead of ``ssl.CERT_NONE``, ``ssl.CERT_OPTIONAL`` |
| | | and ``ssl.CERT_REQUIRED``, ``tlsAllowInvalidCertificates`` |
| | | expects a boolean value - ``True`` is equivalent to |
| | | ``ssl.CERT_NONE``, while ``False`` is equivalent to |
| | | ``ssl.CERT_REQUIRED``. |
+--------------------+-------------------------------+-------------------------------------------------------------+
| ssl_certfile | tlsCertificateKeyFile | Instead of using ``ssl_certfile`` and ``ssl_keyfile`` |
| | | to specify the certificate and private key files, |
+--------------------+ | ``tlsCertificateKeyFile`` expects a single file containing |
| ssl_keyfile | | both the client certificate and the private key. |
+--------------------+-------------------------------+-------------------------------------------------------------+

MongoClient.fsync is removed
............................

Expand Down
77 changes: 43 additions & 34 deletions pymongo/client_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,43 +69,52 @@ def _parse_read_concern(options):

def _parse_ssl_options(options):
"""Parse ssl options."""
use_ssl = options.get('ssl')
if use_ssl is not None:
validate_boolean('ssl', use_ssl)

certfile = options.get('ssl_certfile')
keyfile = options.get('ssl_keyfile')
passphrase = options.get('ssl_pem_passphrase')
ca_certs = options.get('ssl_ca_certs')
cert_reqs = options.get('ssl_cert_reqs')
match_hostname = options.get('ssl_match_hostname', True)
crlfile = options.get('ssl_crlfile')
check_ocsp_endpoint = options.get('ssl_check_ocsp_endpoint', True)

ssl_kwarg_keys = [k for k in options
if k.startswith('ssl_') and options[k]]
if use_ssl is 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 ssl_kwarg_keys and use_ssl is None:
# ssl options imply ssl = True
use_ssl = True

if use_ssl is True:
use_tls = options.get('tls')
if use_tls is not None:
validate_boolean('tls', use_tls)

certfile = options.get('tlscertificatekeyfile')
passphrase = options.get('tlscertificatekeyfilepassword')
ca_certs = options.get('tlscafile')
crlfile = options.get('tlscrlfile')
allow_invalid_certificates = options.get('tlsallowinvalidcertificates', False)
allow_invalid_hostnames = options.get('tlsallowinvalidhostnames', False)
disable_ocsp_endpoint_check = options.get('tlsdisableocspendpointcheck', False)

enabled_tls_opts = []
for opt in ('tlscertificatekeyfile', 'tlscertificatekeyfilepassword',
'tlscafile', 'tlscrlfile'):
# Any non-null value of these options implies tls=True.
if opt in options and options[opt]:
enabled_tls_opts.append(opt)
for opt in ('tlsallowinvalidcertificates', 'tlsallowinvalidhostnames',
'tlsdisableocspendpointcheck'):
# A value of False for these options implies tls=True.
if opt in options and not options[opt]:
enabled_tls_opts.append(opt)

if enabled_tls_opts:
if use_tls is None:
# Implicitly enable TLS when one of the tls* options is set.
use_tls = True
elif not use_tls:
# Error since tls is explicitly disabled but a tls option is set.
raise ConfigurationError("TLS has not been enabled but the "
"following tls parameters have been set: "
"%s. Please set `tls=True` or remove."
% ', '.join(enabled_tls_opts))

if use_tls:
ctx = get_ssl_context(
certfile,
keyfile,
passphrase,
ca_certs,
cert_reqs,
allow_invalid_certificates,
crlfile,
match_hostname,
check_ocsp_endpoint)
return ctx, match_hostname
return None, match_hostname
allow_invalid_hostnames,
disable_ocsp_endpoint_check)
return ctx, allow_invalid_hostnames
return None, allow_invalid_hostnames


def _parse_pool_options(options):
Expand All @@ -127,14 +136,14 @@ def _parse_pool_options(options):
compression_settings = CompressionSettings(
options.get('compressors', []),
options.get('zlibcompressionlevel', -1))
ssl_context, ssl_match_hostname = _parse_ssl_options(options)
ssl_context, tls_allow_invalid_hostnames = _parse_ssl_options(options)
load_balanced = options.get('loadbalanced')
return PoolOptions(max_pool_size,
min_pool_size,
max_idle_time_seconds,
connect_timeout, socket_timeout,
wait_queue_timeout,
ssl_context, ssl_match_hostname,
ssl_context, tls_allow_invalid_hostnames,
_EventListeners(event_listeners),
appname,
driver,
Expand Down
44 changes: 5 additions & 39 deletions pymongo/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
from pymongo.monitoring import _validate_event_listeners
from pymongo.read_concern import ReadConcern
from pymongo.read_preferences import _MONGOS_MODES, _ServerMode
from pymongo.ssl_support import (validate_cert_reqs,
validate_allow_invalid_certs)
from pymongo.write_concern import DEFAULT_WRITE_CONCERN, WriteConcern

ORDERED_TYPES = (SON, OrderedDict)
Expand Down Expand Up @@ -585,18 +583,11 @@ def validate_tzinfo(dummy, value):


# Dictionary where keys are the names of public URI options, and values
# are lists of aliases for that option. Aliases of option names are assumed
# to have been deprecated.
# are lists of aliases for that option.
URI_OPTIONS_ALIAS_MAP = {
'journal': ['j'],
'wtimeoutms': ['wtimeout'],
'tls': ['ssl'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we remove ssl here (and INTERNAL_URI_OPTION_NAME_MAP) as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the decision was made to keep ssl indefinitely since we never deprecated it? Do you think we should deprecate it in the next 3.x release and remove?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note also that the follow up PR to this will be a better place remove this kind of bookkeeping code as that PR will get rid of all remaining options that have been deprecated in 3.x

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we should deprecate it in the next 3.x release and remove?

I think you're right that we should keep it. The URI-options spec still has ssl and doesn't say to remove it. It might be helpful to ask other drivers what their plan is.

'tlsallowinvalidcertificates': ['ssl_cert_reqs'],
'tlsallowinvalidhostnames': ['ssl_match_hostname'],
'tlscrlfile': ['ssl_crlfile'],
'tlscafile': ['ssl_ca_certs'],
'tlscertificatekeyfile': ['ssl_certfile'],
'tlscertificatekeyfilepassword': ['ssl_pem_passphrase'],
}

# Dictionary where keys are the names of URI options, and values
Expand Down Expand Up @@ -626,18 +617,13 @@ def validate_tzinfo(dummy, value):
'loadbalanced': validate_boolean_or_string,
'serverselectiontimeoutms': validate_timeout_or_zero,
'sockettimeoutms': validate_timeout_or_none_or_zero,
'ssl_keyfile': validate_readable,
'tls': validate_boolean_or_string,
'tlsallowinvalidcertificates': validate_allow_invalid_certs,
'ssl_cert_reqs': validate_cert_reqs,
# Normalized to ssl_match_hostname which is the logical inverse of tlsallowinvalidhostnames
'tlsallowinvalidhostnames': lambda *x: not validate_boolean_or_string(*x),
'ssl_match_hostname': validate_boolean_or_string,
'tlsallowinvalidcertificates': validate_boolean_or_string,
'tlsallowinvalidhostnames': validate_boolean_or_string,
'tlscafile': validate_readable,
'tlscertificatekeyfile': validate_readable,
'tlscertificatekeyfilepassword': validate_string_or_none,
# Normalized to ssl_check_ocsp_endpoint which is the logical inverse of tlsdisableocspendpointcheck
'tlsdisableocspendpointcheck': lambda *x: not validate_boolean_or_string(*x),
'tlsdisableocspendpointcheck': validate_boolean_or_string,
'tlsinsecure': validate_boolean_or_string,
'w': validate_non_negative_int_or_basestring,
'wtimeoutms': validate_non_negative_integer,
Expand Down Expand Up @@ -682,14 +668,7 @@ def validate_tzinfo(dummy, value):
INTERNAL_URI_OPTION_NAME_MAP = {
'j': 'journal',
'wtimeout': 'wtimeoutms',
'tls': 'ssl',
'tlsallowinvalidcertificates': 'ssl_cert_reqs',
'tlsallowinvalidhostnames': 'ssl_match_hostname',
'tlscrlfile': 'ssl_crlfile',
'tlscafile': 'ssl_ca_certs',
'tlscertificatekeyfile': 'ssl_certfile',
'tlscertificatekeyfilepassword': 'ssl_pem_passphrase',
'tlsdisableocspendpointcheck': 'ssl_check_ocsp_endpoint',
'ssl': 'tls',
}

# Map from deprecated URI option names to a tuple indicating the method of
Expand All @@ -704,19 +683,6 @@ def validate_tzinfo(dummy, value):
# option and/or recommend remedial action.
'j': ('renamed', 'journal'),
'wtimeout': ('renamed', 'wTimeoutMS'),
'ssl_cert_reqs': ('renamed', 'tlsAllowInvalidCertificates'),
'ssl_match_hostname': ('renamed', 'tlsAllowInvalidHostnames'),
'ssl_crlfile': ('renamed', 'tlsCRLFile'),
'ssl_ca_certs': ('renamed', 'tlsCAFile'),
'ssl_certfile': ('removed', (
'Instead of using ssl_certfile to specify the certificate file, '
'use tlsCertificateKeyFile to pass a single file containing both '
'the client certificate and the private key')),
'ssl_keyfile': ('removed', (
'Instead of using ssl_keyfile to specify the private keyfile, '
'use tlsCertificateKeyFile to pass a single file containing both '
'the client certificate and the private key')),
'ssl_pem_passphrase': ('renamed', 'tlsCertificateKeyFilePassword'),
}

# Augment the option validator map with pymongo-specific option information.
Expand Down
4 changes: 3 additions & 1 deletion pymongo/mongo_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,9 @@ def __init__(
python 2.7.9+ (pypy 2.5.1+) and 3.3+. Defaults to ``None``.
- `tlsDisableOCSPEndpointCheck`: (boolean) If ``True``, disables
certificate revocation status checking via the OCSP responder
specified on the server certificate. Defaults to ``False``.
specified on the server certificate.
``tlsDisableOCSPEndpointCheck=False`` implies ``tls=True``.
Defaults to ``False``.
- `ssl`: (boolean) Alias for ``tls``.

| **Read Concern options:**
Expand Down
2 changes: 1 addition & 1 deletion pymongo/ocsp_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def _get_issuer_cert(cert, chain, trusted_ca_certs):

# Depending on the server's TLS library, the peer's cert chain may not
# include the self signed root CA. In this case we check the user
# provided tlsCAFile (ssl_ca_certs) for the issuer.
# provided tlsCAFile for the issuer.
# Remove once we use the verified peer cert chain in PYTHON-2147.
if trusted_ca_certs:
for candidate in trusted_ca_certs:
Expand Down
12 changes: 6 additions & 6 deletions pymongo/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ class PoolOptions(object):
'__max_idle_time_seconds',
'__connect_timeout', '__socket_timeout',
'__wait_queue_timeout',
'__ssl_context', '__ssl_match_hostname',
'__ssl_context', '__tls_allow_invalid_hostnames',
'__event_listeners', '__appname', '__driver', '__metadata',
'__compression_settings', '__max_connecting',
'__pause_enabled', '__server_api', '__load_balanced')
Expand All @@ -271,7 +271,7 @@ def __init__(self, max_pool_size=MAX_POOL_SIZE,
max_idle_time_seconds=MAX_IDLE_TIME_SEC, connect_timeout=None,
socket_timeout=None, wait_queue_timeout=WAIT_QUEUE_TIMEOUT,
ssl_context=None,
ssl_match_hostname=True,
tls_allow_invalid_hostnames=False,
event_listeners=None, appname=None, driver=None,
compression_settings=None, max_connecting=MAX_CONNECTING,
pause_enabled=True, server_api=None, load_balanced=None):
Expand All @@ -282,7 +282,7 @@ def __init__(self, max_pool_size=MAX_POOL_SIZE,
self.__socket_timeout = socket_timeout
self.__wait_queue_timeout = wait_queue_timeout
self.__ssl_context = ssl_context
self.__ssl_match_hostname = ssl_match_hostname
self.__tls_allow_invalid_hostnames = tls_allow_invalid_hostnames
self.__event_listeners = event_listeners
self.__appname = appname
self.__driver = driver
Expand Down Expand Up @@ -400,10 +400,10 @@ def ssl_context(self):
return self.__ssl_context

@property
def ssl_match_hostname(self):
def tls_allow_invalid_hostnames(self):
"""Call ssl.match_hostname if cert_reqs is not ssl.CERT_NONE.
"""
return self.__ssl_match_hostname
return self.__tls_allow_invalid_hostnames

@property
def event_listeners(self):
Expand Down Expand Up @@ -1047,7 +1047,7 @@ def _configured_socket(address, options):
_raise_connection_failure(address, exc, "SSL handshake failed: ")
if (ssl_context.verify_mode and not
getattr(ssl_context, "check_hostname", False) and
options.ssl_match_hostname):
not options.tls_allow_invalid_hostnames):
try:
ssl.match_hostname(sock.getpeercert(), hostname=host)
except _CertificateError:
Expand Down
52 changes: 9 additions & 43 deletions pymongo/ssl_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,51 +39,26 @@
HAS_SNI = _ssl.HAS_SNI
IPADDR_SAFE = _ssl.IS_PYOPENSSL or sys.version_info[:2] >= (3, 7)
SSLError = _ssl.SSLError
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 isinstance(value, str) and hasattr(_stdlibssl, value):
value = getattr(_stdlibssl, value)

if value in (CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED):
return value
raise ValueError("The value of %s must be one of: "
"`ssl.CERT_NONE`, `ssl.CERT_OPTIONAL` or "
"`ssl.CERT_REQUIRED`" % (option,))

def validate_allow_invalid_certs(option, value):
"""Validate the option to allow invalid certificates is valid."""
# Avoid circular import.
from pymongo.common import validate_boolean_or_string
boolean_cert_reqs = validate_boolean_or_string(option, value)
if boolean_cert_reqs:
return CERT_NONE
return CERT_REQUIRED

def get_ssl_context(*args):
"""Create and return an SSLContext object."""
(certfile,
keyfile,
passphrase,
ca_certs,
cert_reqs,
allow_invalid_certificates,
crlfile,
match_hostname,
check_ocsp_endpoint) = args
verify_mode = CERT_REQUIRED if cert_reqs is None else cert_reqs
allow_invalid_hostnames,
disable_ocsp_endpoint_check) = args
verify_mode = CERT_NONE if allow_invalid_certificates else CERT_REQUIRED
ctx = _ssl.SSLContext(_ssl.PROTOCOL_SSLv23)
# SSLContext.check_hostname was added in CPython 3.4.
if hasattr(ctx, "check_hostname"):
if _ssl.CHECK_HOSTNAME_SAFE and verify_mode != CERT_NONE:
ctx.check_hostname = match_hostname
ctx.check_hostname = not allow_invalid_hostnames
else:
ctx.check_hostname = False
if hasattr(ctx, "check_ocsp_endpoint"):
ctx.check_ocsp_endpoint = check_ocsp_endpoint
ctx.check_ocsp_endpoint = not disable_ocsp_endpoint_check
if hasattr(ctx, "options"):
# Explicitly disable SSLv2, SSLv3 and TLS compression. Note that
# up to date versions of MongoDB 2.4 and above already disable
Expand All @@ -95,20 +70,20 @@ def get_ssl_context(*args):
ctx.options |= _ssl.OP_NO_RENEGOTIATION
if certfile is not None:
try:
ctx.load_cert_chain(certfile, keyfile, passphrase)
ctx.load_cert_chain(certfile, None, passphrase)
except _ssl.SSLError as exc:
raise ConfigurationError(
"Private key doesn't match certificate: %s" % (exc,))
if crlfile is not None:
if _ssl.IS_PYOPENSSL:
raise ConfigurationError(
"ssl_crlfile cannot be used with PyOpenSSL")
"tlsCRLFile cannot be used with PyOpenSSL")
# Match the server's behavior.
ctx.verify_flags = getattr(_ssl, "VERIFY_CRL_CHECK_LEAF", 0)
ctx.load_verify_locations(crlfile)
if ca_certs is not None:
ctx.load_verify_locations(ca_certs)
elif cert_reqs != CERT_NONE:
elif verify_mode != CERT_NONE:
ctx.load_default_certs()
ctx.verify_mode = verify_mode
return ctx
Expand All @@ -117,15 +92,6 @@ class SSLError(Exception):
pass
HAS_SNI = False
IPADDR_SAFE = False
def validate_cert_reqs(option, dummy):
"""No ssl module, raise ConfigurationError."""
raise ConfigurationError("The value of %s is set but can't be "
"validated. The ssl module is not available"
% (option,))

def validate_allow_invalid_certs(option, dummy):
"""No ssl module, raise ConfigurationError."""
return validate_cert_reqs(option, dummy)

def get_ssl_context(*dummy):
"""No ssl module, raise ConfigurationError."""
Expand Down
Loading