From af5225794d1f64b111aee795eebbe59117f016f7 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 20 Jul 2020 16:57:18 +0200 Subject: [PATCH 1/4] Add support for name constraints extension to openssl_csr. --- .../92-openssl_csr-name-constraints.yml | 3 + .../module_utils/crypto/pyopenssl_support.py | 25 +++++ plugins/modules/openssl_csr.py | 99 ++++++++++++++++++- plugins/modules/openssl_csr_info.py | 49 +++++++++ 4 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 changelogs/fragments/92-openssl_csr-name-constraints.yml diff --git a/changelogs/fragments/92-openssl_csr-name-constraints.yml b/changelogs/fragments/92-openssl_csr-name-constraints.yml new file mode 100644 index 000000000..f0d64f9ab --- /dev/null +++ b/changelogs/fragments/92-openssl_csr-name-constraints.yml @@ -0,0 +1,3 @@ +minor_changes: +- "openssl_csr - add support for name constraints extension (https://github.com/ansible-collections/community.crypto/issues/46)." +- "openssl_csr_info - add support for name constraints extension (https://github.com/ansible-collections/community.crypto/issues/46)." diff --git a/plugins/module_utils/crypto/pyopenssl_support.py b/plugins/module_utils/crypto/pyopenssl_support.py index 8a14cf379..061bece9e 100644 --- a/plugins/module_utils/crypto/pyopenssl_support.py +++ b/plugins/module_utils/crypto/pyopenssl_support.py @@ -119,3 +119,28 @@ def pyopenssl_get_extensions_from_csr(csr): # similarly to how cryptography does it. result[oid] = entry return result + + +def pyopenssl_parse_name_constraints(name_constraints_extension): + lines = to_text(name_constraints_extension, errors='surrogate_or_strict').splitlines() + exclude = None + excluded = [] + permitted = [] + for line in lines: + if line.startswith(' ') or line.startswith('\t'): + name = pyopenssl_normalize_name_attribute(line.strip()) + if exclude is True: + excluded.append(name) + elif exclude is False: + permitted.append(name) + else: + raise OpenSSLObjectError('Unexpected nameConstraint line: "{0}"'.format(line)) + else: + line_lc = line.lower() + if line_lc.startswith('exclud'): + exclude = True + elif line_lc.startswith('includ') or line_lc.startswith('permitt'): + exclude = False + else: + raise OpenSSLObjectError('Cannot parse nameConstraint line: "{0}"'.format(line)) + return permitted, excluded diff --git a/plugins/modules/openssl_csr.py b/plugins/modules/openssl_csr.py index fb205c0fd..2534b42f8 100644 --- a/plugins/modules/openssl_csr.py +++ b/plugins/modules/openssl_csr.py @@ -183,13 +183,36 @@ aliases: [ ocspMustStaple ] ocsp_must_staple_critical: description: - - Should the OCSP Must Staple extension be considered as critical + - Should the OCSP Must Staple extension be considered as critical. - Note that according to the RFC, this extension should not be marked as critical, as old clients not knowing about OCSP Must Staple are required to reject such certificates (see U(https://tools.ietf.org/html/rfc7633#section-4)). type: bool aliases: [ ocspMustStaple_critical ] + name_constraints_permitted: + description: + - For CA certificates, this specifies a list of identifiers which describe + subtrees of names that this CA is allowed to issue certificates for. + - Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName), + C(otherName) and the ones specific to your CA). + type: list + elements: str + version_added: 1.1.0 + name_constraints_excluded: + description: + - For CA certificates, this specifies a list of identifiers which describe + subtrees of names that this CA is *not* allowed to issue certificates for. + - Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName), + C(otherName) and the ones specific to your CA). + type: list + elements: str + version_added: 1.1.0 + name_constraints_critical: + description: + - Should the Name Constraints extension be considered as critical. + type: bool + version_added: 1.1.0 select_crypto_backend: description: - Determines which crypto backend to use. @@ -412,6 +435,20 @@ returned: changed or success type: bool sample: false +name_constraints_permitted: + description: List of permitted subtrees to sign certificates for. + returned: changed or success + type: list + elements: str + sample: [email:.somedomain.com] + version_added: 1.1.0 +name_constraints_excluded: + description: List of excluded subtrees the CA cannot sign certificates for. + returned: changed or success + type: list + elements: str + sample: [email:.com] + version_added: 1.1.0 backup_file: description: Name of backup file created. returned: changed and if I(backup) is C(yes) @@ -461,6 +498,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import ( pyopenssl_normalize_name_attribute, + pyopenssl_parse_name_constraints, ) MINIMAL_PYOPENSSL_VERSION = '0.15' @@ -534,6 +572,9 @@ def __init__(self, module): self.basicConstraints_critical = module.params['basic_constraints_critical'] self.ocspMustStaple = module.params['ocsp_must_staple'] self.ocspMustStaple_critical = module.params['ocsp_must_staple_critical'] + self.name_constraints_permitted = module.params['name_constraints_permitted'] or [] + self.name_constraints_excluded = module.params['name_constraints_excluded'] or [] + self.name_constraints_critical = module.params['name_constraints_critical'] self.create_subject_key_identifier = module.params['create_subject_key_identifier'] self.subject_key_identifier = module.params['subject_key_identifier'] self.authority_key_identifier = module.params['authority_key_identifier'] @@ -637,7 +678,9 @@ def dump(self): 'extendedKeyUsage': self.extendedKeyUsage, 'basicConstraints': self.basicConstraints, 'ocspMustStaple': self.ocspMustStaple, - 'changed': self.changed + 'changed': self.changed, + 'name_constraints_permitted': self.name_constraints_permitted, + 'name_constraints_excluded': self.name_constraints_excluded, } if self.backup_file: result['backup_file'] = self.backup_file @@ -697,6 +740,13 @@ def _generate_csr(self): usages = ', '.join(self.basicConstraints) extensions.append(crypto.X509Extension(b"basicConstraints", self.basicConstraints_critical, usages.encode('ascii'))) + if self.name_constraints_permitted or self.name_constraints_excluded: + usages = ', '.join( + ['permitted;{0}'.format(name) for name in self.name_constraints_permitted] + + ['excluded;{0}'.format(name) for name in self.name_constraints_excluded] + ) + extensions.append(crypto.X509Extension(b"nameConstraints", self.name_constraints_critical, usages.encode('ascii'))) + if self.ocspMustStaple: extensions.append(crypto.X509Extension(OPENSSL_MUST_STAPLE_NAME, self.ocspMustStaple_critical, OPENSSL_MUST_STAPLE_VALUE)) @@ -773,6 +823,22 @@ def _check_extenededKeyUsage(extensions): def _check_basicConstraints(extensions): return _check_keyUsage_(extensions, b'basicConstraints', self.basicConstraints, self.basicConstraints_critical) + def _check_nameConstraints(extensions): + nc_ext = next((ext for ext in extensions if ext.get_short_name() == b'nameConstraints'), '') + permitted, excluded = pyopenssl_parse_name_constraints(nc_ext) + if self.name_constraints_permitted or self.name_constraints_excluded: + if set(permitted) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.name_constraints_permitted]): + return False + if set(excluded) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.name_constraints_excluded]): + return False + if nc_ext.get_critical() != self.name_constraints_critical: + return False + else: + if permitted or excluded: + return False + + return True + def _check_ocspMustStaple(extensions): oms_ext = [ext for ext in extensions if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE] if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000: @@ -787,7 +853,7 @@ def _check_extensions(csr): extensions = csr.get_extensions() return (_check_subjectAltName(extensions) and _check_keyUsage(extensions) and _check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions) and - _check_ocspMustStaple(extensions)) + _check_ocspMustStaple(extensions) and _check_nameConstraints(extensions)) def _check_signature(csr): try: @@ -849,6 +915,15 @@ def _generate_csr(self): critical=self.ocspMustStaple_critical ) + if self.name_constraints_permitted or self.name_constraints_excluded: + try: + csr = csr.add_extension(cryptography.x509.NameConstraints( + [cryptography_get_name(name) for name in self.name_constraints_permitted], + [cryptography_get_name(name) for name in self.name_constraints_excluded], + ), critical=self.name_constraints_critical) + except TypeError as e: + raise OpenSSLObjectError('Error while parsing name constraint: {0}'.format(e)) + if self.create_subject_key_identifier: csr = csr.add_extension( cryptography.x509.SubjectKeyIdentifier.from_public_key(self.privatekey.public_key()), @@ -991,6 +1066,19 @@ def _check_ocspMustStaple(extensions): else: return tlsfeature_ext is None + def _check_nameConstraints(extensions): + current_nc_ext = _find_extension(extensions, cryptography.x509.NameConstraints) + current_nc_perm = [str(altname) for altname in current_nc_ext.value.permitted_subtrees] if current_nc_ext else [] + current_nc_excl = [str(altname) for altname in current_nc_ext.value.excluded_subtrees] if current_nc_ext else [] + nc_perm = [str(cryptography_get_name(altname)) for altname in self.name_constraints_permitted] + nc_excl = [str(cryptography_get_name(altname)) for altname in self.name_constraints_excluded] + if set(nc_perm) != set(current_nc_perm) or set(nc_excl) != set(current_nc_excl): + return False + if nc_perm or nc_excl: + if current_nc_ext.critical != self.name_constraints_critical: + return False + return True + def _check_subject_key_identifier(extensions): ext = _find_extension(extensions, cryptography.x509.SubjectKeyIdentifier) if self.create_subject_key_identifier or self.subject_key_identifier is not None: @@ -1026,7 +1114,7 @@ def _check_extensions(csr): return (_check_subjectAltName(extensions) and _check_keyUsage(extensions) and _check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions) and _check_ocspMustStaple(extensions) and _check_subject_key_identifier(extensions) and - _check_authority_key_identifier(extensions)) + _check_authority_key_identifier(extensions) and _check_nameConstraints(extensions)) def _check_signature(csr): if not csr.is_signature_valid: @@ -1081,6 +1169,9 @@ def main(): basic_constraints_critical=dict(type='bool', default=False, aliases=['basicConstraints_critical']), ocsp_must_staple=dict(type='bool', default=False, aliases=['ocspMustStaple']), ocsp_must_staple_critical=dict(type='bool', default=False, aliases=['ocspMustStaple_critical']), + name_constraints_permitted=dict(type='list', elements='str'), + name_constraints_excluded=dict(type='list', elements='str'), + name_constraints_critical=dict(type='bool', default=False), backup=dict(type='bool', default=False), create_subject_key_identifier=dict(type='bool', default=False), subject_key_identifier=dict(type='str'), diff --git a/plugins/modules/openssl_csr_info.py b/plugins/modules/openssl_csr_info.py index b136ab580..44ee0bb92 100644 --- a/plugins/modules/openssl_csr_info.py +++ b/plugins/modules/openssl_csr_info.py @@ -141,6 +141,29 @@ description: Whether the C(ocsp_must_staple) extension is critical. returned: success type: bool +name_constraints_permitted: + description: List of permitted subtrees to sign certificates for. + returned: success + type: list + elements: str + sample: [email:.somedomain.com] + version_added: 1.1.0 +name_constraints_excluded: + description: + - List of excluded subtrees the CA cannot sign certificates for. + - Is C(none) if extension is not present. + returned: success + type: list + elements: str + sample: [email:.com] + version_added: 1.1.0 +name_constraints_critical: + description: + - Whether the C(name_constraints) extension is critical. + - Is C(none) if extension is not present. + returned: success + type: bool + version_added: 1.1.0 subject: description: - The CSR's subject as a dictionary. @@ -231,6 +254,7 @@ pyopenssl_get_extensions_from_csr, pyopenssl_normalize_name, pyopenssl_normalize_name_attribute, + pyopenssl_parse_name_constraints, ) MINIMAL_CRYPTOGRAPHY_VERSION = '1.3' @@ -317,6 +341,10 @@ def _get_ocsp_must_staple(self): def _get_subject_alt_name(self): pass + @abc.abstractmethod + def _get_name_constraints(self): + pass + @abc.abstractmethod def _get_public_key(self, binary): pass @@ -351,6 +379,11 @@ def get_info(self): result['basic_constraints'], result['basic_constraints_critical'] = self._get_basic_constraints() result['ocsp_must_staple'], result['ocsp_must_staple_critical'] = self._get_ocsp_must_staple() result['subject_alt_name'], result['subject_alt_name_critical'] = self._get_subject_alt_name() + ( + result['name_constraints_permitted'], + result['name_constraints_excluded'], + result['name_constraints_critical'], + ) = self._get_name_constraints() result['public_key'] = self._get_public_key(binary=False) pk = self._get_public_key(binary=True) @@ -474,6 +507,15 @@ def _get_subject_alt_name(self): except cryptography.x509.ExtensionNotFound: return None, False + def _get_name_constraints(self): + try: + nc_ext = self.csr.extensions.get_extension_for_class(x509.NameConstraints) + permitted = [cryptography_decode_name(san) for san in nc_ext.value.permitted_subtrees or []] + excluded = [cryptography_decode_name(san) for san in nc_ext.value.excluded_subtrees or []] + return permitted, excluded, nc_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, None, False + def _get_public_key(self, binary): return self.csr.public_key().public_bytes( serialization.Encoding.DER if binary else serialization.Encoding.PEM, @@ -559,6 +601,13 @@ def _get_subject_alt_name(self): return result, bool(extension.get_critical()) return None, False + def _get_name_constraints(self): + for extension in self.csr.get_extensions(): + if extension.get_short_name() == b'nameConstraints': + permitted, excluded = pyopenssl_parse_name_constraints(extension) + return permitted, excluded, bool(extension.get_critical()) + return None, None, False + def _get_public_key(self, binary): try: return crypto.dump_publickey( From 12ee092499969ed005b41bd0353e4ceea7b0d944 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 20 Jul 2020 17:13:34 +0200 Subject: [PATCH 2/4] Linting. --- plugins/module_utils/crypto/pyopenssl_support.py | 4 ++++ plugins/modules/openssl_csr.py | 4 ++-- plugins/modules/openssl_csr_info.py | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/module_utils/crypto/pyopenssl_support.py b/plugins/module_utils/crypto/pyopenssl_support.py index 061bece9e..ccc25a876 100644 --- a/plugins/module_utils/crypto/pyopenssl_support.py +++ b/plugins/module_utils/crypto/pyopenssl_support.py @@ -38,6 +38,10 @@ from ._obj2txt import obj2txt +from .basic import ( + OpenSSLObjectError, +) + def pyopenssl_normalize_name(name, short=False): nid = OpenSSL._util.lib.OBJ_txt2nid(to_bytes(name)) diff --git a/plugins/modules/openssl_csr.py b/plugins/modules/openssl_csr.py index 2534b42f8..5560db993 100644 --- a/plugins/modules/openssl_csr.py +++ b/plugins/modules/openssl_csr.py @@ -440,14 +440,14 @@ returned: changed or success type: list elements: str - sample: [email:.somedomain.com] + sample: ['email:.somedomain.com'] version_added: 1.1.0 name_constraints_excluded: description: List of excluded subtrees the CA cannot sign certificates for. returned: changed or success type: list elements: str - sample: [email:.com] + sample: ['email:.com'] version_added: 1.1.0 backup_file: description: Name of backup file created. diff --git a/plugins/modules/openssl_csr_info.py b/plugins/modules/openssl_csr_info.py index 44ee0bb92..2d1137402 100644 --- a/plugins/modules/openssl_csr_info.py +++ b/plugins/modules/openssl_csr_info.py @@ -146,7 +146,7 @@ returned: success type: list elements: str - sample: [email:.somedomain.com] + sample: ['email:.somedomain.com'] version_added: 1.1.0 name_constraints_excluded: description: @@ -155,7 +155,7 @@ returned: success type: list elements: str - sample: [email:.com] + sample: ['email:.com'] version_added: 1.1.0 name_constraints_critical: description: From 66b73f9989d09df9634620d5497a26bd83341909 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 20 Jul 2020 17:41:37 +0200 Subject: [PATCH 3/4] Add tests. --- .../targets/openssl_csr/tasks/impl.yml | 18 ++++++++++++++++++ .../targets/openssl_csr/tests/validate.yml | 10 +++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/integration/targets/openssl_csr/tasks/impl.yml b/tests/integration/targets/openssl_csr/tasks/impl.yml index ccecfefbc..21f3d56cc 100644 --- a/tests/integration/targets/openssl_csr/tasks/impl.yml +++ b/tests/integration/targets/openssl_csr/tasks/impl.yml @@ -556,6 +556,12 @@ - "CA:TRUE" - "pathlen:23" basic_constraints_critical: yes + name_constraints_permitted: + - "DNS:www.example.com" + name_constraints_excluded: + - "DNS:.example.com" + - "DNS:.org" + name_constraints_critical: yes ocsp_must_staple: yes subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}' authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}' @@ -652,6 +658,12 @@ - "CA:TRUE" - "pathlen:23" basic_constraints_critical: yes + name_constraints_permitted: + - "DNS:www.example.com" + name_constraints_excluded: + - "DNS:.org" + - "DNS:.example.com" + name_constraints_critical: yes ocsp_must_staple: yes subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}' authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}' @@ -749,6 +761,12 @@ - "CA:TRUE" - "pathlen:23" basic_constraints_critical: yes + name_constraints_permitted: + - "DNS:www.example.com" + name_constraints_excluded: + - "DNS:.org" + - "DNS:.example.com" + name_constraints_critical: yes ocsp_must_staple: yes subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}' authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}' diff --git a/tests/integration/targets/openssl_csr/tests/validate.yml b/tests/integration/targets/openssl_csr/tests/validate.yml index b5de7f0f5..3c5a0732c 100644 --- a/tests/integration/targets/openssl_csr/tests/validate.yml +++ b/tests/integration/targets/openssl_csr/tests/validate.yml @@ -200,7 +200,7 @@ "Key Agreement", "Key Encipherment", "Non Repudiation" - ], + ] - everything_info.key_usage_critical == true - everything_info.ocsp_must_staple == true - everything_info.ocsp_must_staple_critical == false @@ -223,6 +223,14 @@ - everything_info.subject.userId == "asdf" - everything_info.subject | length == 16 - everything_info.subject_alt_name_critical == false + - everything_info.name_constraints_permitted == [ + "DNS:www.example.com", + ] + - everything_info.name_constraints_excluded == [ + "DNS:.example.com", + "DNS:.org", + ] + - everything_info.name_constraints_critical == true - name: Check CSR with everything (pyOpenSSL specific) assert: From 9dec640315a42fb272c309f632d352fa7c0da2fe Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 20 Jul 2020 18:07:51 +0200 Subject: [PATCH 4/4] Fix IP address general name handling. --- changelogs/fragments/92-ip-networks.yml | 2 ++ .../crypto/cryptography_support.py | 7 ++++- .../module_utils/crypto/pyopenssl_support.py | 10 +++++-- .../targets/openssl_csr/tasks/impl.yml | 30 +++++++++++++++---- .../targets/openssl_csr/tests/validate.yml | 12 ++++++-- 5 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 changelogs/fragments/92-ip-networks.yml diff --git a/changelogs/fragments/92-ip-networks.yml b/changelogs/fragments/92-ip-networks.yml new file mode 100644 index 000000000..64594ab81 --- /dev/null +++ b/changelogs/fragments/92-ip-networks.yml @@ -0,0 +1,2 @@ +bugfixes: +- "openssl_*, x509_* modules - fix handling of general names which refer to IP networks and not IP addresses (https://github.com/ansible-collections/community.crypto/pull/92)." diff --git a/plugins/module_utils/crypto/cryptography_support.py b/plugins/module_utils/crypto/cryptography_support.py index a527d1c05..d156ecd30 100644 --- a/plugins/module_utils/crypto/cryptography_support.py +++ b/plugins/module_utils/crypto/cryptography_support.py @@ -205,7 +205,10 @@ def cryptography_get_name(name): if name.startswith('DNS:'): return x509.DNSName(to_text(name[4:])) if name.startswith('IP:'): - return x509.IPAddress(ipaddress.ip_address(to_text(name[3:]))) + address = to_text(name[3:]) + if '/' in address: + return x509.IPAddress(ipaddress.ip_network(address)) + return x509.IPAddress(ipaddress.ip_address(address)) if name.startswith('email:'): return x509.RFC822Name(to_text(name[6:])) if name.startswith('URI:'): @@ -261,6 +264,8 @@ def cryptography_decode_name(name): if isinstance(name, x509.DNSName): return 'DNS:{0}'.format(name.value) if isinstance(name, x509.IPAddress): + if isinstance(name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)): + return 'IP:{0}/{1}'.format(name.value.network_address.compressed, name.value.prefixlen) return 'IP:{0}'.format(name.value.compressed) if isinstance(name, x509.RFC822Name): return 'email:{0}'.format(name.value) diff --git a/plugins/module_utils/crypto/pyopenssl_support.py b/plugins/module_utils/crypto/pyopenssl_support.py index ccc25a876..17c98d279 100644 --- a/plugins/module_utils/crypto/pyopenssl_support.py +++ b/plugins/module_utils/crypto/pyopenssl_support.py @@ -60,9 +60,13 @@ def pyopenssl_normalize_name_attribute(san): if san.startswith('IP Address:'): san = 'IP:' + san[len('IP Address:'):] if san.startswith('IP:'): - ip = compat_ipaddress.ip_address(san[3:]) - san = 'IP:{0}'.format(ip.compressed) - + address = san[3:] + if '/' in address: + ip = compat_ipaddress.ip_network(address) + san = 'IP:{0}/{1}'.format(ip.network_address.compressed, ip.prefixlen) + else: + ip = compat_ipaddress.ip_address(address) + san = 'IP:{0}'.format(ip.compressed) if san.startswith('Registered ID:'): san = 'RID:' + san[len('Registered ID:'):] # Some versions of OpenSSL apparently forgot the colon. Happens in CI with Ubuntu 16.04 and FreeBSD 11.1 diff --git a/tests/integration/targets/openssl_csr/tasks/impl.yml b/tests/integration/targets/openssl_csr/tasks/impl.yml index 21f3d56cc..6a12f9e91 100644 --- a/tests/integration/targets/openssl_csr/tasks/impl.yml +++ b/tests/integration/targets/openssl_csr/tasks/impl.yml @@ -556,8 +556,7 @@ - "CA:TRUE" - "pathlen:23" basic_constraints_critical: yes - name_constraints_permitted: - - "DNS:www.example.com" + name_constraints_permitted: '{{ value_for_name_constraints_permitted if select_crypto_backend != "pyopenssl" else value_for_name_constraints_permitted_pyopenssl }}' name_constraints_excluded: - "DNS:.example.com" - "DNS:.org" @@ -617,6 +616,13 @@ - "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:bob@localhost" - "dirName:O = Example Net, CN = example.net" - "dirName:/O=Example Com/CN=example.com" + value_for_name_constraints_permitted: + - "DNS:www.example.com" + - "IP:1.2.3.0/24" + - "IP:::1:0:0/112" + value_for_name_constraints_permitted_pyopenssl: + - "DNS:www.example.com" + - "IP:1.2.3.0/255.255.255.0" register: everything_1 - name: Generate CSR with everything (idempotent, check mode) @@ -658,8 +664,7 @@ - "CA:TRUE" - "pathlen:23" basic_constraints_critical: yes - name_constraints_permitted: - - "DNS:www.example.com" + name_constraints_permitted: '{{ value_for_name_constraints_permitted if select_crypto_backend != "pyopenssl" else value_for_name_constraints_permitted_pyopenssl }}' name_constraints_excluded: - "DNS:.org" - "DNS:.example.com" @@ -719,6 +724,13 @@ - "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:bob@localhost" - "dirName:O=Example Net,CN=example.net" - "dirName:/O = Example Com/CN = 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" + value_for_name_constraints_permitted_pyopenssl: + - "DNS:www.example.com" + - "IP:1.2.3.0/255.255.255.0" check_mode: yes register: everything_2 @@ -761,8 +773,7 @@ - "CA:TRUE" - "pathlen:23" basic_constraints_critical: yes - name_constraints_permitted: - - "DNS:www.example.com" + name_constraints_permitted: '{{ value_for_name_constraints_permitted if select_crypto_backend != "pyopenssl" else value_for_name_constraints_permitted_pyopenssl }}' name_constraints_excluded: - "DNS:.org" - "DNS:.example.com" @@ -822,6 +833,13 @@ - "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:bob@localhost" - "dirName:O =Example Net, CN= example.net" - "dirName:/O =Example Com/CN= 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" + value_for_name_constraints_permitted_pyopenssl: + - "DNS:www.example.com" + - "IP:1.2.3.0/255.255.255.0" register: everything_3 - name: Get info from CSR with everything diff --git a/tests/integration/targets/openssl_csr/tests/validate.yml b/tests/integration/targets/openssl_csr/tests/validate.yml index 3c5a0732c..bfbb9d87b 100644 --- a/tests/integration/targets/openssl_csr/tests/validate.yml +++ b/tests/integration/targets/openssl_csr/tests/validate.yml @@ -223,9 +223,6 @@ - everything_info.subject.userId == "asdf" - everything_info.subject | length == 16 - everything_info.subject_alt_name_critical == false - - everything_info.name_constraints_permitted == [ - "DNS:www.example.com", - ] - everything_info.name_constraints_excluded == [ "DNS:.example.com", "DNS:.org", @@ -257,6 +254,10 @@ "dvcs", "qcStatements", ] + - everything_info.name_constraints_permitted == [ + "DNS:www.example.com", + "IP:1.2.3.0/24", + ] when: select_crypto_backend == 'pyopenssl' - name: Check CSR with everything (non-pyOpenSSL specific) @@ -296,6 +297,11 @@ "dvcs", "qcStatements", ] + - everything_info.name_constraints_permitted == [ + "DNS:www.example.com", + "IP:1.2.3.0/24", + "IP:::1:0:0/112", + ] when: select_crypto_backend != 'pyopenssl' - name: Verify Ed25519 and Ed448 tests (for cryptography >= 2.6, < 2.8)