Enable TLS/SSL CTX Options for the get_certificate Module (#779)

* Enable SSL CTX options for get_certificate

Signed-off-by: David Ehrman <dlehrman@liberty.edu>

* Support both str and int SSL CTX options, override defaults

Signed-off-by: David Ehrman <dlehrman@liberty.edu>

* Add changelog fragment

Signed-off-by: David Ehrman <dlehrman@liberty.edu>

* 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 <dlehrman@liberty.edu>

* Set ssl_ctx_options version_added

Signed-off-by: David Ehrman <dlehrman@liberty.edu>

* Initial application of suggestions from code review

Working on completing application of suggestions

Co-authored-by: Felix Fontein <felix@fontein.de>

* Finish applying suggestions from code review

Signed-off-by: David Ehrman <dlehrman@liberty.edu>

* Documentation update

Co-authored-by: Felix Fontein <felix@fontein.de>

* Include value in fail output for wrong data type

Co-authored-by: Felix Fontein <felix@fontein.de>

* Handle invalid tls_ctx_option strings

Co-authored-by: Felix Fontein <felix@fontein.de>

* Minor documentation update

Signed-off-by: David Ehrman <dlehrman@liberty.edu>

---------

Signed-off-by: David Ehrman <dlehrman@liberty.edu>
Co-authored-by: Felix Fontein <felix@fontein.de>
pull/780/head
dlehrman 2024-07-07 15:48:48 -04:00 committed by GitHub
parent 577d86265e
commit 6ba06f24ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 70 additions and 1 deletions

View File

@ -0,0 +1,4 @@
---
minor_changes:
- get_certificate - adds ``tls_ctx_options`` option for specifying SSL CTX options (https://github.com/ansible-collections/community.crypto/pull/779).
...

View File

@ -99,6 +99,14 @@ options:
- 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
tls_ctx_options:
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 context options is dependent on the Python and OpenSSL/LibreSSL versions.
type: list
elements: raw
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.
@ -205,18 +213,37 @@ EXAMPLES = '''
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
tls_ctx_options:
- 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
'''
import atexit
import base64
import traceback
import ssl
from os.path import isfile
from socket import create_connection, setdefaulttimeout, socket
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
@ -285,6 +312,7 @@ def main():
starttls=dict(type='str', choices=['mysql']),
ciphers=dict(type='list', elements='str'),
asn1_base64=dict(type='bool'),
tls_ctx_options=dict(type='list', elements='raw'),
),
)
@ -298,6 +326,7 @@ def main():
start_tls_server_type = module.params.get('starttls')
ciphers = module.params.get('ciphers')
asn1_base64 = module.params['asn1_base64']
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 '
@ -346,6 +375,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 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!
cert = get_server_certificate((host, port), ca_certs=ca_cert)
@ -381,6 +413,39 @@ def main():
ciphers_joined = ":".join(ciphers)
ctx.set_ciphers(ciphers_joined)
if tls_ctx_options is not None:
# Clear default ctx options
ctx.options = 0
# 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, 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
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(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, got {0!r}".format(tls_ctx_option))
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)
except Exception as e: