-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Changes from 22 commits
fd50ca1
ae1358a
d285d3d
6f24cdb
ebd1d4d
93cbf38
e236f8b
deca485
1c4303b
b0f197b
f5fdc9a
8449cc6
5847213
4f2f350
566f027
045dd73
ec12ee6
7aec629
ef822ce
5eeffb1
8ed1aef
b5924c4
e8db6f0
c393433
1f2f6ba
48daa20
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,43 +69,51 @@ 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') | ||
allow_invalid_certificates = options.get('tlsallowinvalidcertificates') | ||
allow_invalid_hostnames = options.get('tlsallowinvalidhostnames', False) | ||
crlfile = options.get('tlscrlfile') | ||
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 options[opt] is False: | ||
enabled_tls_opts.append(opt) | ||
|
||
if enabled_tls_opts: | ||
if use_tls is False: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest switching the order of the this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
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)) | ||
elif use_tls is None: | ||
# Implicitly enable TLS when one of the tls* options is set. | ||
use_tls = True | ||
|
||
if use_tls is True: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
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): | ||
|
@@ -127,14 +135,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, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
|
@@ -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'], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the decision was made to keep There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think you're right that we should keep it. The URI-options spec still has |
||
'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 | ||
|
@@ -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, | ||
|
@@ -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 | ||
|
@@ -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. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,51 +39,29 @@ | |
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 | ||
if allow_invalid_certificates is True: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed to a one-liner:
|
||
verify_mode = CERT_NONE | ||
else: # allow_invalid_certificates in (False, None) | ||
verify_mode = 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 | ||
|
@@ -95,20 +73,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 | ||
|
@@ -117,15 +95,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.""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is
is False
needed or can this beand not options[opt]
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to
and not options[opt]