Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom validity dates over new X509 certificates #53149

Merged
merged 22 commits into from Apr 29, 2020
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bd37b41
Ported unit test for fix to new Salt master branch.
jeduardo Dec 21, 2019
869dba3
Ported fixes to allow not_before and not_after to master branch.
jeduardo Dec 21, 2019
f2c381d
Removed CSR tests as they are not available in master.
jeduardo Dec 21, 2019
f280978
Fixed test to ensure a string is passed to the write method.
jeduardo Dec 22, 2019
af84ea9
Lint fixes.
jeduardo Dec 22, 2019
f7e56cc
Merge branch 'master' into x509-specify-date-range
jeduardo Jan 2, 2020
38b590a
Merge branch 'master' into x509-specify-date-range
jeduardo Jan 5, 2020
ea80c18
Merge branch 'master' into x509-specify-date-range
jeduardo Jan 27, 2020
aa08e1b
Merge branch 'master' into x509-specify-date-range
jeduardo Feb 2, 2020
cddba82
Merge branch 'master' into x509-specify-date-range
jeduardo Mar 7, 2020
8d8d549
Merge branch 'master' into x509-specify-date-range
jeduardo Mar 14, 2020
f123bc9
Changed salt version.
jeduardo Apr 12, 2020
704811e
Merge branch 'master' into x509-specify-date-range
jeduardo Apr 16, 2020
a0ffdbd
Removed unused variable detected by lint
jeduardo Apr 16, 2020
1237d19
Merge branch 'master' into x509-specify-date-range
jeduardo Apr 17, 2020
56a7d4a
Code formatting changes.
jeduardo Apr 17, 2020
99c2ce4
Merge branch 'master' into x509-specify-date-range
jeduardo Apr 19, 2020
0391afa
Added integration tests for x509 state covering new functionality.
jeduardo Apr 24, 2020
7f884ac
Merge branch 'master' into x509-specify-date-range
jeduardo Apr 24, 2020
c164814
Merge branch 'master' into x509-specify-date-range
jeduardo Apr 28, 2020
4a2cb48
Removed deprecated options from documentation.
jeduardo Apr 28, 2020
0bfd52c
Merge branch 'master' into x509-specify-date-range
jeduardo Apr 29, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
62 changes: 60 additions & 2 deletions salt/modules/x509.py
Expand Up @@ -1381,6 +1381,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 @@ -1496,12 +1508,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
38 changes: 38 additions & 0 deletions salt/states/x509.py
Expand Up @@ -150,6 +150,33 @@
- CN: www.example.com
- days_remaining: 30
- backup: True
- managed_private_key:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

managed_private_key was removed on purpose here: https://github.com/saltstack/salt/pull/52935/files#diff-5499a295a50d60a761c34f4080e4014bL80 in favor of using the separate x509.private_key_managed state. Can you remove the reference here and on line 175.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Ch3LL, good catch. I remember seeing the change in the code but forgot to look for it in the docs. Just removed it, thanks!

name: /etc/pki/www.key
bits: 4096
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
- managed_private_key:
name: /etc/pki/www-time-limited.key
bits: 4096
backup: True

"""

# Import Python Libs
Expand Down Expand Up @@ -530,6 +557,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