acme_certificate_renewal_info: add treat_parsing_error_as_non_existing option and existing and parsable return values (#838)
* Fix error reporting for OpenSSL backend: raise BackendExceptions instead of directly failing the module. * Add treat_parsing_error_as_non_existing option and existing and parsable return values.pull/837/head
parent
49354f2121
commit
01e7bf1f33
|
@ -0,0 +1,3 @@
|
|||
minor_changes:
|
||||
- "acme_certificate_renewal_info - add ``exists`` and ``parsable`` return values and ``treat_parsing_error_as_non_existing`` option
|
||||
(https://github.com/ansible-collections/community.crypto/pull/838)."
|
|
@ -122,8 +122,10 @@ class OpenSSLCLIBackend(CryptoBackend):
|
|||
raise KeyParsingError('unknown key type "%s"' % account_key_type)
|
||||
|
||||
openssl_keydump_cmd = [self.openssl_binary, account_key_type, "-in", key_file, "-noout", "-text"]
|
||||
dummy, out, dummy = self.module.run_command(
|
||||
openssl_keydump_cmd, check_rc=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
||||
rc, out, err = self.module.run_command(
|
||||
openssl_keydump_cmd, check_rc=False, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
||||
if rc != 0:
|
||||
raise BackendException('Error while running {cmd}: {stderr}'.format(cmd=' '.join(openssl_keydump_cmd), stderr=to_text(err)))
|
||||
|
||||
out_text = to_text(out, errors='surrogate_or_strict')
|
||||
|
||||
|
@ -205,8 +207,10 @@ class OpenSSLCLIBackend(CryptoBackend):
|
|||
cmd_postfix = ["-sign", key_data['key_file']]
|
||||
openssl_sign_cmd = [self.openssl_binary, "dgst", "-{0}".format(key_data['hash'])] + cmd_postfix
|
||||
|
||||
dummy, out, dummy = self.module.run_command(
|
||||
openssl_sign_cmd, data=sign_payload, check_rc=True, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
||||
rc, out, err = self.module.run_command(
|
||||
openssl_sign_cmd, data=sign_payload, check_rc=False, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
||||
if rc != 0:
|
||||
raise BackendException('Error while running {cmd}: {stderr}'.format(cmd=' '.join(openssl_sign_cmd), stderr=to_text(err)))
|
||||
|
||||
if key_data['type'] == 'ec':
|
||||
dummy, der_out, dummy = self.module.run_command(
|
||||
|
@ -281,8 +285,10 @@ class OpenSSLCLIBackend(CryptoBackend):
|
|||
data = csr_content.encode('utf-8')
|
||||
|
||||
openssl_csr_cmd = [self.openssl_binary, "req", "-in", filename, "-noout", "-text"]
|
||||
dummy, out, dummy = self.module.run_command(
|
||||
openssl_csr_cmd, data=data, check_rc=True, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
||||
rc, out, err = self.module.run_command(
|
||||
openssl_csr_cmd, data=data, check_rc=False, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
||||
if rc != 0:
|
||||
raise BackendException('Error while running {cmd}: {stderr}'.format(cmd=' '.join(openssl_csr_cmd), stderr=to_text(err)))
|
||||
|
||||
identifiers = set()
|
||||
result = []
|
||||
|
@ -341,8 +347,11 @@ class OpenSSLCLIBackend(CryptoBackend):
|
|||
return -1
|
||||
|
||||
openssl_cert_cmd = [self.openssl_binary, "x509", "-in", filename, "-noout", "-text"]
|
||||
dummy, out, dummy = self.module.run_command(
|
||||
openssl_cert_cmd, data=data, check_rc=True, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
||||
rc, out, err = self.module.run_command(
|
||||
openssl_cert_cmd, data=data, check_rc=False, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
||||
if rc != 0:
|
||||
raise BackendException('Error while running {cmd}: {stderr}'.format(cmd=' '.join(openssl_cert_cmd), stderr=to_text(err)))
|
||||
|
||||
out_text = to_text(out, errors='surrogate_or_strict')
|
||||
not_after = _extract_date(out_text, 'Not After', cert_filename_suffix=cert_filename_suffix)
|
||||
if now is None:
|
||||
|
@ -371,8 +380,11 @@ class OpenSSLCLIBackend(CryptoBackend):
|
|||
cert_filename_suffix = ''
|
||||
|
||||
openssl_cert_cmd = [self.openssl_binary, "x509", "-in", filename, "-noout", "-text"]
|
||||
dummy, out, dummy = self.module.run_command(
|
||||
openssl_cert_cmd, data=data, check_rc=True, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
||||
rc, out, err = self.module.run_command(
|
||||
openssl_cert_cmd, data=data, check_rc=False, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
|
||||
if rc != 0:
|
||||
raise BackendException('Error while running {cmd}: {stderr}'.format(cmd=' '.join(openssl_cert_cmd), stderr=to_text(err)))
|
||||
|
||||
out_text = to_text(out, errors='surrogate_or_strict')
|
||||
|
||||
not_after = _extract_date(out_text, 'Not After', cert_filename_suffix=cert_filename_suffix)
|
||||
|
|
|
@ -74,6 +74,15 @@ options:
|
|||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer + C([w | d | h | m | s]) (for example
|
||||
V(+32w1d2h)).
|
||||
type: str
|
||||
treat_parsing_error_as_non_existing:
|
||||
description:
|
||||
- Determines the behavior when the certificate file exists or its contents are provided, but the certificate cannot be parsed.
|
||||
- If V(true), will exit successfully with RV(exists=true), RV(parsable=false), and RV(should_renew=true).
|
||||
- If V(false), the module will fail.
|
||||
- If the file exists, but cannot be loaded due to I/O errors or permission errors, the module always fails.
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 2.24.0
|
||||
seealso:
|
||||
- module: community.crypto.acme_certificate
|
||||
description: Allows to obtain a certificate using the ACME protocol.
|
||||
|
@ -101,6 +110,23 @@ should_renew:
|
|||
type: bool
|
||||
sample: true
|
||||
|
||||
exists:
|
||||
description:
|
||||
- Whether the certificate file exists, or O(certificate_content) was provided.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: true
|
||||
version_added: 2.24.0
|
||||
|
||||
parsable:
|
||||
description:
|
||||
- Whether the certificate file exists, or O(certificate_content) was provided, and the certificate can be parsed.
|
||||
- Can only differ from RV(exists) if O(treat_parsing_error_as_non_existing=true).
|
||||
returned: success
|
||||
type: bool
|
||||
sample: true
|
||||
version_added: 2.24.0
|
||||
|
||||
msg:
|
||||
description:
|
||||
- Information on the reason for renewal.
|
||||
|
@ -139,6 +165,8 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.acme import
|
|||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.io import read_file
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import compute_cert_id
|
||||
|
||||
|
||||
|
@ -152,6 +180,7 @@ def main():
|
|||
remaining_days=dict(type='int'),
|
||||
remaining_percentage=dict(type='float'),
|
||||
now=dict(type='str'),
|
||||
treat_parsing_error_as_non_existing=dict(type='bool', default=False),
|
||||
)
|
||||
argument_spec.update(
|
||||
mutually_exclusive=(
|
||||
|
@ -164,6 +193,8 @@ def main():
|
|||
result = dict(
|
||||
changed=False,
|
||||
msg='The certificate is still valid and no condition was reached',
|
||||
exists=False,
|
||||
parsable=False,
|
||||
supports_ari=False,
|
||||
)
|
||||
|
||||
|
@ -175,14 +206,28 @@ def main():
|
|||
if not module.params['certificate_path'] and not module.params['certificate_content']:
|
||||
complete(True, msg='No certificate was specified')
|
||||
|
||||
if module.params['certificate_path'] is not None and not os.path.exists(module.params['certificate_path']):
|
||||
if module.params['certificate_path'] is not None:
|
||||
if not os.path.exists(module.params['certificate_path']):
|
||||
complete(True, msg='The certificate file does not exist')
|
||||
if module.params['treat_parsing_error_as_non_existing']:
|
||||
try:
|
||||
read_file(module.params['certificate_path'])
|
||||
except ModuleFailException as e:
|
||||
e.do_fail(module)
|
||||
|
||||
result['exists'] = True
|
||||
try:
|
||||
cert_info = backend.get_cert_information(
|
||||
cert_filename=module.params['certificate_path'],
|
||||
cert_content=module.params['certificate_content'],
|
||||
)
|
||||
except ModuleFailException as e:
|
||||
if module.params['treat_parsing_error_as_non_existing']:
|
||||
complete(True, msg='Certificate cannot be parsed: {0}'.format(e.msg))
|
||||
e.do_fail(module)
|
||||
|
||||
result['parsable'] = True
|
||||
try:
|
||||
cert_id = compute_cert_id(backend, cert_info=cert_info, none_if_required_information_is_missing=True)
|
||||
if cert_id is not None:
|
||||
result['cert_id'] = cert_id
|
||||
|
|
|
@ -49,27 +49,25 @@
|
|||
slurp:
|
||||
src: '{{ remote_tmp_dir }}/cert-1.pem'
|
||||
register: slurp_cert_1
|
||||
- name: Obtain certificate information (1/9)
|
||||
- name: Obtain certificate information (1/11)
|
||||
acme_certificate_renewal_info:
|
||||
select_crypto_backend: "{{ select_crypto_backend }}"
|
||||
certificate_path: "{{ remote_tmp_dir }}/cert-1.pem"
|
||||
acme_version: 2
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: false
|
||||
# Certificate is valid for ~1826 days
|
||||
register: cert_1_renewal_1
|
||||
- name: Obtain certificate information (2/9)
|
||||
- name: Obtain certificate information (2/11)
|
||||
acme_certificate_renewal_info:
|
||||
select_crypto_backend: "{{ select_crypto_backend }}"
|
||||
certificate_path: "{{ remote_tmp_dir }}/cert-1.pem"
|
||||
acme_version: 2
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: false
|
||||
# Certificate is valid for ~1826 days
|
||||
remaining_days: 1000
|
||||
remaining_percentage: 0.5
|
||||
register: cert_1_renewal_2
|
||||
- name: Obtain certificate information (3/9)
|
||||
- name: Obtain certificate information (3/11)
|
||||
acme_certificate_renewal_info:
|
||||
select_crypto_backend: "{{ select_crypto_backend }}"
|
||||
certificate_content: "{{ slurp_cert_1.content | b64decode }}"
|
||||
|
@ -77,9 +75,8 @@
|
|||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: false
|
||||
now: +1800d
|
||||
# Certificate is valid for ~26 days
|
||||
register: cert_1_renewal_3
|
||||
- name: Obtain certificate information (4/9)
|
||||
- name: Obtain certificate information (4/11)
|
||||
acme_certificate_renewal_info:
|
||||
select_crypto_backend: "{{ select_crypto_backend }}"
|
||||
certificate_path: "{{ remote_tmp_dir }}/cert-1.pem"
|
||||
|
@ -87,11 +84,10 @@
|
|||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: false
|
||||
now: +1800d
|
||||
# Certificate is valid for ~26 days
|
||||
remaining_days: 30
|
||||
remaining_percentage: 0.1
|
||||
register: cert_1_renewal_4
|
||||
- name: Obtain certificate information (5/9)
|
||||
- name: Obtain certificate information (5/11)
|
||||
acme_certificate_renewal_info:
|
||||
select_crypto_backend: "{{ select_crypto_backend }}"
|
||||
certificate_path: "{{ remote_tmp_dir }}/cert-1.pem"
|
||||
|
@ -99,11 +95,10 @@
|
|||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: false
|
||||
now: +1800d
|
||||
# Certificate is valid for ~26 days
|
||||
remaining_days: 30
|
||||
remaining_percentage: 0.01
|
||||
register: cert_1_renewal_5
|
||||
- name: Obtain certificate information (6/9)
|
||||
- name: Obtain certificate information (6/11)
|
||||
acme_certificate_renewal_info:
|
||||
select_crypto_backend: "{{ select_crypto_backend }}"
|
||||
certificate_path: "{{ remote_tmp_dir }}/cert-1.pem"
|
||||
|
@ -111,11 +106,10 @@
|
|||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: false
|
||||
now: +1800d
|
||||
# Certificate is valid for ~26 days
|
||||
remaining_days: 10
|
||||
remaining_percentage: 0.03
|
||||
register: cert_1_renewal_6
|
||||
- name: Obtain certificate information (7/9)
|
||||
- name: Obtain certificate information (7/11)
|
||||
acme_certificate_renewal_info:
|
||||
select_crypto_backend: "{{ select_crypto_backend }}"
|
||||
certificate_path: "{{ remote_tmp_dir }}/cert-1.pem"
|
||||
|
@ -123,23 +117,44 @@
|
|||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: false
|
||||
now: +1830d
|
||||
# Certificate is no longer valid
|
||||
register: cert_1_renewal_7
|
||||
- name: Obtain certificate information (8/9)
|
||||
- name: Obtain certificate information (8/11)
|
||||
acme_certificate_renewal_info:
|
||||
select_crypto_backend: "{{ select_crypto_backend }}"
|
||||
acme_version: 2
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: false
|
||||
now: +1830d
|
||||
# Certificate is no longer valid
|
||||
register: cert_1_renewal_8
|
||||
- name: Obtain certificate information (9/9)
|
||||
- name: Obtain certificate information (9/11)
|
||||
acme_certificate_renewal_info:
|
||||
select_crypto_backend: "{{ select_crypto_backend }}"
|
||||
certificate_path: "{{ remote_tmp_dir }}/cert-does-not-exist.pem"
|
||||
acme_version: 2
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: false
|
||||
# Certificate is no longer valid
|
||||
register: cert_1_renewal_9
|
||||
- name: Create broken file
|
||||
copy:
|
||||
dest: "{{ remote_tmp_dir }}/cert-is-broken.pem"
|
||||
content: |
|
||||
--- THIS IS NOT A CERT ---
|
||||
- name: Obtain certificate information (10/11)
|
||||
acme_certificate_renewal_info:
|
||||
treat_parsing_error_as_non_existing: false
|
||||
select_crypto_backend: "{{ select_crypto_backend }}"
|
||||
certificate_path: "{{ remote_tmp_dir }}/cert-is-broken.pem"
|
||||
acme_version: 2
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: false
|
||||
register: cert_1_renewal_10
|
||||
ignore_errors: true
|
||||
- name: Obtain certificate information (11/11)
|
||||
acme_certificate_renewal_info:
|
||||
treat_parsing_error_as_non_existing: true
|
||||
select_crypto_backend: "{{ select_crypto_backend }}"
|
||||
certificate_path: "{{ remote_tmp_dir }}/cert-is-broken.pem"
|
||||
acme_version: 2
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: false
|
||||
register: cert_1_renewal_11
|
||||
|
|
|
@ -10,38 +10,67 @@
|
|||
- cert_1_renewal_1.msg == 'The certificate is still valid and no condition was reached'
|
||||
- cert_1_renewal_1.supports_ari == supports_ari
|
||||
- cert_1_renewal_1.cert_id is string or not can_have_cert_id
|
||||
- cert_1_renewal_1.exists == true
|
||||
- cert_1_renewal_1.parsable == true
|
||||
- cert_1_renewal_2.should_renew == false
|
||||
- cert_1_renewal_2.msg == 'The certificate is still valid and no condition was reached'
|
||||
- cert_1_renewal_2.supports_ari == supports_ari
|
||||
- cert_1_renewal_2.cert_id is string or not can_have_cert_id
|
||||
- cert_1_renewal_2.exists == true
|
||||
- cert_1_renewal_2.parsable == true
|
||||
- cert_1_renewal_3.should_renew == false
|
||||
- cert_1_renewal_3.msg == 'The certificate is still valid and no condition was reached'
|
||||
- cert_1_renewal_3.supports_ari == supports_ari
|
||||
- cert_1_renewal_3.cert_id is string or not can_have_cert_id
|
||||
- cert_1_renewal_3.exists == true
|
||||
- cert_1_renewal_3.parsable == true
|
||||
- cert_1_renewal_4.should_renew == true
|
||||
- cert_1_renewal_4.msg == 'The certificate expires in 25 days'
|
||||
- cert_1_renewal_4.supports_ari == supports_ari
|
||||
- cert_1_renewal_4.cert_id is string or not can_have_cert_id
|
||||
- cert_1_renewal_4.exists == true
|
||||
- cert_1_renewal_4.parsable == true
|
||||
- cert_1_renewal_5.should_renew == true
|
||||
- cert_1_renewal_5.msg == 'The certificate expires in 25 days'
|
||||
- cert_1_renewal_5.supports_ari == supports_ari
|
||||
- cert_1_renewal_5.cert_id is string or not can_have_cert_id
|
||||
- cert_1_renewal_5.exists == true
|
||||
- cert_1_renewal_5.parsable == true
|
||||
- cert_1_renewal_6.should_renew == true
|
||||
- cert_1_renewal_6.msg.startswith("The remaining percentage 3.0% of the certificate's lifespan was reached on ")
|
||||
- cert_1_renewal_6.supports_ari == supports_ari
|
||||
- cert_1_renewal_6.cert_id is string or not can_have_cert_id
|
||||
- cert_1_renewal_6.exists == true
|
||||
- cert_1_renewal_6.parsable == true
|
||||
- cert_1_renewal_7.should_renew == true
|
||||
- cert_1_renewal_7.msg == 'The certificate has already expired'
|
||||
- cert_1_renewal_7.supports_ari == false
|
||||
- cert_1_renewal_7.cert_id is string or not can_have_cert_id
|
||||
- cert_1_renewal_7.exists == true
|
||||
- cert_1_renewal_7.parsable == true
|
||||
- cert_1_renewal_8.should_renew == true
|
||||
- cert_1_renewal_8.msg == 'No certificate was specified'
|
||||
- cert_1_renewal_8.supports_ari == false
|
||||
- cert_1_renewal_8.cert_id is not defined
|
||||
- cert_1_renewal_8.exists == false
|
||||
- cert_1_renewal_8.parsable == false
|
||||
- cert_1_renewal_9.should_renew == true
|
||||
- cert_1_renewal_9.msg == 'The certificate file does not exist'
|
||||
- cert_1_renewal_9.supports_ari == false
|
||||
- cert_1_renewal_9.cert_id is not defined
|
||||
- cert_1_renewal_9.exists == false
|
||||
- cert_1_renewal_9.parsable == false
|
||||
- cert_1_renewal_10 is failed
|
||||
- cert_1_renewal_10.msg.startswith('Error while running ') or
|
||||
cert_1_renewal_10.msg.startswith('Cannot parse certificate ')
|
||||
- cert_1_renewal_11.should_renew == true
|
||||
- >-
|
||||
cert_1_renewal_11.msg.startswith('Certificate cannot be parsed: Error while running ') or
|
||||
cert_1_renewal_11.msg.startswith('Certificate cannot be parsed: Cannot parse certificate ')
|
||||
- cert_1_renewal_11.supports_ari == false
|
||||
- cert_1_renewal_11.cert_id is not defined
|
||||
- cert_1_renewal_11.exists == true
|
||||
- cert_1_renewal_11.parsable == false
|
||||
vars:
|
||||
can_have_cert_id: cert_1_info.authority_key_identifier is string
|
||||
supports_ari: false
|
||||
|
|
Loading…
Reference in New Issue