From 589e7c72ef4d28088f3d7b424fdf11cfce55bf78 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 31 Oct 2021 15:05:04 +0100 Subject: [PATCH] Allow to specify subject (for CSRs) and issuer (for CRLs) ordered (#316) * Allow to specify subject (for CSRs) and issuer (for CRLs) ordered. * Forgot import. * Apply suggestions from code review Co-authored-by: Ajpantuso * Apply suggestions from code review Co-authored-by: Ajpantuso * Fix typo. * Simplify error handling, reject empty values outright. * Document d497231e1cd2777ee7fe6bea856975d7b905101e. Co-authored-by: Ajpantuso --- changelogs/fragments/315-ordered-names.yml | 9 + plugins/doc_fragments/module_csr.py | 13 ++ .../module_utils/acme/backend_cryptography.py | 4 +- .../crypto/module_backends/csr.py | 25 ++- plugins/module_utils/crypto/support.py | 41 ++++- plugins/modules/acme_certificate.py | 9 +- plugins/modules/x509_crl.py | 38 +++- .../targets/openssl_csr/tasks/impl.yml | 166 ++++++++++++++---- .../targets/openssl_csr/tests/validate.yml | 20 +++ .../targets/x509_crl/tasks/impl.yml | 150 +++++++++++----- .../targets/x509_crl/tests/validate.yml | 24 ++- 11 files changed, 391 insertions(+), 108 deletions(-) create mode 100644 changelogs/fragments/315-ordered-names.yml diff --git a/changelogs/fragments/315-ordered-names.yml b/changelogs/fragments/315-ordered-names.yml new file mode 100644 index 00000000..f4416e07 --- /dev/null +++ b/changelogs/fragments/315-ordered-names.yml @@ -0,0 +1,9 @@ +minor_changes: + - "acme_certificate - the ``subject`` and ``issuer`` fields in in the ``select_chain`` entries are now more strictly validated (https://github.com/ansible-collections/community.crypto/pull/316)." + - "openssl_csr, openssl_csr_pipe - there is now stricter validation of the values of the ``subject`` option (https://github.com/ansible-collections/community.crypto/pull/316)." + - "openssl_csr, openssl_csr_pipe - provide a new ``subject_ordered`` option if the order of the components in the subject is of importance (https://github.com/ansible-collections/community.crypto/issues/291, https://github.com/ansible-collections/community.crypto/pull/316)." + - "x509_crl - there is now stricter validation of the values of the ``issuer`` option (https://github.com/ansible-collections/community.crypto/pull/316)." + - "x509_crl - provide a new ``issuer_ordered`` option if the order of the components in the issuer is of importance (https://github.com/ansible-collections/community.crypto/issues/291, https://github.com/ansible-collections/community.crypto/pull/316)." +breaking_changes: + - "openssl_csr, openssl_csr_pipe, x509_crl - the ``subject`` respectively ``issuer`` fields no longer ignore empty values, but instead fail when encountering them (https://github.com/ansible-collections/community.crypto/pull/316)." + - "x509_crl - for idempotency checks, the ``issuer`` order is ignored. If order is important, use the new ``issuer_ordered`` option (https://github.com/ansible-collections/community.crypto/pull/316)." diff --git a/plugins/doc_fragments/module_csr.py b/plugins/doc_fragments/module_csr.py index 2b749b91..0f8d57ac 100644 --- a/plugins/doc_fragments/module_csr.py +++ b/plugins/doc_fragments/module_csr.py @@ -52,7 +52,20 @@ options: description: - Key/value pairs that will be present in the subject name field of the certificate signing request. - If you need to specify more than one value with the same key, use a list as value. + - If the order of the components is important, use I(subject_ordered). + - Mutually exclusive with I(subject_ordered). type: dict + subject_ordered: + description: + - A list of dictionaries, where every dictionary must contain one key/value pair. This key/value pair + will be present in the subject name field of the certificate signing request. + - If you want to specify more than one value with the same key in a row, you can use a list as value. + - Mutually exclusive with I(subject), and any other subject field option, such as I(country_name), + I(state_or_province_name), I(locality_name), I(organization_name), I(organizational_unit_name), + I(common_name), or I(email_address). + type: list + elements: dict + version_added: 2.0.0 country_name: description: - The countryName field of the certificate signing request subject. diff --git a/plugins/module_utils/acme/backend_cryptography.py b/plugins/module_utils/acme/backend_cryptography.py index 6e66cb91..ed1cf9a4 100644 --- a/plugins/module_utils/acme/backend_cryptography.py +++ b/plugins/module_utils/acme/backend_cryptography.py @@ -118,11 +118,11 @@ class CryptographyChainMatcher(ChainMatcher): self.issuer = [] if criterium.subject: self.subject = [ - (cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.subject) + (cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.subject, 'subject') ] if criterium.issuer: self.issuer = [ - (cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.issuer) + (cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.issuer, 'issuer') ] self.subject_key_identifier = CryptographyChainMatcher._parse_key_identifier( criterium.subject_key_identifier, 'subject_key_identifier', criterium.index, module) diff --git a/plugins/module_utils/crypto/module_backends/csr.py b/plugins/module_utils/crypto/module_backends/csr.py index aae7193e..5acfea32 100644 --- a/plugins/module_utils/crypto/module_backends/csr.py +++ b/plugins/module_utils/crypto/module_backends/csr.py @@ -16,7 +16,7 @@ from distutils.version import LooseVersion from ansible.module_utils import six from ansible.module_utils.basic import missing_required_lib -from ansible.module_utils.common.text.converters import to_bytes, to_text +from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( OpenSSLObjectError, @@ -27,6 +27,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im load_privatekey, load_certificate_request, parse_name_field, + parse_ordered_name_field, select_message_digest, ) @@ -119,6 +120,7 @@ class CertificateSigningRequestBackend(object): if self.create_subject_key_identifier and self.subject_key_identifier is not None: module.fail_json(msg='subject_key_identifier cannot be specified if create_subject_key_identifier is true') + self.ordered_subject = False self.subject = [ ('C', module.params['country_name']), ('ST', module.params['state_or_province_name']), @@ -128,11 +130,19 @@ class CertificateSigningRequestBackend(object): ('CN', module.params['common_name']), ('emailAddress', module.params['email_address']), ] - - if module.params['subject']: - self.subject = self.subject + parse_name_field(module.params['subject']) self.subject = [(entry[0], entry[1]) for entry in self.subject if entry[1]] + try: + if module.params['subject']: + self.subject = self.subject + parse_name_field(module.params['subject'], 'subject') + if module.params['subject_ordered']: + if self.subject: + raise CertificateSigningRequestError('subject_ordered cannot be combined with any other subject field') + self.subject = parse_ordered_name_field(module.params['subject_ordered'], 'subject_ordered') + self.ordered_subject = True + except ValueError as exc: + raise CertificateSigningRequestError(to_native(exc)) + self.using_common_name_for_san = False if not self.subjectAltName and module.params['use_common_name_for_san']: for sub in self.subject: @@ -401,7 +411,10 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack def _check_subject(csr): subject = [(cryptography_name_to_oid(entry[0]), to_text(entry[1])) for entry in self.subject] current_subject = [(sub.oid, sub.value) for sub in csr.subject] - return set(subject) == set(current_subject) + if self.ordered_subject: + return subject == current_subject + else: + return set(subject) == set(current_subject) def _find_extension(extensions, exttype): return next( @@ -592,6 +605,7 @@ def get_csr_argument_spec(): privatekey_passphrase=dict(type='str', no_log=True), version=dict(type='int', default=1, choices=[1]), subject=dict(type='dict'), + subject_ordered=dict(type='list', elements='dict'), country_name=dict(type='str', aliases=['C', 'countryName']), state_or_province_name=dict(type='str', aliases=['ST', 'stateOrProvinceName']), locality_name=dict(type='str', aliases=['L', 'localityName']), @@ -645,6 +659,7 @@ def get_csr_argument_spec(): ], mutually_exclusive=[ ['privatekey_path', 'privatekey_content'], + ['subject', 'subject_ordered'], ], required_one_of=[ ['privatekey_path', 'privatekey_content'], diff --git a/plugins/module_utils/crypto/support.py b/plugins/module_utils/crypto/support.py index b6bc2fa8..5a265498 100644 --- a/plugins/module_utils/crypto/support.py +++ b/plugins/module_utils/crypto/support.py @@ -237,16 +237,43 @@ def load_certificate_request(path, content=None, backend='cryptography'): raise OpenSSLObjectError(exc) -def parse_name_field(input_dict): +def parse_name_field(input_dict, name_field_name=None): + """Take a dict with key: value or key: list_of_values mappings and return a list of tuples""" + error_str = '{key}' if name_field_name is None else '{key} in {name}' + + result = [] + for key, value in input_dict.items(): + if isinstance(value, list): + for entry in value: + if not isinstance(entry, six.string_types): + raise TypeError(('Values %s must be strings' % error_str).format(key=key, name=name_field_name)) + if not entry: + raise ValueError(('Values for %s must not be empty strings' % error_str).format(key=key)) + result.append((key, entry)) + elif isinstance(value, six.string_types): + if not value: + raise ValueError(('Value for %s must not be an empty string' % error_str).format(key=key)) + result.append((key, value)) + else: + raise TypeError(('Value for %s must be either a string or a list of strings' % error_str).format(key=key)) + return result + + +def parse_ordered_name_field(input_list, name_field_name): """Take a dict with key: value or key: list_of_values mappings and return a list of tuples""" result = [] - for key in input_dict: - if isinstance(input_dict[key], list): - for entry in input_dict[key]: - result.append((key, entry)) - else: - result.append((key, input_dict[key])) + for index, entry in enumerate(input_list): + if len(entry) != 1: + raise ValueError( + 'Entry #{index} in {name} must be a dictionary with exactly one key-value pair'.format( + name=name_field_name, index=index + 1)) + try: + result.extend(parse_name_field(entry, name_field_name=name_field_name)) + except (TypeError, ValueError) as exc: + raise ValueError( + 'Error while processing entry #{index} in {name}: {error}'.format( + name=name_field_name, index=index + 1, error=exc)) return result diff --git a/plugins/modules/acme_certificate.py b/plugins/modules/acme_certificate.py index fba85b53..de3e1374 100644 --- a/plugins/modules/acme_certificate.py +++ b/plugins/modules/acme_certificate.py @@ -580,9 +580,12 @@ class ACMECertificateClient(object): if self.module.params['select_chain']: for criterium_idx, criterium in enumerate(self.module.params['select_chain']): - self.select_chain_matcher.append( - self.client.backend.create_chain_matcher( - Criterium(criterium, index=criterium_idx))) + try: + self.select_chain_matcher.append( + self.client.backend.create_chain_matcher( + Criterium(criterium, index=criterium_idx))) + except ValueError as exc: + self.module.warn('Error while parsing criterium: {error}. Ignoring criterium.'.format(error=exc)) # Make sure account exists modify_account = module.params['modify_account'] diff --git a/plugins/modules/x509_crl.py b/plugins/modules/x509_crl.py index f9997d85..714db997 100644 --- a/plugins/modules/x509_crl.py +++ b/plugins/modules/x509_crl.py @@ -92,8 +92,21 @@ options: description: - Key/value pairs that will be present in the issuer name field of the CRL. - If you need to specify more than one value with the same key, use a list as value. - - Required if I(state) is C(present). + - If the order of the components is important, use I(issuer_ordered). + - One of I(issuer) and I(issuer_ordered) is required if I(state) is C(present). + - Mutually exclusive with I(issuer_ordered). type: dict + issuer_ordered: + description: + - A list of dictionaries, where every dictionary must contain one key/value pair. + This key/value pair will be present in the issuer name field of the CRL. + - If you want to specify more than one value with the same key in a row, you can + use a list as value. + - One of I(issuer) and I(issuer_ordered) is required if I(state) is C(present). + - Mutually exclusive with I(issuer). + type: list + elements: dict + version_added: 2.0.0 last_update: description: @@ -386,6 +399,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im load_privatekey, load_certificate, parse_name_field, + parse_ordered_name_field, get_relative_time_option, select_message_digest, ) @@ -462,8 +476,15 @@ class CRL(OpenSSLObject): self.privatekey_content = self.privatekey_content.encode('utf-8') self.privatekey_passphrase = module.params['privatekey_passphrase'] - self.issuer = parse_name_field(module.params['issuer']) - self.issuer = [(entry[0], entry[1]) for entry in self.issuer if entry[1]] + try: + if module.params['issuer_ordered']: + self.issuer_ordered = True + self.issuer = parse_ordered_name_field(module.params['issuer_ordered'], 'issuer_ordered') + else: + self.issuer_ordered = False + self.issuer = parse_name_field(module.params['issuer'], 'issuer') + except (TypeError, ValueError) as exc: + module.fail_json(msg=to_native(exc)) self.last_update = get_relative_time_option(module.params['last_update'], 'last_update') self.next_update = get_relative_time_option(module.params['next_update'], 'next_update') @@ -616,7 +637,11 @@ class CRL(OpenSSLObject): return False want_issuer = [(cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.issuer] - if want_issuer != [(sub.oid, sub.value) for sub in self.crl.issuer]: + is_issuer = [(sub.oid, sub.value) for sub in self.crl.issuer] + if not self.issuer_ordered: + want_issuer = set(want_issuer) + is_issuer = set(is_issuer) + if want_issuer != is_issuer: return False old_entries = [self._compress_entry(cryptography_decode_revoked_certificate(cert)) for cert in self.crl] @@ -782,6 +807,7 @@ def main(): privatekey_content=dict(type='str', no_log=True), privatekey_passphrase=dict(type='str', no_log=True), issuer=dict(type='dict'), + issuer_ordered=dict(type='list', elements='dict'), last_update=dict(type='str', default='+0s'), next_update=dict(type='str'), digest=dict(type='str', default='sha256'), @@ -815,10 +841,12 @@ def main(): ), required_if=[ ('state', 'present', ['privatekey_path', 'privatekey_content'], True), - ('state', 'present', ['issuer', 'next_update', 'revoked_certificates'], False), + ('state', 'present', ['issuer', 'issuer_ordered'], True), + ('state', 'present', ['next_update', 'revoked_certificates'], False), ], mutually_exclusive=( ['privatekey_path', 'privatekey_content'], + ['issuer', 'issuer_ordered'], ), supports_check_mode=True, add_file_common_args=True, diff --git a/tests/integration/targets/openssl_csr/tasks/impl.yml b/tests/integration/targets/openssl_csr/tasks/impl.yml index 0d9041de..c137509d 100644 --- a/tests/integration/targets/openssl_csr/tasks/impl.yml +++ b/tests/integration/targets/openssl_csr/tasks/impl.yml @@ -29,8 +29,8 @@ openssl_csr: path: '{{ remote_tmp_dir }}/csr.csr' privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' - subject: - commonName: www.ansible.com + subject_ordered: + - commonName: www.ansible.com select_crypto_backend: '{{ select_crypto_backend }}' return_content: yes register: generate_csr_idempotent @@ -517,23 +517,23 @@ openssl_csr: path: '{{ remote_tmp_dir }}/csr_everything.csr' privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' - subject: - commonName: www.example.com - C: de - L: Somewhere - ST: Zürich - streetAddress: Welcome Street N° 5 - O: Ansiblé - organizationalUnitName: Crÿpto Depârtment ☺ - serialNumber: "1234" - SN: Last Name Which Happens To Be A Very Løng String With A Lot Of Spaces, Jr. - GN: First Name - title: Chïeff - pseudonym: test - UID: asdf - emailAddress: test@example.com - postalAddress: 1234 Somewhere - postalCode: "1234" + subject_ordered: + - commonName: www.example.com + - C: de + - L: Somewhere + - ST: Zürich + - streetAddress: Welcome Street N° 5 + - O: Ansiblé + - organizationalUnitName: Crÿpto Depârtment ☺ + - serialNumber: "1234" + - SN: Last Name Which Happens To Be A Very Løng String With A Lot Of Spaces, Jr. + - GN: First Name + - title: Chïeff + - pseudonym: test + - UID: asdf + - emailAddress: test@example.com + - postalAddress: 1234 Somewhere + - postalCode: "1234" useCommonNameForSAN: no key_usage: - digitalSignature @@ -602,23 +602,23 @@ openssl_csr: path: '{{ remote_tmp_dir }}/csr_everything.csr' privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' - subject: - CN: www.example.com - countryName: de - L: Somewhere - ST: Zürich - streetAddress: Welcome Street N° 5 - organizationName: Ansiblé - organizationalUnitName: Crÿpto Depârtment ☺ - serialNumber: "1234" - SN: Last Name Which Happens To Be A Very Løng String With A Lot Of Spaces, Jr. - GN: First Name - title: Chïeff - pseudonym: test - UID: asdf - emailAddress: test@example.com - postalAddress: 1234 Somewhere - postalCode: "1234" + subject_ordered: + - CN: www.example.com + - countryName: de + - L: Somewhere + - ST: Zürich + - streetAddress: Welcome Street N° 5 + - organizationName: Ansiblé + - organizationalUnitName: Crÿpto Depârtment ☺ + - serialNumber: "1234" + - SN: Last Name Which Happens To Be A Very Løng String With A Lot Of Spaces, Jr. + - GN: First Name + - title: Chïeff + - pseudonym: test + - UID: asdf + - emailAddress: test@example.com + - postalAddress: 1234 Somewhere + - postalCode: "1234" useCommonNameForSAN: no key_usage: - digitalSignature @@ -689,18 +689,19 @@ path: '{{ remote_tmp_dir }}/csr_everything.csr' privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' subject: + # Subject has been reordered, but is inside 'subject' and not 'subject_ordered' CN: www.example.com - countryName: de L: Somewhere + countryName: de ST: Zürich streetAddress: Welcome Street N° 5 - organizationName: Ansiblé organizationalUnitName: Crÿpto Depârtment ☺ + organizationName: Ansiblé serialNumber: "1234" SN: Last Name Which Happens To Be A Very Løng String With A Lot Of Spaces, Jr. GN: First Name - title: Chïeff pseudonym: test + title: Chïeff UID: asdf emailAddress: test@example.com postalAddress: 1234 Somewhere @@ -769,6 +770,93 @@ - "IP:0::0:1:0:0/112" register: everything_3 +- name: "({{ select_crypto_backend }}) Generate CSR with everything (not idempotent, check mode)" + openssl_csr: + path: '{{ remote_tmp_dir }}/csr_everything.csr' + privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' + subject_ordered: + # Subject has been reordered, this should force a change + - CN: www.example.com + - L: Somewhere + - countryName: de + - ST: Zürich + - streetAddress: Welcome Street N° 5 + - organizationalUnitName: Crÿpto Depârtment ☺ + - organizationName: Ansiblé + - serialNumber: "1234" + - SN: Last Name Which Happens To Be A Very Løng String With A Lot Of Spaces, Jr. + - GN: First Name + - pseudonym: test + - title: Chïeff + - UID: asdf + - emailAddress: test@example.com + - postalAddress: 1234 Somewhere + - postalCode: "1234" + useCommonNameForSAN: no + key_usage: + - digitalSignature + - keyAgreement + - Non Repudiation + - Key Encipherment + - dataEncipherment + - Certificate Sign + - cRLSign + - Encipher Only + - decipherOnly + key_usage_critical: yes + extended_key_usage: '{{ value_for_extended_key_usage }}' + subject_alt_name: '{{ value_for_san }}' + basic_constraints: + - "CA:TRUE" + - "pathlen:23" + basic_constraints_critical: yes + name_constraints_permitted: '{{ value_for_name_constraints_permitted }}' + name_constraints_excluded: + - "DNS:.org" + - "DNS:.example.com" + name_constraints_critical: yes + ocsp_must_staple: yes + subject_key_identifier: 00:11:22:33 + authority_key_identifier: 44:55:66:77 + authority_cert_issuer: '{{ value_for_authority_cert_issuer }}' + authority_cert_serial_number: 12345 + select_crypto_backend: '{{ select_crypto_backend }}' + vars: + value_for_extended_key_usage: + - serverAuth # the same as "TLS Web Server Authentication" + - TLS Web Server Authentication + - TLS Web Client Authentication + - Code Signing + - E-mail Protection + - timeStamping + - OCSPSigning + - Any Extended Key Usage + - qcStatements + - DVCS + - IPSec User + - biometricInfo + - 1.2.3.4.5.6 + value_for_authority_cert_issuer: + - "DNS:ca.example.org" + - "IP:1.2.3.4" + value_for_san: + - "DNS:www.ansible.com" + - "IP:1.2.3.4" + - "IP:::1" + - "email:test@example.org" + - "URI:https://example.org/test/index.html" + - "RID:1.2.3.4" + - "otherName:1.2.3.4;0c:07:63:65:72:74:72:65:71" + - "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:bob@localhost" + - "dirName:CN= example.net, O =Example Net" + - "dirName:/CN= example.com/O =Example Com" + value_for_name_constraints_permitted: + - "DNS:www.example.com" + - "IP:1.2.3.0/255.255.255.0" + - "IP:0::0:1:0:0/112" + register: everything_4 + check_mode: true + - name: "({{ select_crypto_backend }}) Get info from CSR with everything" community.crypto.openssl_csr_info: path: '{{ remote_tmp_dir }}/csr_everything.csr' diff --git a/tests/integration/targets/openssl_csr/tests/validate.yml b/tests/integration/targets/openssl_csr/tests/validate.yml index 969ae0ef..81bd4ff9 100644 --- a/tests/integration/targets/openssl_csr/tests/validate.yml +++ b/tests/integration/targets/openssl_csr/tests/validate.yml @@ -195,6 +195,7 @@ - everything_1 is changed - everything_2 is not changed - everything_3 is not changed + - everything_4 is changed - everything_info.basic_constraints == [ "CA:TRUE", "pathlen:23", @@ -233,6 +234,25 @@ - everything_info.subject.title == "Chïeff" - everything_info.subject.userId == "asdf" - everything_info.subject | length == 16 + - > + everything_info.subject_ordered == [ + ["commonName", "www.example.com"], + ["countryName", "de"], + ["localityName", "Somewhere"], + ["stateOrProvinceName", "Zürich"], + ["streetAddress", "Welcome Street N° 5"], + ["organizationName", "Ansiblé"], + ["organizationalUnitName", "Crÿpto Depârtment ☺"], + ["serialNumber", "1234"], + ["surname", "Last Name Which Happens To Be A Very Løng String With A Lot Of Spaces, Jr."], + ["givenName", "First Name"], + ["title", "Chïeff"], + ["pseudonym", "test"], + ["userId", "asdf"], + ["emailAddress", "test@example.com"], + ["postalAddress", "1234 Somewhere"], + ["postalCode", "1234"], + ] - everything_info.subject_alt_name_critical == false - everything_info.name_constraints_excluded == [ "DNS:.example.com", diff --git a/tests/integration/targets/x509_crl/tasks/impl.yml b/tests/integration/targets/x509_crl/tasks/impl.yml index 4fa6bbd3..db18e98f 100644 --- a/tests/integration/targets/x509_crl/tasks/impl.yml +++ b/tests/integration/targets/x509_crl/tasks/impl.yml @@ -1,25 +1,4 @@ --- -- name: Create CRL 1 (check mode) - x509_crl: - path: '{{ remote_tmp_dir }}/ca-crl1.crl' - privatekey_path: '{{ remote_tmp_dir }}/ca.key' - issuer: - CN: Ansible - last_update: 20191013000000Z - next_update: 20191113000000Z - revoked_certificates: - - path: '{{ remote_tmp_dir }}/cert-1.pem' - revocation_date: 20191013000000Z - - path: '{{ remote_tmp_dir }}/cert-2.pem' - revocation_date: 20191013000000Z - reason: key_compromise - reason_critical: yes - invalidity_date: 20191012000000Z - - serial_number: 1234 - revocation_date: 20191001000000Z - check_mode: yes - register: crl_1_check - - name: Create CRL 1 (check mode) x509_crl: path: '{{ remote_tmp_dir }}/ca-crl1.crl' @@ -283,8 +262,11 @@ x509_crl: path: '{{ remote_tmp_dir }}/ca-crl2.crl' privatekey_path: '{{ remote_tmp_dir }}/ca.key' - issuer: - CN: Ansible + issuer_ordered: + - CN: Ansible + - CN: CRL + - countryName: US + - CN: Test last_update: +0d next_update: +0d revoked_certificates: @@ -301,8 +283,11 @@ x509_crl: path: '{{ remote_tmp_dir }}/ca-crl2.crl' privatekey_path: '{{ remote_tmp_dir }}/ca.key' - issuer: - CN: Ansible + issuer_ordered: + - CN: Ansible + - CN: CRL + - countryName: US + - CN: Test last_update: +0d next_update: +0d revoked_certificates: @@ -318,8 +303,11 @@ x509_crl: path: '{{ remote_tmp_dir }}/ca-crl2.crl' privatekey_path: '{{ remote_tmp_dir }}/ca.key' - issuer: - CN: Ansible + issuer_ordered: + - CN: Ansible + - CN: CRL + - C: US + - CN: Test last_update: +0d next_update: +0d revoked_certificates: @@ -337,8 +325,11 @@ x509_crl: path: '{{ remote_tmp_dir }}/ca-crl2.crl' privatekey_path: '{{ remote_tmp_dir }}/ca.key' - issuer: - CN: Ansible + issuer_ordered: + - CN: Ansible + - CN: CRL + - countryName: US + - CN: Test last_update: +0d next_update: +0d revoked_certificates: @@ -355,8 +346,11 @@ x509_crl: path: '{{ remote_tmp_dir }}/ca-crl2.crl' privatekey_path: '{{ remote_tmp_dir }}/ca.key' - issuer: - CN: Ansible + issuer_ordered: + - CN: Ansible + - CN: CRL + - countryName: US + - CN: Test last_update: +0d next_update: +0d revoked_certificates: @@ -370,8 +364,11 @@ x509_crl: path: '{{ remote_tmp_dir }}/ca-crl2.crl' privatekey_path: '{{ remote_tmp_dir }}/ca.key' - issuer: - CN: Ansible + issuer_ordered: + - CN: Ansible + - CN: CRL + - countryName: US + - CN: Test last_update: +0d next_update: +0d revoked_certificates: @@ -384,8 +381,11 @@ x509_crl: path: '{{ remote_tmp_dir }}/ca-crl2.crl' privatekey_path: '{{ remote_tmp_dir }}/ca.key' - issuer: - CN: Ansible + issuer_ordered: + - CN: Ansible + - CN: CRL + - countryName: US + - CN: Test last_update: +0d next_update: +0d revoked_certificates: @@ -402,8 +402,11 @@ x509_crl: path: '{{ remote_tmp_dir }}/ca-crl2.crl' privatekey_path: '{{ remote_tmp_dir }}/ca.key' - issuer: - CN: Ansible + issuer_ordered: + - CN: Ansible + - CN: CRL + - countryName: US + - CN: Test last_update: +0d next_update: +0d revoked_certificates: @@ -419,8 +422,11 @@ x509_crl: path: '{{ remote_tmp_dir }}/ca-crl2.crl' privatekey_path: '{{ remote_tmp_dir }}/ca.key' - issuer: - CN: Ansible + issuer_ordered: + - CN: Ansible + - CN: CRL + - countryName: US + - CN: Test last_update: +0d next_update: +0d revoked_certificates: @@ -437,8 +443,11 @@ x509_crl: path: '{{ remote_tmp_dir }}/ca-crl2.crl' privatekey_path: '{{ remote_tmp_dir }}/ca.key' - issuer: - CN: Ansible + issuer_ordered: + - CN: Ansible + - CN: CRL + - countryName: US + - CN: Test last_update: +0d next_update: +0d revoked_certificates: @@ -451,8 +460,67 @@ return_content: yes register: crl_2_change +- name: Read ca-crl2.crl + slurp: + src: '{{ remote_tmp_dir }}/ca-crl2.crl' + register: slurp_crl2_1 + - name: Retrieve CRL 2 infos x509_crl_info: path: '{{ remote_tmp_dir }}/ca-crl2.crl' list_revoked_certificates: false register: crl_2_info_1 + +- name: Create CRL 2 (changed order, should be ignored) + x509_crl: + path: '{{ remote_tmp_dir }}/ca-crl2.crl' + privatekey_path: '{{ remote_tmp_dir }}/ca.key' + issuer: + countryName: US + CN: + - Ansible + - CRL + - Test + last_update: +0d + next_update: +0d + revoked_certificates: + - path: '{{ remote_tmp_dir }}/cert-2.pem' + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + ignore_timestamps: true + mode: update + return_content: yes + register: crl_2_change_order_ignore + +- name: Create CRL 2 (changed order) + x509_crl: + path: '{{ remote_tmp_dir }}/ca-crl2.crl' + privatekey_path: '{{ remote_tmp_dir }}/ca.key' + issuer_ordered: + - CN: Ansible + - countryName: US + - CN: CRL + - CN: Test + last_update: +0d + next_update: +0d + revoked_certificates: + - path: '{{ remote_tmp_dir }}/cert-2.pem' + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + ignore_timestamps: true + mode: update + return_content: yes + register: crl_2_change_order + +- name: Read ca-crl2.crl + slurp: + src: '{{ remote_tmp_dir }}/ca-crl2.crl' + register: slurp_crl2_2 + +- name: Retrieve CRL 2 infos again + x509_crl_info: + path: '{{ remote_tmp_dir }}/ca-crl2.crl' + list_revoked_certificates: false + register: crl_2_info_2 diff --git a/tests/integration/targets/x509_crl/tests/validate.yml b/tests/integration/targets/x509_crl/tests/validate.yml index b495f26e..9e6aa733 100644 --- a/tests/integration/targets/x509_crl/tests/validate.yml +++ b/tests/integration/targets/x509_crl/tests/validate.yml @@ -66,11 +66,6 @@ that: - crl_1_format_idem.crl | b64decode == content.content | b64decode -- name: Read ca-crl2.crl - slurp: - src: '{{ remote_tmp_dir }}/ca-crl2.crl' - register: slurp - - name: Validate CRL 2 assert: that: @@ -84,9 +79,26 @@ - crl_2_idem_update is not changed - crl_2_change_check is changed - crl_2_change is changed - - crl_2_change.crl == (slurp.content | b64decode) + - crl_2_change.crl == (slurp_crl2_1.content | b64decode) + - crl_2_change_order_ignore is not changed + - crl_2_change_order is changed + - crl_2_change_order.crl == (slurp_crl2_2.content | b64decode) - name: Validate CRL 2 info assert: that: - "'revoked_certificates' not in crl_2_info_1" + - > + crl_2_info_1.issuer_ordered == [ + ['commonName', 'Ansible'], + ['commonName', 'CRL'], + ['countryName', 'US'], + ['commonName', 'Test'], + ] + - > + crl_2_info_2.issuer_ordered == [ + ['commonName', 'Ansible'], + ['countryName', 'US'], + ['commonName', 'CRL'], + ['commonName', 'Test'], + ]