From b6f7c164cbe7cfb25a9d7a5289c87078b4e8851f Mon Sep 17 00:00:00 2001 From: David Ehrman Date: Sat, 6 Jul 2024 12:37:31 -0400 Subject: [PATCH 01/11] Enable SSL CTX options for get_certificate Signed-off-by: David Ehrman --- plugins/modules/get_certificate.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/plugins/modules/get_certificate.py b/plugins/modules/get_certificate.py index d4b38afbd..bf671c09b 100644 --- a/plugins/modules/get_certificate.py +++ b/plugins/modules/get_certificate.py @@ -99,6 +99,13 @@ - The default value V(false) is B(deprecated) and will change to V(true) in community.crypto 3.0.0. type: bool version_added: 2.12.0 + ssl_ctx_options: + description: + - SSL CTX options (SSL OP flags) to use for the request. + - See the L(List of SSL OP Flags,https://wiki.openssl.org/index.php/List_of_SSL_OP_Flags) for more details. + - The available SSL CTX options is dependent on the Python and OpenSSL/LibreSSL versions. + type: list + version_added: tbd notes: - When using ca_cert on OS X it has been reported that in some conditions the validate will always succeed. @@ -205,6 +212,18 @@ msg: "cert expires in: {{ expire_days }} days." vars: expire_days: "{{ (( cert.not_after | to_datetime('%Y%m%d%H%M%SZ')) - (ansible_date_time.iso8601 | to_datetime('%Y-%m-%dT%H:%M:%SZ')) ).days }}" + +- name: Allow legacy insecure renegotiation to get a cert from a legacy device + community.crypto.get_certificate: + host: "legacy-device.domain.com" + port: 443 + ciphers: + - HIGH + ssl_ctx_options: + - 0x4 + delegate_to: localhost + run_once: true + register: legacy_cert ''' import atexit @@ -285,6 +304,7 @@ def main(): starttls=dict(type='str', choices=['mysql']), ciphers=dict(type='list', elements='str'), asn1_base64=dict(type='bool'), + ssl_ctx_options=dict(type='list', default=None), ), ) @@ -298,6 +318,7 @@ def main(): start_tls_server_type = module.params.get('starttls') ciphers = module.params.get('ciphers') asn1_base64 = module.params['asn1_base64'] + ssl_ctx_options = module.params.get('ssl_ctx_options') if asn1_base64 is None: module.deprecate( 'The default value `false` for asn1_base64 is deprecated and will change to `true` in ' @@ -346,6 +367,9 @@ def main(): if ciphers is not None: module.fail_json(msg='To use ciphers, you must run the get_certificate module with Python 2.7 or newer.', exception=CREATE_DEFAULT_CONTEXT_IMP_ERR) + if ssl_ctx_options is not None: + module.fail_json(msg='To use ssl_ctx_options, you must run the get_certificate module with Python 2.7 or newer.', + exception=CREATE_DEFAULT_CONTEXT_IMP_ERR) try: # Note: get_server_certificate does not support SNI! cert = get_server_certificate((host, port), ca_certs=ca_cert) @@ -381,6 +405,10 @@ def main(): ciphers_joined = ":".join(ciphers) ctx.set_ciphers(ciphers_joined) + if ssl_ctx_options is not None: + for ssl_ctx_option in ssl_ctx_options: + ctx.options |= ssl_ctx_option + cert = ctx.wrap_socket(sock, server_hostname=server_name or host).getpeercert(True) cert = DER_cert_to_PEM_cert(cert) except Exception as e: From e6af8aad52d382107d76e7046d0fbc89851312e0 Mon Sep 17 00:00:00 2001 From: David Ehrman Date: Sat, 6 Jul 2024 15:21:34 -0400 Subject: [PATCH 02/11] Support both str and int SSL CTX options, override defaults Signed-off-by: David Ehrman --- plugins/modules/get_certificate.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/plugins/modules/get_certificate.py b/plugins/modules/get_certificate.py index bf671c09b..af9bf9243 100644 --- a/plugins/modules/get_certificate.py +++ b/plugins/modules/get_certificate.py @@ -105,6 +105,7 @@ - See the L(List of SSL OP Flags,https://wiki.openssl.org/index.php/List_of_SSL_OP_Flags) for more details. - The available SSL CTX options is dependent on the Python and OpenSSL/LibreSSL versions. type: list + elements: [ str, int ] version_added: tbd notes: @@ -220,7 +221,12 @@ ciphers: - HIGH ssl_ctx_options: - - 0x4 + - OP_ALL + - OP_NO_SSLv3 + - OP_CIPHER_SERVER_PREFERENCE + - OP_ENABLE_MIDDLEBOX_COMPAT + - OP_NO_COMPRESSION + - 4 # OP_LEGACY_SERVER_CONNECT delegate_to: localhost run_once: true register: legacy_cert @@ -229,6 +235,7 @@ import atexit import base64 import traceback +import ssl from os.path import isfile from socket import create_connection, setdefaulttimeout, socket @@ -406,8 +413,19 @@ def main(): ctx.set_ciphers(ciphers_joined) if ssl_ctx_options is not None: + # Clear default options + ctx.options = 0 + + # For each item in the ssl_ctx_options list for ssl_ctx_option in ssl_ctx_options: - ctx.options |= ssl_ctx_option + # If the item is a string + if isinstance(ssl_ctx_option, str): + # Get the int value for the option and add it to ctx options + ctx.options |= getattr(ssl, ssl_ctx_option) + # If the item is an integer + elif isinstance(ssl_ctx_option, int): + # Add the int value of the option to ctx options + ctx.options |= ssl_ctx_option cert = ctx.wrap_socket(sock, server_hostname=server_name or host).getpeercert(True) cert = DER_cert_to_PEM_cert(cert) From acc728207fe6d05c7c2951cb4316353daae1c562 Mon Sep 17 00:00:00 2001 From: David Ehrman Date: Sat, 6 Jul 2024 15:37:37 -0400 Subject: [PATCH 03/11] Add changelog fragment Signed-off-by: David Ehrman --- changelogs/fragments/779-add-ssl_ctx_options-option.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/fragments/779-add-ssl_ctx_options-option.yml diff --git a/changelogs/fragments/779-add-ssl_ctx_options-option.yml b/changelogs/fragments/779-add-ssl_ctx_options-option.yml new file mode 100644 index 000000000..4398d48b9 --- /dev/null +++ b/changelogs/fragments/779-add-ssl_ctx_options-option.yml @@ -0,0 +1,4 @@ +--- +minor_changes: + - get_certificate - adds `ssl_ctx_options` option for specifying SSL CTX options (https://github.com/ansible-collections/community.crypto/pull/779). +... From efe1460eeee29d905f179b4a5181fed3fc10a6a9 Mon Sep 17 00:00:00 2001 From: David Ehrman Date: Sat, 6 Jul 2024 15:40:53 -0400 Subject: [PATCH 04/11] Resolve doc builder error ssl_ctx_options can be a mix of str and int, but `elements: [ str, int ]` made the Ansible doc builder angry. Signed-off-by: David Ehrman --- plugins/modules/get_certificate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/modules/get_certificate.py b/plugins/modules/get_certificate.py index af9bf9243..c20ed39ec 100644 --- a/plugins/modules/get_certificate.py +++ b/plugins/modules/get_certificate.py @@ -105,7 +105,6 @@ - See the L(List of SSL OP Flags,https://wiki.openssl.org/index.php/List_of_SSL_OP_Flags) for more details. - The available SSL CTX options is dependent on the Python and OpenSSL/LibreSSL versions. type: list - elements: [ str, int ] version_added: tbd notes: From a4a9204ffab5bdc9a72b5f7d05fe17f3db3bb7e5 Mon Sep 17 00:00:00 2001 From: David Ehrman Date: Sat, 6 Jul 2024 15:42:41 -0400 Subject: [PATCH 05/11] Set ssl_ctx_options version_added Signed-off-by: David Ehrman --- plugins/modules/get_certificate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/get_certificate.py b/plugins/modules/get_certificate.py index c20ed39ec..866bf3820 100644 --- a/plugins/modules/get_certificate.py +++ b/plugins/modules/get_certificate.py @@ -105,7 +105,7 @@ - See the L(List of SSL OP Flags,https://wiki.openssl.org/index.php/List_of_SSL_OP_Flags) for more details. - The available SSL CTX options is dependent on the Python and OpenSSL/LibreSSL versions. type: list - version_added: tbd + version_added: 2.21.0 notes: - When using ca_cert on OS X it has been reported that in some conditions the validate will always succeed. From 096399f395c787aa7d38bf23494f01844ab9ed4d Mon Sep 17 00:00:00 2001 From: dlehrman Date: Sat, 6 Jul 2024 15:52:25 -0400 Subject: [PATCH 06/11] Initial application of suggestions from code review Working on completing application of suggestions Co-authored-by: Felix Fontein --- changelogs/fragments/779-add-ssl_ctx_options-option.yml | 2 +- plugins/modules/get_certificate.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/changelogs/fragments/779-add-ssl_ctx_options-option.yml b/changelogs/fragments/779-add-ssl_ctx_options-option.yml index 4398d48b9..c656903cf 100644 --- a/changelogs/fragments/779-add-ssl_ctx_options-option.yml +++ b/changelogs/fragments/779-add-ssl_ctx_options-option.yml @@ -1,4 +1,4 @@ --- minor_changes: - - get_certificate - adds `ssl_ctx_options` option for specifying SSL CTX options (https://github.com/ansible-collections/community.crypto/pull/779). + - get_certificate - adds ``tls_ctx_options`` option for specifying SSL CTX options (https://github.com/ansible-collections/community.crypto/pull/779). ... diff --git a/plugins/modules/get_certificate.py b/plugins/modules/get_certificate.py index 866bf3820..3c0ae7ae4 100644 --- a/plugins/modules/get_certificate.py +++ b/plugins/modules/get_certificate.py @@ -99,7 +99,7 @@ - The default value V(false) is B(deprecated) and will change to V(true) in community.crypto 3.0.0. type: bool version_added: 2.12.0 - ssl_ctx_options: + tls_ctx_options: description: - SSL CTX options (SSL OP flags) to use for the request. - See the L(List of SSL OP Flags,https://wiki.openssl.org/index.php/List_of_SSL_OP_Flags) for more details. @@ -310,7 +310,7 @@ def main(): starttls=dict(type='str', choices=['mysql']), ciphers=dict(type='list', elements='str'), asn1_base64=dict(type='bool'), - ssl_ctx_options=dict(type='list', default=None), + ssl_ctx_options=dict(type='list', elements='raw'), ), ) From 6659bfe9e9ebcb0d2a77a4be3148f2061b15455f Mon Sep 17 00:00:00 2001 From: David Ehrman Date: Sat, 6 Jul 2024 17:08:48 -0400 Subject: [PATCH 07/11] Finish applying suggestions from code review Signed-off-by: David Ehrman --- ...yml => 779-add-tls_ctx_options-option.yml} | 0 plugins/modules/get_certificate.py | 58 +++++++++++++------ 2 files changed, 39 insertions(+), 19 deletions(-) rename changelogs/fragments/{779-add-ssl_ctx_options-option.yml => 779-add-tls_ctx_options-option.yml} (100%) diff --git a/changelogs/fragments/779-add-ssl_ctx_options-option.yml b/changelogs/fragments/779-add-tls_ctx_options-option.yml similarity index 100% rename from changelogs/fragments/779-add-ssl_ctx_options-option.yml rename to changelogs/fragments/779-add-tls_ctx_options-option.yml diff --git a/plugins/modules/get_certificate.py b/plugins/modules/get_certificate.py index 3c0ae7ae4..2c9d2af64 100644 --- a/plugins/modules/get_certificate.py +++ b/plugins/modules/get_certificate.py @@ -101,10 +101,11 @@ version_added: 2.12.0 tls_ctx_options: description: - - SSL CTX options (SSL OP flags) to use for the request. + - TLS CTX options (TLS/SSL OP flags) to use for the request. - See the L(List of SSL OP Flags,https://wiki.openssl.org/index.php/List_of_SSL_OP_Flags) for more details. - - The available SSL CTX options is dependent on the Python and OpenSSL/LibreSSL versions. + - The available TLS CTX options is dependent on the Python and OpenSSL/LibreSSL versions. type: list + elements: raw version_added: 2.21.0 notes: @@ -219,7 +220,7 @@ port: 443 ciphers: - HIGH - ssl_ctx_options: + tls_ctx_options: - OP_ALL - OP_NO_SSLv3 - OP_CIPHER_SERVER_PREFERENCE @@ -241,7 +242,8 @@ from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_REQUIRED from ansible.module_utils.basic import AnsibleModule, missing_required_lib -from ansible.module_utils.common.text.converters import to_bytes +from ansible.module_utils.common.text.converters import to_bytes, to_native +from ansible.module_utils.six import string_types from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion @@ -310,7 +312,7 @@ def main(): starttls=dict(type='str', choices=['mysql']), ciphers=dict(type='list', elements='str'), asn1_base64=dict(type='bool'), - ssl_ctx_options=dict(type='list', elements='raw'), + tls_ctx_options=dict(type='list', elements='raw'), ), ) @@ -324,7 +326,7 @@ def main(): start_tls_server_type = module.params.get('starttls') ciphers = module.params.get('ciphers') asn1_base64 = module.params['asn1_base64'] - ssl_ctx_options = module.params.get('ssl_ctx_options') + tls_ctx_options = module.params.get('tls_ctx_options') if asn1_base64 is None: module.deprecate( 'The default value `false` for asn1_base64 is deprecated and will change to `true` in ' @@ -373,8 +375,8 @@ def main(): if ciphers is not None: module.fail_json(msg='To use ciphers, you must run the get_certificate module with Python 2.7 or newer.', exception=CREATE_DEFAULT_CONTEXT_IMP_ERR) - if ssl_ctx_options is not None: - module.fail_json(msg='To use ssl_ctx_options, you must run the get_certificate module with Python 2.7 or newer.', + if tls_ctx_options is not None: + module.fail_json(msg='To use tls_ctx_options, you must run the get_certificate module with Python 2.7 or newer.', exception=CREATE_DEFAULT_CONTEXT_IMP_ERR) try: # Note: get_server_certificate does not support SNI! @@ -411,20 +413,38 @@ def main(): ciphers_joined = ":".join(ciphers) ctx.set_ciphers(ciphers_joined) - if ssl_ctx_options is not None: - # Clear default options + if tls_ctx_options is not None: + # Clear default ctx options ctx.options = 0 - # For each item in the ssl_ctx_options list - for ssl_ctx_option in ssl_ctx_options: - # If the item is a string - if isinstance(ssl_ctx_option, str): - # Get the int value for the option and add it to ctx options - ctx.options |= getattr(ssl, ssl_ctx_option) + # For each item in the tls_ctx_options list + for tls_ctx_option in tls_ctx_options: + # If the item is a string_type + if isinstance(tls_ctx_option, string_types): + # Convert tls_ctx_option to a native string + tls_ctx_option_str = to_native(tls_ctx_option) + # Get the tls_ctx_option_str attribute from ssl + tls_ctx_option_attr = getattr(ssl, tls_ctx_option_str) + # If tls_ctx_option_attr is an integer + if isinstance(tls_ctx_option_attr, int): + # Set tls_ctx_option_int to the attribute value + tls_ctx_option_int = tls_ctx_option_attr + # If tls_ctx_option_attr is not an integer + else: + module.fail_json(msg="Failed to determine the numeric value for {0}".format(tls_ctx_option_str)) # If the item is an integer - elif isinstance(ssl_ctx_option, int): - # Add the int value of the option to ctx options - ctx.options |= ssl_ctx_option + elif isinstance(tls_ctx_option, int): + # Set tls_ctx_option_int to the item value + tls_ctx_option_int = tls_ctx_option + # If the item is not a string nor integer + else: + module.fail_json(msg="tls_ctx_options must be a string or integer") + + try: + # Add the int value of the item to ctx options + ctx.options |= tls_ctx_option_int + except Exception as e: + module.fail_json(msg="Failed to add {0} to CTX options".format(tls_ctx_option_str or tls_ctx_option_int)) cert = ctx.wrap_socket(sock, server_hostname=server_name or host).getpeercert(True) cert = DER_cert_to_PEM_cert(cert) From e97ddb671c238dd0e669ccef7b32299d7527c969 Mon Sep 17 00:00:00 2001 From: dlehrman Date: Sun, 7 Jul 2024 14:42:55 -0400 Subject: [PATCH 08/11] Documentation update Co-authored-by: Felix Fontein --- plugins/modules/get_certificate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/get_certificate.py b/plugins/modules/get_certificate.py index 2c9d2af64..0252be4f6 100644 --- a/plugins/modules/get_certificate.py +++ b/plugins/modules/get_certificate.py @@ -101,7 +101,7 @@ version_added: 2.12.0 tls_ctx_options: description: - - TLS CTX options (TLS/SSL OP flags) to use for the request. + - TLS context options (TLS/SSL OP flags) to use for the request. - See the L(List of SSL OP Flags,https://wiki.openssl.org/index.php/List_of_SSL_OP_Flags) for more details. - The available TLS CTX options is dependent on the Python and OpenSSL/LibreSSL versions. type: list From 3a3bb86b959f9bbf2de04c622a0984c3aeb701bb Mon Sep 17 00:00:00 2001 From: dlehrman Date: Sun, 7 Jul 2024 15:04:35 -0400 Subject: [PATCH 09/11] Include value in fail output for wrong data type Co-authored-by: Felix Fontein --- plugins/modules/get_certificate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/get_certificate.py b/plugins/modules/get_certificate.py index 0252be4f6..1fc78d723 100644 --- a/plugins/modules/get_certificate.py +++ b/plugins/modules/get_certificate.py @@ -438,7 +438,7 @@ def main(): tls_ctx_option_int = tls_ctx_option # If the item is not a string nor integer else: - module.fail_json(msg="tls_ctx_options must be a string or integer") + module.fail_json(msg="tls_ctx_options must be a string or integer, got {0!r}".format(tls_ctx_option)) try: # Add the int value of the item to ctx options From bba9e2f1843fd6bd5cee254e161bc42b91968652 Mon Sep 17 00:00:00 2001 From: dlehrman Date: Sun, 7 Jul 2024 15:09:29 -0400 Subject: [PATCH 10/11] Handle invalid tls_ctx_option strings Co-authored-by: Felix Fontein --- plugins/modules/get_certificate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/get_certificate.py b/plugins/modules/get_certificate.py index 1fc78d723..ad6051dee 100644 --- a/plugins/modules/get_certificate.py +++ b/plugins/modules/get_certificate.py @@ -424,7 +424,7 @@ def main(): # Convert tls_ctx_option to a native string tls_ctx_option_str = to_native(tls_ctx_option) # Get the tls_ctx_option_str attribute from ssl - tls_ctx_option_attr = getattr(ssl, tls_ctx_option_str) + tls_ctx_option_attr = getattr(ssl, tls_ctx_option_str, None) # If tls_ctx_option_attr is an integer if isinstance(tls_ctx_option_attr, int): # Set tls_ctx_option_int to the attribute value From 9a90a42e9b2553a71ecec8b21e810e4cf07fc4d2 Mon Sep 17 00:00:00 2001 From: David Ehrman Date: Sun, 7 Jul 2024 15:12:03 -0400 Subject: [PATCH 11/11] Minor documentation update Signed-off-by: David Ehrman --- plugins/modules/get_certificate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/get_certificate.py b/plugins/modules/get_certificate.py index ad6051dee..d9ebe80cb 100644 --- a/plugins/modules/get_certificate.py +++ b/plugins/modules/get_certificate.py @@ -103,7 +103,7 @@ description: - TLS context options (TLS/SSL OP flags) to use for the request. - See the L(List of SSL OP Flags,https://wiki.openssl.org/index.php/List_of_SSL_OP_Flags) for more details. - - The available TLS CTX options is dependent on the Python and OpenSSL/LibreSSL versions. + - The available TLS context options is dependent on the Python and OpenSSL/LibreSSL versions. type: list elements: raw version_added: 2.21.0