Skip to content

Commit

Permalink
Allow custom validity dates over new X509 certificates (#53149)
Browse files Browse the repository at this point in the history
* Ported unit test for fix to new Salt master branch.

* Ported fixes to allow not_before and not_after to master branch.

* Removed CSR tests as they are not available in master.

* Fixed test to ensure a string is passed to the write method.

* Lint fixes.

* Changed salt version.

* Removed unused variable detected by lint

* Code formatting changes.

* Added integration tests for x509 state covering new functionality.

* Removed deprecated options from documentation.
  • Loading branch information
jeduardo committed Apr 29, 2020
1 parent 7c37be4 commit ff4fd1e
Show file tree
Hide file tree
Showing 7 changed files with 612 additions and 187 deletions.
62 changes: 60 additions & 2 deletions salt/modules/x509.py
Expand Up @@ -1378,6 +1378,18 @@ def create_certificate(path=None, text=False, overwrite=True, ca_server=None, **
The above signing policy can be invoked with ``signing_policy=www``
not_before:
Initial validity date for the certificate. This date must be specified
in the format '%Y-%m-%d %H:%M:%S'.
.. versionadded:: Sodium
not_after:
Final validity date for the certificate. This date must be specified in
the format '%Y-%m-%d %H:%M:%S'.
.. versionadded:: Sodium
CLI Example:
.. code-block:: bash
Expand Down Expand Up @@ -1493,12 +1505,58 @@ def create_certificate(path=None, text=False, overwrite=True, ca_server=None, **
serial_number -= int(serial_number / sys.maxsize) * sys.maxsize
cert.set_serial_number(serial_number)

# Handle not_before and not_after dates for custom certificate validity
fmt = "%Y-%m-%d %H:%M:%S"
if "not_before" in kwargs:
try:
time = datetime.datetime.strptime(kwargs["not_before"], fmt)
except:
raise salt.exceptions.SaltInvocationError(
"not_before: {0} is not in required format {1}".format(
kwargs["not_before"], fmt
)
)

# If we do not set an explicit timezone to this naive datetime object,
# the M2Crypto code will assume it is from the local machine timezone
# and will try to adjust the time shift.
time = time.replace(tzinfo=M2Crypto.ASN1.UTC)
asn1_not_before = M2Crypto.ASN1.ASN1_UTCTIME()
asn1_not_before.set_datetime(time)
cert.set_not_before(asn1_not_before)

if "not_after" in kwargs:
try:
time = datetime.datetime.strptime(kwargs["not_after"], fmt)
except:
raise salt.exceptions.SaltInvocationError(
"not_after: {0} is not in required format {1}".format(
kwargs["not_after"], fmt
)
)

# Forcing the datetime to have an explicit tzinfo here as well.
time = time.replace(tzinfo=M2Crypto.ASN1.UTC)
asn1_not_after = M2Crypto.ASN1.ASN1_UTCTIME()
asn1_not_after.set_datetime(time)
cert.set_not_after(asn1_not_after)

# Set validity dates
# pylint: disable=no-member

# if no 'not_before' or 'not_after' dates are setup, both of the following
# dates will be the date of today. then the days_valid offset makes sense.

not_before = M2Crypto.m2.x509_get_not_before(cert.x509)
not_after = M2Crypto.m2.x509_get_not_after(cert.x509)
M2Crypto.m2.x509_gmtime_adj(not_before, 0)
M2Crypto.m2.x509_gmtime_adj(not_after, 60 * 60 * 24 * kwargs["days_valid"])

# Only process the dynamic dates if start and end are not specified.
if "not_before" not in kwargs:
M2Crypto.m2.x509_gmtime_adj(not_before, 0)
if "not_after" not in kwargs:
valid_seconds = 60 * 60 * 24 * kwargs["days_valid"] # 60s * 60m * 24 * days
M2Crypto.m2.x509_gmtime_adj(not_after, valid_seconds)

# pylint: enable=no-member

# If neither public_key or csr are included, this cert is self-signed
Expand Down
30 changes: 30 additions & 0 deletions salt/states/x509.py
Expand Up @@ -150,6 +150,25 @@
- CN: www.example.com
- days_remaining: 30
- backup: True
This other state creates a private key then requests a certificate signed by ca
according to the www policy but adds a strict date range for the certificate to
be considered valid.
/srv/salt/www-time-limited.sls
.. code-block:: yaml
/etc/pki/www-time-limited.crt:
x509.certificate_managed:
- ca_server: ca
- signing_policy: www
- public_key: /etc/pki/www-time-limited.key
- CN: www.example.com
- not_before: 2019-05-05 00:00:00
- not_after: 2020-05-05 14:30:00
- backup: True
"""

# Import Python Libs
Expand Down Expand Up @@ -530,6 +549,17 @@ def certificate_managed(
<salt.modules.x509.create_certificate>` or :py:func:`file.managed
<salt.states.file.managed>` are supported.
not_before:
Initial validity date for the certificate. This date must be specified
in the format '%Y-%m-%d %H:%M:%S'.
.. versionadded:: Sodium
not_after:
Final validity date for the certificate. This date must be specified in
the format '%Y-%m-%d %H:%M:%S'.
.. versionadded:: Sodium
Examples:
.. code-block:: yaml
Expand Down
66 changes: 66 additions & 0 deletions tests/integration/files/file/base/test_cert_not_after.sls
@@ -0,0 +1,66 @@
{% set tmp_dir = pillar['tmp_dir'] %}

{{ tmp_dir }}/pki:
file.directory

{{ tmp_dir }}/pki/issued_certs:
file.directory

{{ tmp_dir }}/pki/ca.key:
x509.private_key_managed:
- bits: 4096
- require:
- file: {{ tmp_dir }}/pki

{{ tmp_dir }}/pki/ca.crt:
x509.certificate_managed:
- signing_private_key: {{ tmp_dir }}/pki/ca.key
- CN: ca.example.com
- C: US
- ST: Utah
- L: Salt Lake City
- basicConstraints: "critical CA:true"
- keyUsage: "critical cRLSign, keyCertSign"
- subjectKeyIdentifier: hash
- authorityKeyIdentifier: keyid,issuer:always
- days_valid: 3650
- days_remaining: 0
- backup: True
- managed_private_key:
name: {{ tmp_dir }}/pki/ca.key
bits: 4096
backup: True
- require:
- file: {{ tmp_dir }}/pki
- {{ tmp_dir }}/pki/ca.key

mine.send:
module.run:
- func: x509.get_pem_entries
- kwargs:
glob_path: {{ tmp_dir }}/pki/ca.crt
- onchanges:
- x509: {{ tmp_dir }}/pki/ca.crt

{{ tmp_dir }}/pki/test.key:
x509.private_key_managed:
- bits: 4096
- backup: True

test_crt:
x509.certificate_managed:
- name: {{ tmp_dir }}/pki/test.crt
- ca_server: minion
- signing_policy: ca_policy
- public_key: {{ tmp_dir }}/pki/test.key
- CN: minion
- days_remaining: 30
- not_after: 2020-05-05 14:30:00
- backup: True
- managed_private_key:
name: {{ tmp_dir }}/pki/test.key
bits: 4096
backup: True
- require:
- {{ tmp_dir }}/pki/ca.crt
- {{ tmp_dir }}/pki/test.key
66 changes: 66 additions & 0 deletions tests/integration/files/file/base/test_cert_not_before.sls
@@ -0,0 +1,66 @@
{% set tmp_dir = pillar['tmp_dir'] %}

{{ tmp_dir }}/pki:
file.directory

{{ tmp_dir }}/pki/issued_certs:
file.directory

{{ tmp_dir }}/pki/ca.key:
x509.private_key_managed:
- bits: 4096
- require:
- file: {{ tmp_dir }}/pki

{{ tmp_dir }}/pki/ca.crt:
x509.certificate_managed:
- signing_private_key: {{ tmp_dir }}/pki/ca.key
- CN: ca.example.com
- C: US
- ST: Utah
- L: Salt Lake City
- basicConstraints: "critical CA:true"
- keyUsage: "critical cRLSign, keyCertSign"
- subjectKeyIdentifier: hash
- authorityKeyIdentifier: keyid,issuer:always
- days_valid: 3650
- days_remaining: 0
- backup: True
- managed_private_key:
name: {{ tmp_dir }}/pki/ca.key
bits: 4096
backup: True
- require:
- file: {{ tmp_dir }}/pki
- {{ tmp_dir }}/pki/ca.key

mine.send:
module.run:
- func: x509.get_pem_entries
- kwargs:
glob_path: {{ tmp_dir }}/pki/ca.crt
- onchanges:
- x509: {{ tmp_dir }}/pki/ca.crt

{{ tmp_dir }}/pki/test.key:
x509.private_key_managed:
- bits: 4096
- backup: True

test_crt:
x509.certificate_managed:
- name: {{ tmp_dir }}/pki/test.crt
- ca_server: minion
- signing_policy: ca_policy
- public_key: {{ tmp_dir }}/pki/test.key
- CN: minion
- days_remaining: 30
- not_before: 2019-05-05 00:00:00
- backup: True
- managed_private_key:
name: {{ tmp_dir }}/pki/test.key
bits: 4096
backup: True
- require:
- {{ tmp_dir }}/pki/ca.crt
- {{ tmp_dir }}/pki/test.key
@@ -0,0 +1,67 @@
{% set tmp_dir = pillar['tmp_dir'] %}

{{ tmp_dir }}/pki:
file.directory

{{ tmp_dir }}/pki/issued_certs:
file.directory

{{ tmp_dir }}/pki/ca.key:
x509.private_key_managed:
- bits: 4096
- require:
- file: {{ tmp_dir }}/pki

{{ tmp_dir }}/pki/ca.crt:
x509.certificate_managed:
- signing_private_key: {{ tmp_dir }}/pki/ca.key
- CN: ca.example.com
- C: US
- ST: Utah
- L: Salt Lake City
- basicConstraints: "critical CA:true"
- keyUsage: "critical cRLSign, keyCertSign"
- subjectKeyIdentifier: hash
- authorityKeyIdentifier: keyid,issuer:always
- days_valid: 3650
- days_remaining: 0
- backup: True
- managed_private_key:
name: {{ tmp_dir }}/pki/ca.key
bits: 4096
backup: True
- require:
- file: {{ tmp_dir }}/pki
- {{ tmp_dir }}/pki/ca.key

mine.send:
module.run:
- func: x509.get_pem_entries
- kwargs:
glob_path: {{ tmp_dir }}/pki/ca.crt
- onchanges:
- x509: {{ tmp_dir }}/pki/ca.crt

{{ tmp_dir }}/pki/test.key:
x509.private_key_managed:
- bits: 4096
- backup: True

test_crt:
x509.certificate_managed:
- name: {{ tmp_dir }}/pki/test.crt
- ca_server: minion
- signing_policy: ca_policy
- public_key: {{ tmp_dir }}/pki/test.key
- CN: minion
- days_remaining: 30
- not_before: 2019-05-05 00:00:00
- not_after: 2020-05-05 14:30:00
- backup: True
- managed_private_key:
name: {{ tmp_dir }}/pki/test.key
bits: 4096
backup: True
- require:
- {{ tmp_dir }}/pki/ca.crt
- {{ tmp_dir }}/pki/test.key
54 changes: 54 additions & 0 deletions tests/integration/states/test_x509.py
Expand Up @@ -115,6 +115,60 @@ def test_cert_signing(self):
assert "Certificate" in ret[key]["changes"]
assert "New" in ret[key]["changes"]["Certificate"]

def test_cert_issue_not_before_not_after(self):
ret = self.run_function(
"state.apply",
["test_cert_not_before_not_after"],
pillar={"tmp_dir": RUNTIME_VARS.TMP},
)
key = "x509_|-test_crt_|-{}/pki/test.crt_|-certificate_managed".format(
RUNTIME_VARS.TMP
)
assert key in ret
assert "changes" in ret[key]
assert "Certificate" in ret[key]["changes"]
assert "New" in ret[key]["changes"]["Certificate"]
assert "Not Before" in ret[key]["changes"]["Certificate"]["New"]
assert "Not After" in ret[key]["changes"]["Certificate"]["New"]
not_before = ret[key]["changes"]["Certificate"]["New"]["Not Before"]
not_after = ret[key]["changes"]["Certificate"]["New"]["Not After"]
assert not_before == "2019-05-05 00:00:00"
assert not_after == "2020-05-05 14:30:00"

def test_cert_issue_not_before(self):
ret = self.run_function(
"state.apply",
["test_cert_not_before"],
pillar={"tmp_dir": RUNTIME_VARS.TMP},
)
key = "x509_|-test_crt_|-{}/pki/test.crt_|-certificate_managed".format(
RUNTIME_VARS.TMP
)
assert key in ret
assert "changes" in ret[key]
assert "Certificate" in ret[key]["changes"]
assert "New" in ret[key]["changes"]["Certificate"]
assert "Not Before" in ret[key]["changes"]["Certificate"]["New"]
assert "Not After" in ret[key]["changes"]["Certificate"]["New"]
not_before = ret[key]["changes"]["Certificate"]["New"]["Not Before"]
assert not_before == "2019-05-05 00:00:00"

def test_cert_issue_not_after(self):
ret = self.run_function(
"state.apply", ["test_cert_not_after"], pillar={"tmp_dir": RUNTIME_VARS.TMP}
)
key = "x509_|-test_crt_|-{}/pki/test.crt_|-certificate_managed".format(
RUNTIME_VARS.TMP
)
assert key in ret
assert "changes" in ret[key]
assert "Certificate" in ret[key]["changes"]
assert "New" in ret[key]["changes"]["Certificate"]
assert "Not Before" in ret[key]["changes"]["Certificate"]["New"]
assert "Not After" in ret[key]["changes"]["Certificate"]["New"]
not_after = ret[key]["changes"]["Certificate"]["New"]["Not After"]
assert not_after == "2020-05-05 14:30:00"

@with_tempfile(suffix=".crt", create=False)
@with_tempfile(suffix=".key", create=False)
def test_self_signed_cert(self, keyfile, crtfile):
Expand Down

0 comments on commit ff4fd1e

Please sign in to comment.