diff --git a/salt/modules/x509.py b/salt/modules/x509.py index a68cea67030a..019c9b31a4b0 100644 --- a/salt/modules/x509.py +++ b/salt/modules/x509.py @@ -206,9 +206,10 @@ def _get_csr_extensions(csr): return ret for short_name, long_name in six.iteritems(EXT_NAME_MAPPINGS): - if csrexts and long_name in csrexts: - ret[short_name] = csrexts[long_name] - + if long_name in csrexts: + csrexts[short_name] = csrexts[long_name] + del csrexts[long_name] + ret = csrexts return ret @@ -1336,6 +1337,13 @@ def create_certificate( The above signing policy can be invoked with ``signing_policy=www`` + ext_mapping: + Provide additional X509v3 extension mappings. This argument should be + in the form of a dictionary and should include both the OID and the + friendly name for the extension. + + .. versionadded:: Neon + CLI Example: .. code-block:: bash @@ -1475,6 +1483,9 @@ def create_certificate( signing_cert = cert cert.set_issuer(signing_cert.get_subject()) + if 'ext_mapping' in kwargs: + EXT_NAME_MAPPINGS.update(kwargs['ext_mapping']) + for extname, extlongname in six.iteritems(EXT_NAME_MAPPINGS): if (extname in kwargs or extlongname in kwargs or extname in csrexts or extlongname in csrexts) is False: @@ -1579,6 +1590,13 @@ def create_csr(path=None, text=False, **kwargs): :mod:`x509.create_certificate ` can be used. + ext_mapping: + Provide additional X509v3 extension mappings. This argument should be + in the form of a dictionary and should include both the OID and the + friendly name for the extension. + + .. versionadded:: Neon + CLI Example: .. code-block:: bash @@ -1633,12 +1651,15 @@ def create_csr(path=None, text=False, **kwargs): setattr(subject, entry, kwargs[entry]) # pylint: enable=unused-variable + if 'ext_mapping' in kwargs: + EXT_NAME_MAPPINGS.update(kwargs['ext_mapping']) + extstack = M2Crypto.X509.X509_Extension_Stack() for extname, extlongname in six.iteritems(EXT_NAME_MAPPINGS): if extname not in kwargs and extlongname not in kwargs: continue - extval = kwargs[extname] or kwargs[extlongname] + extval = kwargs.get(extname, None) or kwargs.get(extlongname, None) critical = False if extval.startswith('critical '): diff --git a/salt/states/x509.py b/salt/states/x509.py index 75f1fe570825..cce00dfdc202 100644 --- a/salt/states/x509.py +++ b/salt/states/x509.py @@ -333,6 +333,13 @@ def csr_managed(name, kwargs: Any arguments supported by :py:func:`file.managed ` are supported. + ext_mapping: + Provide additional X509v3 extension mappings. This argument should be + in the form of a dictionary and should include both the OID and the + friendly name for the extension. + + .. versionadded:: Neon + Example: .. code-block:: yaml @@ -345,6 +352,19 @@ def csr_managed(name, - ST: Utah - L: Salt Lake City - keyUsage: 'critical dataEncipherment' + + /etc/pki/mycert.csr: + x509.csr_managed: + - private_key: /etc/pki/mycert.key + - CN: www.example.com + - C: US + - ST: Utah + - L: Salt Lake City + - keyUsage: 'critical dataEncipherment' + - DomainController: 'ASN1:UTF8String:SomeOneSomeWhere' + - ext_mapping: + '1.3.6.1.4.1.311.20.2': 'DomainController' + ''' try: old = __salt__['x509.read_csr'](name) @@ -393,6 +413,13 @@ def certificate_managed(name, ` or :py:func:`file.managed ` are supported. + ext_mapping: + Provide additional X509v3 extension mappings. This argument should be + in the form of a dictionary and should include both the OID and the + friendly name for the extension. + + .. versionadded:: Neon + Examples: .. code-block:: yaml diff --git a/tests/unit/modules/test_x509.py b/tests/unit/modules/test_x509.py index 1b1ac5c2bc9f..b0439ed9da5b 100644 --- a/tests/unit/modules/test_x509.py +++ b/tests/unit/modules/test_x509.py @@ -35,6 +35,7 @@ ) from salt.modules import x509 +from salt.modules import cmdmod import salt.utils.stringutils import salt.utils.files @@ -50,7 +51,14 @@ class X509TestCase(TestCase, LoaderModuleMockMixin): def setup_loader_modules(self): - return {x509: {}} + module_globals = { + x509: { + '__salt__': { + 'cmd.run_stdout': cmdmod.run_stdout + } + } + } + return module_globals @patch('salt.modules.x509.log', MagicMock()) def test_private_func__parse_subject(self): @@ -342,3 +350,67 @@ def test_revoke_certificate_with_crl(self): # Ensure that the correct server cert serial is amongst # the revoked certificates self.assertIn(serial_number, crl) + + @skipIf(not HAS_M2CRYPTO, 'Skipping, M2Crypto is unavailble') + def test_create_csr(self): + ''' + Test create_csr + :return: + ''' + ca_key = ''' +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQCjdjbgL4kQ8Lu73xeRRM1q3C3K3ptfCLpyfw38LRnymxaoJ6ls +pNSx2dU1uJ89YKFlYLo1QcEk4rJ2fdIjarV0kuNCY3rC8jYUp9BpAU5Z6p9HKeT1 +2rTPH81JyjbQDR5PyfCyzYOQtpwpB4zIUUK/Go7tTm409xGKbbUFugJNgQIDAQAB +AoGAF24we34U1ZrMLifSRv5nu3OIFNZHyx2DLDpOFOGaII5edwgIXwxZeIzS5Ppr +yO568/8jcdLVDqZ4EkgCwRTgoXRq3a1GLHGFmBdDNvWjSTTMLoozuM0t2zjRmIsH +hUd7tnai9Lf1Bp5HlBEhBU2gZWk+SXqLvxXe74/+BDAj7gECQQDRw1OPsrgTvs3R +3MNwX6W8+iBYMTGjn6f/6rvEzUs/k6rwJluV7n8ISNUIAxoPy5g5vEYK6Ln/Ttc7 +u0K1KNlRAkEAx34qcxjuswavL3biNGE+8LpDJnJx1jaNWoH+ObuzYCCVMusdT2gy +kKuq9ytTDgXd2qwZpIDNmscvReFy10glMQJAXebMz3U4Bk7SIHJtYy7OKQzn0dMj +35WnRV81c2Jbnzhhu2PQeAvt/i1sgEuzLQL9QEtSJ6wLJ4mJvImV0TdaIQJAAYyk +TcKK0A8kOy0kMp3yvDHmJZ1L7wr7bBGIZPBlQ0Ddh8i1sJExm1gJ+uN2QKyg/XrK +tDFf52zWnCdVGgDwcQJALW/WcbSEK+JVV6KDJYpwCzWpKIKpBI0F6fdCr1G7Xcwj +c9bcgp7D7xD+TxWWNj4CSXEccJgGr91StV+gFg4ARQ== +-----END RSA PRIVATE KEY----- +''' + + ret = x509.create_csr(text=True, + public_key=ca_key, + CN='Redacted Root CA') + self.assertIn(b'BEGIN CERTIFICATE REQUEST', ret) + + @skipIf(not HAS_M2CRYPTO, 'Skipping, M2Crypto is unavailble') + def test_create_csr_ext_mapping(self): + ''' + Test create_csr with ext_mapping + :return: + ''' + ca_key = ''' +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQCjdjbgL4kQ8Lu73xeRRM1q3C3K3ptfCLpyfw38LRnymxaoJ6ls +pNSx2dU1uJ89YKFlYLo1QcEk4rJ2fdIjarV0kuNCY3rC8jYUp9BpAU5Z6p9HKeT1 +2rTPH81JyjbQDR5PyfCyzYOQtpwpB4zIUUK/Go7tTm409xGKbbUFugJNgQIDAQAB +AoGAF24we34U1ZrMLifSRv5nu3OIFNZHyx2DLDpOFOGaII5edwgIXwxZeIzS5Ppr +yO568/8jcdLVDqZ4EkgCwRTgoXRq3a1GLHGFmBdDNvWjSTTMLoozuM0t2zjRmIsH +hUd7tnai9Lf1Bp5HlBEhBU2gZWk+SXqLvxXe74/+BDAj7gECQQDRw1OPsrgTvs3R +3MNwX6W8+iBYMTGjn6f/6rvEzUs/k6rwJluV7n8ISNUIAxoPy5g5vEYK6Ln/Ttc7 +u0K1KNlRAkEAx34qcxjuswavL3biNGE+8LpDJnJx1jaNWoH+ObuzYCCVMusdT2gy +kKuq9ytTDgXd2qwZpIDNmscvReFy10glMQJAXebMz3U4Bk7SIHJtYy7OKQzn0dMj +35WnRV81c2Jbnzhhu2PQeAvt/i1sgEuzLQL9QEtSJ6wLJ4mJvImV0TdaIQJAAYyk +TcKK0A8kOy0kMp3yvDHmJZ1L7wr7bBGIZPBlQ0Ddh8i1sJExm1gJ+uN2QKyg/XrK +tDFf52zWnCdVGgDwcQJALW/WcbSEK+JVV6KDJYpwCzWpKIKpBI0F6fdCr1G7Xcwj +c9bcgp7D7xD+TxWWNj4CSXEccJgGr91StV+gFg4ARQ== +-----END RSA PRIVATE KEY----- +''' + + ret = x509.create_csr(text=True, + public_key=ca_key, + CN='Redacted Root CA', + DomainController='ASN1:UTF8String:SomeOneSomeWhere', + ext_mapping={'1.3.6.1.4.1.311.20.2': 'DomainController'}) + + self.assertIn(b'BEGIN CERTIFICATE REQUEST', ret) + + ret = x509.read_csr(ret) + self.assertIn(b'1.3.6.1.4.1.311.20.2', ret['X509v3 Extensions'])