Skip to content

Commit 6ba06f2

Browse files
Enable TLS/SSL CTX Options for the get_certificate Module (#779)
* Enable SSL CTX options for get_certificate Signed-off-by: David Ehrman <[email protected]> * Support both str and int SSL CTX options, override defaults Signed-off-by: David Ehrman <[email protected]> * Add changelog fragment Signed-off-by: David Ehrman <[email protected]> * 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 <[email protected]> * Set ssl_ctx_options version_added Signed-off-by: David Ehrman <[email protected]> * Initial application of suggestions from code review Working on completing application of suggestions Co-authored-by: Felix Fontein <[email protected]> * Finish applying suggestions from code review Signed-off-by: David Ehrman <[email protected]> * Documentation update Co-authored-by: Felix Fontein <[email protected]> * Include value in fail output for wrong data type Co-authored-by: Felix Fontein <[email protected]> * Handle invalid tls_ctx_option strings Co-authored-by: Felix Fontein <[email protected]> * Minor documentation update Signed-off-by: David Ehrman <[email protected]> --------- Signed-off-by: David Ehrman <[email protected]> Co-authored-by: Felix Fontein <[email protected]>
1 parent 577d862 commit 6ba06f2

File tree

2 files changed

+70
-1
lines changed

2 files changed

+70
-1
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
minor_changes:
3+
- get_certificate - adds ``tls_ctx_options`` option for specifying SSL CTX options (https://github.com/ansible-collections/community.crypto/pull/779).
4+
...

plugins/modules/get_certificate.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@
9999
- The default value V(false) is B(deprecated) and will change to V(true) in community.crypto 3.0.0.
100100
type: bool
101101
version_added: 2.12.0
102+
tls_ctx_options:
103+
description:
104+
- TLS context options (TLS/SSL OP flags) to use for the request.
105+
- See the L(List of SSL OP Flags,https://wiki.openssl.org/index.php/List_of_SSL_OP_Flags) for more details.
106+
- The available TLS context options is dependent on the Python and OpenSSL/LibreSSL versions.
107+
type: list
108+
elements: raw
109+
version_added: 2.21.0
102110
103111
notes:
104112
- When using ca_cert on OS X it has been reported that in some conditions the validate will always succeed.
@@ -205,18 +213,37 @@
205213
msg: "cert expires in: {{ expire_days }} days."
206214
vars:
207215
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 }}"
216+
217+
- name: Allow legacy insecure renegotiation to get a cert from a legacy device
218+
community.crypto.get_certificate:
219+
host: "legacy-device.domain.com"
220+
port: 443
221+
ciphers:
222+
- HIGH
223+
tls_ctx_options:
224+
- OP_ALL
225+
- OP_NO_SSLv3
226+
- OP_CIPHER_SERVER_PREFERENCE
227+
- OP_ENABLE_MIDDLEBOX_COMPAT
228+
- OP_NO_COMPRESSION
229+
- 4 # OP_LEGACY_SERVER_CONNECT
230+
delegate_to: localhost
231+
run_once: true
232+
register: legacy_cert
208233
'''
209234

210235
import atexit
211236
import base64
212237
import traceback
238+
import ssl
213239

214240
from os.path import isfile
215241
from socket import create_connection, setdefaulttimeout, socket
216242
from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_REQUIRED
217243

218244
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
219-
from ansible.module_utils.common.text.converters import to_bytes
245+
from ansible.module_utils.common.text.converters import to_bytes, to_native
246+
from ansible.module_utils.six import string_types
220247

221248
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
222249

@@ -285,6 +312,7 @@ def main():
285312
starttls=dict(type='str', choices=['mysql']),
286313
ciphers=dict(type='list', elements='str'),
287314
asn1_base64=dict(type='bool'),
315+
tls_ctx_options=dict(type='list', elements='raw'),
288316
),
289317
)
290318

@@ -298,6 +326,7 @@ def main():
298326
start_tls_server_type = module.params.get('starttls')
299327
ciphers = module.params.get('ciphers')
300328
asn1_base64 = module.params['asn1_base64']
329+
tls_ctx_options = module.params.get('tls_ctx_options')
301330
if asn1_base64 is None:
302331
module.deprecate(
303332
'The default value `false` for asn1_base64 is deprecated and will change to `true` in '
@@ -346,6 +375,9 @@ def main():
346375
if ciphers is not None:
347376
module.fail_json(msg='To use ciphers, you must run the get_certificate module with Python 2.7 or newer.',
348377
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR)
378+
if tls_ctx_options is not None:
379+
module.fail_json(msg='To use tls_ctx_options, you must run the get_certificate module with Python 2.7 or newer.',
380+
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR)
349381
try:
350382
# Note: get_server_certificate does not support SNI!
351383
cert = get_server_certificate((host, port), ca_certs=ca_cert)
@@ -381,6 +413,39 @@ def main():
381413
ciphers_joined = ":".join(ciphers)
382414
ctx.set_ciphers(ciphers_joined)
383415

416+
if tls_ctx_options is not None:
417+
# Clear default ctx options
418+
ctx.options = 0
419+
420+
# For each item in the tls_ctx_options list
421+
for tls_ctx_option in tls_ctx_options:
422+
# If the item is a string_type
423+
if isinstance(tls_ctx_option, string_types):
424+
# Convert tls_ctx_option to a native string
425+
tls_ctx_option_str = to_native(tls_ctx_option)
426+
# Get the tls_ctx_option_str attribute from ssl
427+
tls_ctx_option_attr = getattr(ssl, tls_ctx_option_str, None)
428+
# If tls_ctx_option_attr is an integer
429+
if isinstance(tls_ctx_option_attr, int):
430+
# Set tls_ctx_option_int to the attribute value
431+
tls_ctx_option_int = tls_ctx_option_attr
432+
# If tls_ctx_option_attr is not an integer
433+
else:
434+
module.fail_json(msg="Failed to determine the numeric value for {0}".format(tls_ctx_option_str))
435+
# If the item is an integer
436+
elif isinstance(tls_ctx_option, int):
437+
# Set tls_ctx_option_int to the item value
438+
tls_ctx_option_int = tls_ctx_option
439+
# If the item is not a string nor integer
440+
else:
441+
module.fail_json(msg="tls_ctx_options must be a string or integer, got {0!r}".format(tls_ctx_option))
442+
443+
try:
444+
# Add the int value of the item to ctx options
445+
ctx.options |= tls_ctx_option_int
446+
except Exception as e:
447+
module.fail_json(msg="Failed to add {0} to CTX options".format(tls_ctx_option_str or tls_ctx_option_int))
448+
384449
cert = ctx.wrap_socket(sock, server_hostname=server_name or host).getpeercert(True)
385450
cert = DER_cert_to_PEM_cert(cert)
386451
except Exception as e:

0 commit comments

Comments
 (0)