From 553ab45f46cf5af6bed5867e447e4c9878cf8e68 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sat, 4 May 2024 23:38:57 +0200 Subject: [PATCH] ACME: improve acme_certificate docs, include cert_id in acme_certificate_renewal_info return value (#747) * Use community.dns.quote_txt filter instead of regex replace to quote TXT entry value. * Fix documentation of acme_certificate's challenge_data return value. * Also return cert_id from acme_certificate_renewal_info module. * The cert ID cannot be computed if the certificate has no AKI. This happens with older Pebble versions, which are used when testing against older ansible-core/-base/Ansible versions. * Fix AKI extraction for older OpenSSL versions. --- plugins/module_utils/acme/acme.py | 4 +- .../module_utils/acme/backend_openssl_cli.py | 12 +-- plugins/module_utils/acme/utils.py | 2 +- plugins/modules/acme_certificate.py | 80 ++++++++------ .../modules/acme_certificate_renewal_info.py | 100 ++++++++---------- .../tasks/impl.yml | 43 ++++++-- .../tests/validate.yml | 19 ++++ .../plugins/module_utils/acme/backend_data.py | 37 ++++--- .../module_utils/acme/fixtures/cert_2-b.txt | 57 ++++++++++ .../acme/fixtures/cert_2-b.txt.license | 3 + .../module_utils/acme/fixtures/cert_2.pem | 19 ++++ .../acme/fixtures/cert_2.pem.license | 3 + .../module_utils/acme/fixtures/cert_2.txt | 56 ++++++++++ .../acme/fixtures/cert_2.txt.license | 3 + 14 files changed, 323 insertions(+), 115 deletions(-) create mode 100644 tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt create mode 100644 tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt.license create mode 100644 tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem create mode 100644 tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem.license create mode 100644 tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt create mode 100644 tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt.license diff --git a/plugins/module_utils/acme/acme.py b/plugins/module_utils/acme/acme.py index ff91da7c4..044f85080 100644 --- a/plugins/module_utils/acme/acme.py +++ b/plugins/module_utils/acme/acme.py @@ -390,6 +390,7 @@ def get_request(self, uri, parse_json_result=True, headers=None, get_only=False, def get_renewal_info( self, + cert_id=None, cert_info=None, cert_filename=None, cert_content=None, @@ -399,7 +400,8 @@ def get_renewal_info( if not self.directory.has_renewal_info_endpoint(): raise ModuleFailException('The ACME endpoint does not support ACME Renewal Information retrieval') - cert_id = compute_cert_id(self.backend, cert_info=cert_info, cert_filename=cert_filename, cert_content=cert_content) + if cert_id is None: + cert_id = compute_cert_id(self.backend, cert_info=cert_info, cert_filename=cert_filename, cert_content=cert_content) url = '{base}{cert_id}'.format(base=self.directory.directory['renewalInfo'], cert_id=cert_id) data, info = self.get_request(url, parse_json_result=True, fail_on_error=True, get_only=True) diff --git a/plugins/module_utils/acme/backend_openssl_cli.py b/plugins/module_utils/acme/backend_openssl_cli.py index b3d1f73e1..9aab187ac 100644 --- a/plugins/module_utils/acme/backend_openssl_cli.py +++ b/plugins/module_utils/acme/backend_openssl_cli.py @@ -56,12 +56,12 @@ def _decode_octets(octets_text): return binascii.unhexlify(re.sub(r"(\s|:)", "", octets_text).encode("utf-8")) -def _extract_octets(out_text, name, required=True): - match = re.search( - r"\s+%s:\s*\n\s+([A-Fa-f0-9]{2}(?::[A-Fa-f0-9]{2})*)\s*\n" % name, - out_text, - re.MULTILINE | re.DOTALL, +def _extract_octets(out_text, name, required=True, potential_prefixes=None): + regexp = r"\s+%s:\s*\n\s+%s([A-Fa-f0-9]{2}(?::[A-Fa-f0-9]{2})*)\s*\n" % ( + name, + ('(?:%s)' % '|'.join(re.escape(pp) for pp in potential_prefixes)) if potential_prefixes else '', ) + match = re.search(regexp, out_text, re.MULTILINE | re.DOTALL) if match is not None: return _decode_octets(match.group(1)) if not required: @@ -379,7 +379,7 @@ def get_cert_information(self, cert_filename=None, cert_content=None): serial = convert_bytes_to_int(_extract_octets(out_text, 'Serial Number', required=True)) ski = _extract_octets(out_text, 'X509v3 Subject Key Identifier', required=False) - aki = _extract_octets(out_text, 'X509v3 Authority Key Identifier', required=False) + aki = _extract_octets(out_text, 'X509v3 Authority Key Identifier', required=False, potential_prefixes=['keyid:', '']) return CertificateInformation( not_valid_after=not_after, diff --git a/plugins/module_utils/acme/utils.py b/plugins/module_utils/acme/utils.py index e0e9ef7f1..9935c2b42 100644 --- a/plugins/module_utils/acme/utils.py +++ b/plugins/module_utils/acme/utils.py @@ -109,7 +109,7 @@ def compute_cert_id(backend, cert_info=None, cert_filename=None, cert_content=No # Convert Authority Key Identifier to string if cert_info.authority_key_identifier is None: - raise ModuleFailException('Module has no Authority Key Identifier extension') + raise ModuleFailException('Certificate has no Authority Key Identifier extension') aki = to_native(base64.urlsafe_b64encode(cert_info.authority_key_identifier)).replace('=', '') # Convert serial number to string diff --git a/plugins/modules/acme_certificate.py b/plugins/modules/acme_certificate.py index 94cb9bcd8..118bc5f5d 100644 --- a/plugins/modules/acme_certificate.py +++ b/plugins/modules/acme_certificate.py @@ -404,7 +404,7 @@ # state: present # wait: true # # Note: route53 requires TXT entries to be enclosed in quotes -# value: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].resource_value | regex_replace('^(.*)$', '\"\\1\"') }}" +# value: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].resource_value | community.dns.quote_txt(always_quote=true) }}" # when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge.challenge_data # # Alternative way: @@ -419,7 +419,7 @@ # wait: true # # Note: item.value is a list of TXT entries, and route53 # # requires every entry to be enclosed in quotes -# value: "{{ item.value | map('regex_replace', '^(.*)$', '\"\\1\"' ) | list }}" +# value: "{{ item.value | map('community.dns.quote_txt', always_quote=true) | list }}" # loop: "{{ sample_com_challenge.challenge_data_dns | dict2items }}" # when: sample_com_challenge is changed @@ -475,39 +475,55 @@ - Per identifier / challenge type challenge data. - Since Ansible 2.8.5, only challenges which are not yet valid are returned. returned: changed - type: list - elements: dict + type: dict contains: - resource: - description: The challenge resource that must be created for validation. - returned: changed - type: str - sample: .well-known/acme-challenge/evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA - resource_original: + identifier: description: - - The original challenge resource including type identifier for V(tls-alpn-01) - challenges. - returned: changed and O(challenge) is V(tls-alpn-01) - type: str - sample: DNS:example.com - resource_value: - description: - - The value the resource has to produce for the validation. - - For V(http-01) and V(dns-01) challenges, the value can be used as-is. - - "For V(tls-alpn-01) challenges, note that this return value contains a - Base64 encoded version of the correct binary blob which has to be put - into the acmeValidation x509 extension; see - U(https://www.rfc-editor.org/rfc/rfc8737.html#section-3) - for details. To do this, you might need the P(ansible.builtin.b64decode#filter) Jinja filter - to extract the binary blob from this return value." + - For every identifier, provides a dictionary of challenge types mapping to challenge data. + - The keys in this dictionary the identifiers. C(identifier) is a placeholder used in the documentation. + - Note that the keys are not valid Jinja2 identifiers. returned: changed - type: str - sample: IlirfxKKXA...17Dt3juxGJ-PCt92wr-oA - record: - description: The full DNS record's name for the challenge. - returned: changed and challenge is V(dns-01) - type: str - sample: _acme-challenge.example.com + type: dict + contains: + challenge-type: + description: + - Data for every challenge type. + - The keys in this dictionary the challenge types. C(challenge-type) is a placeholder used in the documentation. + Possible keys are V(http-01), V(dns-01), and V(tls-alpn-01). + - Note that the keys are not valid Jinja2 identifiers. + returned: changed + type: dict + contains: + resource: + description: The challenge resource that must be created for validation. + returned: changed + type: str + sample: .well-known/acme-challenge/evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA + resource_original: + description: + - The original challenge resource including type identifier for V(tls-alpn-01) + challenges. + returned: changed and O(challenge) is V(tls-alpn-01) + type: str + sample: DNS:example.com + resource_value: + description: + - The value the resource has to produce for the validation. + - For V(http-01) and V(dns-01) challenges, the value can be used as-is. + - "For V(tls-alpn-01) challenges, note that this return value contains a + Base64 encoded version of the correct binary blob which has to be put + into the acmeValidation x509 extension; see + U(https://www.rfc-editor.org/rfc/rfc8737.html#section-3) + for details. To do this, you might need the P(ansible.builtin.b64decode#filter) Jinja filter + to extract the binary blob from this return value." + returned: changed + type: str + sample: IlirfxKKXA...17Dt3juxGJ-PCt92wr-oA + record: + description: The full DNS record's name for the challenge. + returned: changed and challenge is V(dns-01) + type: str + sample: _acme-challenge.example.com challenge_data_dns: description: - List of TXT values per DNS record, in case challenge is V(dns-01). diff --git a/plugins/modules/acme_certificate_renewal_info.py b/plugins/modules/acme_certificate_renewal_info.py index 4279c75c2..028f8c9ec 100644 --- a/plugins/modules/acme_certificate_renewal_info.py +++ b/plugins/modules/acme_certificate_renewal_info.py @@ -119,6 +119,13 @@ returned: success type: bool sample: true + +cert_id: + description: + - The certificate ID according to the L(ARI specification, https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-4.1). + returned: success, the certificate exists, and has an Authority Key Identifier X.509 extension + type: str + sample: aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE ''' import os @@ -134,6 +141,8 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException +from ansible_collections.community.crypto.plugins.module_utils.acme.utils import compute_cert_id + def main(): argument_spec = get_default_argspec(with_account=False) @@ -155,109 +164,86 @@ def main(): ) backend = create_backend(module, True) + result = dict( + changed=False, + msg='The certificate is still valid and no condition was reached', + supports_ari=False, + ) + + def complete(should_renew, **kwargs): + result['should_renew'] = should_renew + result.update(kwargs) + module.exit_json(**result) + if not module.params['certificate_path'] and not module.params['certificate_content']: - module.exit_json( - changed=False, - should_renew=True, - msg='No certificate was specified', - supports_ari=False, - ) + complete(True, msg='No certificate was specified') if module.params['certificate_path'] is not None and not os.path.exists(module.params['certificate_path']): - module.exit_json( - changed=False, - should_renew=True, - msg='The certificate file does not exist', - supports_ari=False, - ) + complete(True, msg='The certificate file does not exist') try: cert_info = backend.get_cert_information( cert_filename=module.params['certificate_path'], cert_content=module.params['certificate_content'], ) + cert_id = None + if cert_info.authority_key_identifier is not None: + cert_id = compute_cert_id(backend, cert_info=cert_info) + if cert_id is not None: + result['cert_id'] = cert_id if module.params['now']: now = backend.parse_module_parameter(module.params['now'], 'now') else: now = backend.get_now() - no_renewal_msg = 'The certificate is still valid and no condition was reached' - renewal_ari = False - if now >= cert_info.not_valid_after: - module.exit_json( - changed=False, - should_renew=True, - msg='The certificate already expired', - supports_ari=False, - ) + complete(True, msg='The certificate has already expired') client = ACMEClient(module, backend) - if client.directory.has_renewal_info_endpoint(): - renewal_info = client.get_renewal_info(cert_info=cert_info) + if cert_id is not None and module.params['use_ari'] and client.directory.has_renewal_info_endpoint(): + renewal_info = client.get_renewal_info(cert_id=cert_id) window_start = backend.parse_acme_timestamp(renewal_info['suggestedWindow']['start']) window_end = backend.parse_acme_timestamp(renewal_info['suggestedWindow']['end']) msg_append = '' if 'explanationURL' in renewal_info: msg_append = '. Information on renewal interval: {0}'.format(renewal_info['explanationURL']) - renewal_ari = True + result['supports_ari'] = True if now > window_end: - module.exit_json( - changed=False, - should_renew=True, - msg='The suggested renewal interval provided by ARI is in the past{0}'.format(msg_append), - supports_ari=True, - ) + complete(True, msg='The suggested renewal interval provided by ARI is in the past{0}'.format(msg_append)) if module.params['ari_algorithm'] == 'start': if now > window_start: - module.exit_json( - changed=False, - should_renew=True, - msg='The suggested renewal interval provided by ARI has begun{0}'.format(msg_append), - supports_ari=True, - ) + complete(True, msg='The suggested renewal interval provided by ARI has begun{0}'.format(msg_append)) else: random_time = backend.interpolate_timestamp(window_start, window_end, random.random()) if now > random_time: - module.exit_json( - changed=False, - should_renew=True, - msg='The picked random renewal time {0} in sugested renewal internal provided by ARI is in the past{1}'.format(random_time, msg_append), - supports_ari=True, + complete( + True, + msg='The picked random renewal time {0} in sugested renewal internal provided by ARI is in the past{1}'.format( + random_time, + msg_append, + ), ) # TODO check remaining_days if module.params['remaining_days'] is not None: remaining_days = (cert_info.not_valid_after - now).days if remaining_days < module.params['remaining_days']: - module.exit_json( - changed=False, - should_renew=True, - msg='The certificate expires in {0} days'.format(remaining_days), - supports_ari=False, - ) + complete(True, msg='The certificate expires in {0} days'.format(remaining_days)) # TODO check remaining_percentage if module.params['remaining_percentage'] is not None: timestamp = backend.interpolate_timestamp(cert_info.not_valid_before, cert_info.not_valid_after, 1 - module.params['remaining_percentage']) if timestamp < now: - module.exit_json( - changed=False, - should_renew=True, + complete( + True, msg="The remaining percentage {0}% of the certificate's lifespan was reached on {1}".format( module.params['remaining_percentage'] * 100, timestamp, ), - supports_ari=False, ) - module.exit_json( - changed=False, - should_renew=False, - msg=no_renewal_msg, - supports_ari=renewal_ari, - ) + complete(False) except ModuleFailException as e: e.do_fail(module) diff --git a/tests/integration/targets/acme_certificate_renewal_info/tasks/impl.yml b/tests/integration/targets/acme_certificate_renewal_info/tasks/impl.yml index 2135fd490..b30808ed5 100644 --- a/tests/integration/targets/acme_certificate_renewal_info/tasks/impl.yml +++ b/tests/integration/targets/acme_certificate_renewal_info/tasks/impl.yml @@ -38,6 +38,9 @@ terms_agreed: true account_email: "example@example.org" ## OBTAIN CERTIFICATE INFOS ################################################################### +- name: Dump OpenSSL x509 info + command: + cmd: openssl x509 -in {{ remote_tmp_dir }}/cert-1.pem -noout -text - name: Obtain certificate information x509_certificate_info: path: "{{ remote_tmp_dir }}/cert-1.pem" @@ -46,7 +49,7 @@ slurp: src: '{{ remote_tmp_dir }}/cert-1.pem' register: slurp_cert_1 -- name: Obtain certificate information (1/6) +- name: Obtain certificate information (1/9) acme_certificate_renewal_info: select_crypto_backend: "{{ select_crypto_backend }}" certificate_path: "{{ remote_tmp_dir }}/cert-1.pem" @@ -55,7 +58,7 @@ validate_certs: false # Certificate is valid for ~1826 days register: cert_1_renewal_1 -- name: Obtain certificate information (2/6) +- name: Obtain certificate information (2/9) acme_certificate_renewal_info: select_crypto_backend: "{{ select_crypto_backend }}" certificate_path: "{{ remote_tmp_dir }}/cert-1.pem" @@ -66,7 +69,7 @@ remaining_days: 1000 remaining_percentage: 0.5 register: cert_1_renewal_2 -- name: Obtain certificate information (3/6) +- name: Obtain certificate information (3/9) acme_certificate_renewal_info: select_crypto_backend: "{{ select_crypto_backend }}" certificate_content: "{{ slurp_cert_1.content | b64decode }}" @@ -76,7 +79,7 @@ now: +1800d # Certificate is valid for ~26 days register: cert_1_renewal_3 -- name: Obtain certificate information (4/6) +- name: Obtain certificate information (4/9) acme_certificate_renewal_info: select_crypto_backend: "{{ select_crypto_backend }}" certificate_path: "{{ remote_tmp_dir }}/cert-1.pem" @@ -88,7 +91,7 @@ remaining_days: 30 remaining_percentage: 0.1 register: cert_1_renewal_4 -- name: Obtain certificate information (5/6) +- name: Obtain certificate information (5/9) acme_certificate_renewal_info: select_crypto_backend: "{{ select_crypto_backend }}" certificate_path: "{{ remote_tmp_dir }}/cert-1.pem" @@ -100,7 +103,7 @@ remaining_days: 30 remaining_percentage: 0.01 register: cert_1_renewal_5 -- name: Obtain certificate information (6/6) +- name: Obtain certificate information (6/9) acme_certificate_renewal_info: select_crypto_backend: "{{ select_crypto_backend }}" certificate_path: "{{ remote_tmp_dir }}/cert-1.pem" @@ -112,3 +115,31 @@ remaining_days: 10 remaining_percentage: 0.03 register: cert_1_renewal_6 +- name: Obtain certificate information (7/9) + 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 + now: +1830d + # Certificate is no longer valid + register: cert_1_renewal_7 +- name: Obtain certificate information (8/9) + 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) + 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 diff --git a/tests/integration/targets/acme_certificate_renewal_info/tests/validate.yml b/tests/integration/targets/acme_certificate_renewal_info/tests/validate.yml index ef3a821d7..116e524c4 100644 --- a/tests/integration/targets/acme_certificate_renewal_info/tests/validate.yml +++ b/tests/integration/targets/acme_certificate_renewal_info/tests/validate.yml @@ -9,20 +9,39 @@ - cert_1_renewal_1.should_renew == false - 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_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_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_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_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_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_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_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_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 vars: + can_have_cert_id: cert_1_info.authority_key_identifier is string supports_ari: false diff --git a/tests/unit/plugins/module_utils/acme/backend_data.py b/tests/unit/plugins/module_utils/acme/backend_data.py index d508f006f..c4aa09a6a 100644 --- a/tests/unit/plugins/module_utils/acme/backend_data.py +++ b/tests/unit/plugins/module_utils/acme/backend_data.py @@ -81,9 +81,12 @@ def load_fixture(name): TEST_CERT = load_fixture("cert_1.pem") +TEST_CERT_2 = load_fixture("cert_2.pem") -TEST_CERT_OPENSSL_OUTPUT = load_fixture("cert_1.txt") +TEST_CERT_OPENSSL_OUTPUT = load_fixture("cert_1.txt") # OpenSSL 3.3.0 output +TEST_CERT_OPENSSL_OUTPUT_2 = load_fixture("cert_2.txt") # OpenSSL 3.3.0 output +TEST_CERT_OPENSSL_OUTPUT_2B = load_fixture("cert_2-b.txt") # OpenSSL 1.1.1f output TEST_CERT_DAYS = [ @@ -93,18 +96,28 @@ def load_fixture(name): ] +TEST_CERT_INFO = CertificateInformation( + not_valid_after=datetime.datetime(2018, 11, 26, 15, 28, 24), + not_valid_before=datetime.datetime(2018, 11, 25, 15, 28, 23), + serial_number=1, + subject_key_identifier=b'\x98\xD2\xFD\x3C\xCC\xCD\x69\x45\xFB\xE2\x8C\x30\x2C\x54\x62\x18\x34\xB7\x07\x73', + authority_key_identifier=None, +) + + +TEST_CERT_INFO_2 = CertificateInformation( + not_valid_before=datetime.datetime(2024, 5, 4, 20, 42, 21), + not_valid_after=datetime.datetime(2029, 5, 4, 20, 42, 20), + serial_number=4218235397573492796, + subject_key_identifier=b'\x17\xE5\x83\x22\x14\xEF\x74\xD3\xBE\x7E\x30\x76\x56\x1F\x51\x74\x65\x1F\xE9\xF0', + authority_key_identifier=b'\x13\xC3\x4C\x3E\x59\x45\xDD\xE3\x63\x51\xA3\x46\x80\xC4\x08\xC7\x14\xC0\x64\x4E', +) + + TEST_CERT_INFO = [ - ( - TEST_CERT, - CertificateInformation( - not_valid_after=datetime.datetime(2018, 11, 26, 15, 28, 24), - not_valid_before=datetime.datetime(2018, 11, 25, 15, 28, 23), - serial_number=1, - subject_key_identifier=b'\x98\xD2\xFD\x3C\xCC\xCD\x69\x45\xFB\xE2\x8C\x30\x2C\x54\x62\x18\x34\xB7\x07\x73', - authority_key_identifier=None, - ), - TEST_CERT_OPENSSL_OUTPUT, - ), + (TEST_CERT, TEST_CERT_INFO, TEST_CERT_OPENSSL_OUTPUT), + (TEST_CERT_2, TEST_CERT_INFO_2, TEST_CERT_OPENSSL_OUTPUT_2), + (TEST_CERT_2, TEST_CERT_INFO_2, TEST_CERT_OPENSSL_OUTPUT_2B), ] diff --git a/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt b/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt new file mode 100644 index 000000000..78326443b --- /dev/null +++ b/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt @@ -0,0 +1,57 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4218235397573492796 (0x3a8a2ebeb358c03c) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN = Pebble Intermediate CA 734609 + Validity + Not Before: May 4 20:42:21 2024 GMT + Not After : May 4 20:42:20 2029 GMT + Subject: CN = example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (1024 bit) + Modulus: + 00:c1:43:a5:f9:ad:00:b7:bb:1b:73:27:00:b3:a2: + 4e:27:0d:ff:ae:64:3e:a0:7e:f9:28:56:48:47:21: + 9e:0f:d8:fb:69:b5:21:e8:98:84:60:6c:aa:73:b9: + 6e:d9:f6:19:ad:85:e0:c2:f6:80:d3:22:b8:5a:d6: + 3a:89:3e:2a:7a:fc:1d:bf:fc:69:20:e5:91:b8:34: + 52:26:c8:15:74:e1:36:0c:cd:ab:01:4a:ad:83:f5: + 0b:77:96:31:cf:1c:ea:6f:88:75:23:ac:51:a6:d8: + 77:43:1b:b3:44:93:2c:8d:05:25:fb:77:41:36:94: + 81:d5:ca:56:ff:b5:23:b2:a5 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 17:E5:83:22:14:EF:74:D3:BE:7E:30:76:56:1F:51:74:65:1F:E9:F0 + X509v3 Authority Key Identifier: + keyid:13:C3:4C:3E:59:45:DD:E3:63:51:A3:46:80:C4:08:C7:14:C0:64:4E + + Authority Information Access: + OCSP - URI:http://10.88.0.74:5000/ocsp + + X509v3 Subject Alternative Name: + DNS:example.com + Signature Algorithm: sha256WithRSAEncryption + 31:43:de:b6:48:f4:b8:30:46:25:65:e6:91:22:33:1b:d1:ba: + 3f:60:f8:c3:18:32:72:e9:f8:d1:88:11:5a:0a:86:dc:1d:6d: + a5:ea:58:cd:05:ea:cd:5e:40:86:c1:ae:d5:cd:2e:8a:ca:50: + ee:df:bd:cf:6c:d9:20:3b:4b:49:f8:d5:8a:e3:be:f3:dd:24: + b2:7f:3f:3b:bf:e6:8d:7a:f8:8f:4b:6e:25:60:80:33:6f:0f: + 53:b7:7d:94:2a:d2:4a:db:3a:2f:70:79:d7:bf:05:ed:df:10: + 61:e7:24:ac:b2:fc:03:bd:ad:8c:e1:f3:1d:cc:78:99:e3:22: + 59:bf:c5:92:57:95:92:56:35:fc:05:8b:26:10:c5:1b:87:17: + 64:0b:bd:33:a9:54:d5:c0:2b:43:56:1b:52:d3:4f:8b:6f:25: + 06:58:7f:6f:aa:27:35:05:d5:57:6d:83:a0:73:de:40:3f:67: + 1c:5a:92:c6:37:e6:8f:c7:b8:91:d7:50:b9:4d:d4:f2:92:1f: + 8b:93:0c:e2:b4:b8:d7:1d:8e:ce:6d:19:dc:8f:12:8e:c0:f2: + 92:3b:95:5a:8c:c8:69:0e:0b:f7:fa:1f:55:62:80:7c:e2:f6: + 41:3f:7d:69:36:9e:7c:90:7e:d7:3b:e6:a3:15:de:a4:7d:95: + 13:46:c6:1a diff --git a/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt.license b/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem b/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem new file mode 100644 index 000000000..92aecb621 --- /dev/null +++ b/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDDjCCAfagAwIBAgIIOoouvrNYwDwwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE +AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSA3MzQ2MDkwHhcNMjQwNTA0MjA0MjIx +WhcNMjkwNTA0MjA0MjIwWjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCBnzANBgkq +hkiG9w0BAQEFAAOBjQAwgYkCgYEAwUOl+a0At7sbcycAs6JOJw3/rmQ+oH75KFZI +RyGeD9j7abUh6JiEYGyqc7lu2fYZrYXgwvaA0yK4WtY6iT4qevwdv/xpIOWRuDRS +JsgVdOE2DM2rAUqtg/ULd5Yxzxzqb4h1I6xRpth3QxuzRJMsjQUl+3dBNpSB1cpW +/7UjsqUCAwEAAaOB0TCBzjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB +BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFBflgyIU73TT +vn4wdlYfUXRlH+nwMB8GA1UdIwQYMBaAFBPDTD5ZRd3jY1GjRoDECMcUwGROMDcG +CCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAYYbaHR0cDovLzEwLjg4LjAuNzQ6NTAw +MC9vY3NwMBYGA1UdEQQPMA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IB +AQAxQ962SPS4MEYlZeaRIjMb0bo/YPjDGDJy6fjRiBFaCobcHW2l6ljNBerNXkCG +wa7VzS6KylDu373PbNkgO0tJ+NWK477z3SSyfz87v+aNeviPS24lYIAzbw9Tt32U +KtJK2zovcHnXvwXt3xBh5ySssvwDva2M4fMdzHiZ4yJZv8WSV5WSVjX8BYsmEMUb +hxdkC70zqVTVwCtDVhtS00+LbyUGWH9vqic1BdVXbYOgc95AP2ccWpLGN+aPx7iR +11C5TdTykh+LkwzitLjXHY7ObRncjxKOwPKSO5VajMhpDgv3+h9VYoB84vZBP31p +Np58kH7XO+ajFd6kfZUTRsYa +-----END CERTIFICATE----- diff --git a/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem.license b/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt b/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt new file mode 100644 index 000000000..3cda74955 --- /dev/null +++ b/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt @@ -0,0 +1,56 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4218235397573492796 (0x3a8a2ebeb358c03c) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=Pebble Intermediate CA 734609 + Validity + Not Before: May 4 20:42:21 2024 GMT + Not After : May 4 20:42:20 2029 GMT + Subject: CN=example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:c1:43:a5:f9:ad:00:b7:bb:1b:73:27:00:b3:a2: + 4e:27:0d:ff:ae:64:3e:a0:7e:f9:28:56:48:47:21: + 9e:0f:d8:fb:69:b5:21:e8:98:84:60:6c:aa:73:b9: + 6e:d9:f6:19:ad:85:e0:c2:f6:80:d3:22:b8:5a:d6: + 3a:89:3e:2a:7a:fc:1d:bf:fc:69:20:e5:91:b8:34: + 52:26:c8:15:74:e1:36:0c:cd:ab:01:4a:ad:83:f5: + 0b:77:96:31:cf:1c:ea:6f:88:75:23:ac:51:a6:d8: + 77:43:1b:b3:44:93:2c:8d:05:25:fb:77:41:36:94: + 81:d5:ca:56:ff:b5:23:b2:a5 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 17:E5:83:22:14:EF:74:D3:BE:7E:30:76:56:1F:51:74:65:1F:E9:F0 + X509v3 Authority Key Identifier: + 13:C3:4C:3E:59:45:DD:E3:63:51:A3:46:80:C4:08:C7:14:C0:64:4E + Authority Information Access: + OCSP - URI:http://10.88.0.74:5000/ocsp + X509v3 Subject Alternative Name: + DNS:example.com + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 31:43:de:b6:48:f4:b8:30:46:25:65:e6:91:22:33:1b:d1:ba: + 3f:60:f8:c3:18:32:72:e9:f8:d1:88:11:5a:0a:86:dc:1d:6d: + a5:ea:58:cd:05:ea:cd:5e:40:86:c1:ae:d5:cd:2e:8a:ca:50: + ee:df:bd:cf:6c:d9:20:3b:4b:49:f8:d5:8a:e3:be:f3:dd:24: + b2:7f:3f:3b:bf:e6:8d:7a:f8:8f:4b:6e:25:60:80:33:6f:0f: + 53:b7:7d:94:2a:d2:4a:db:3a:2f:70:79:d7:bf:05:ed:df:10: + 61:e7:24:ac:b2:fc:03:bd:ad:8c:e1:f3:1d:cc:78:99:e3:22: + 59:bf:c5:92:57:95:92:56:35:fc:05:8b:26:10:c5:1b:87:17: + 64:0b:bd:33:a9:54:d5:c0:2b:43:56:1b:52:d3:4f:8b:6f:25: + 06:58:7f:6f:aa:27:35:05:d5:57:6d:83:a0:73:de:40:3f:67: + 1c:5a:92:c6:37:e6:8f:c7:b8:91:d7:50:b9:4d:d4:f2:92:1f: + 8b:93:0c:e2:b4:b8:d7:1d:8e:ce:6d:19:dc:8f:12:8e:c0:f2: + 92:3b:95:5a:8c:c8:69:0e:0b:f7:fa:1f:55:62:80:7c:e2:f6: + 41:3f:7d:69:36:9e:7c:90:7e:d7:3b:e6:a3:15:de:a4:7d:95: + 13:46:c6:1a diff --git a/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt.license b/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project