diff --git a/salt/file/crypto/x509/boilerplate_certificate.py b/salt/file/crypto/x509/boilerplate_certificate.py index e1eb2cc..2fbf44f 100644 --- a/salt/file/crypto/x509/boilerplate_certificate.py +++ b/salt/file/crypto/x509/boilerplate_certificate.py @@ -28,11 +28,13 @@ self-signed CA certificate with a single child EE certificate, which seems to better match how PKIX and TLS are designed. -The CA certificate and its key are immediately discarded, leaving the EE -certificate and its key for use with TLS software that can be configured to -trust an EE certificate directly. If this script ever needs to be used with TLS -software that requires a CA to trust, it should be re-reviewed for that purpose, -and it would probably need some security improvements: +The CA certificate and its key are immediately discarded by default, leaving the +EE certificate and its key for use with TLS software that can be configured to +trust an EE certificate directly. The option to keep the CA certificate is +designed for software that can pin EE certificates but still needs the CA +certificate too. If this script ever needs to be used with TLS software that +requires a CA to trust without EE certificate pinning, it should be re-reviewed +for that purpose, and it would probably need some security improvements: The exposure surface of the CA's private key should be minimized to avoid an attacker surreptitiously creating new EE certs. mlockall(2) looks useful, though @@ -55,6 +57,7 @@ import argparse from collections.abc import Sequence import pathlib +import shutil import subprocess import tempfile import textwrap @@ -69,6 +72,11 @@ def _args(): required=True, help="DNS name for the EE certificate.", ) + parser.add_argument( + "--ca-cert", + type=pathlib.Path, + help="Path to write the CA certificate to.", + ) parser.add_argument( "--key", type=pathlib.Path, @@ -183,6 +191,8 @@ def main() -> None: input=ca_private_key, check=True, ) + if args.ca_cert is not None: + shutil.copyfile(tempdir.joinpath("ca-cert.pem"), args.ca_cert) subprocess.run( ("openssl", "genpkey", *genpkey_args, "-out", str(args.key)), diff --git a/salt/file/crypto/x509/map.jinja b/salt/file/crypto/x509/map.jinja index caa4bdf..08df7b3 100644 --- a/salt/file/crypto/x509/map.jinja +++ b/salt/file/crypto/x509/map.jinja @@ -24,7 +24,8 @@ name, warning_on_change, dir_name=none, - group='root') %} + group='root', + keep_ca_cert=false) %} {% if dir_name is none %} {% set dir_name = name %} {% endif %} @@ -36,6 +37,9 @@ boilerplate_certificate_{{ dir_name }}: && {{ common.local_bin }}/boilerplate-certificate --name={{ name }} + {%- if keep_ca_cert %} + --ca-cert={{ common.local_etc }}/x509/{{ dir_name }}/cacert.pem + {%- endif %} --key={{ common.local_etc }}/x509/{{ dir_name }}/privkey.pem --cert={{ common.local_etc }}/x509/{{ dir_name }}/cert.pem --key-algorithm={{ crypto.openssl.strict_key_algorithm }} @@ -43,12 +47,23 @@ boilerplate_certificate_{{ dir_name }}: --key-option={{ key_option }} {%- endfor %} --days={{ crypto.secret_validity_period_days }} + {%- if keep_ca_cert %} + && + echo CA: + && + cat {{ common.local_etc }}/x509/{{ dir_name }}/cacert.pem + {%- endif %} + && + echo EE: && openssl x509 -in {{ common.local_etc }}/x509/{{ dir_name }}/cert.pem -{{ crypto.openssl.digest }} -fingerprint - creates: + {%- if keep_ca_cert %} + - {{ common.local_etc }}/x509/{{ dir_name }}/cacert.pem + {%- endif %} - {{ common.local_etc }}/x509/{{ dir_name }}/privkey.pem - {{ common.local_etc }}/x509/{{ dir_name }}/cert.pem - require: @@ -59,6 +74,22 @@ boilerplate_certificate_{{ dir_name }}: - onchanges: - cmd: boilerplate_certificate_{{ dir_name }} +{% if keep_ca_cert %} +{{ common.local_etc }}/x509/{{ dir_name }}/cacert.pem: + file.exists: + - require: + - boilerplate_certificate_{{ dir_name }} +{{ common.local_etc }}/x509/{{ dir_name }}/cacert.pem should be rotated: + file.accumulated: + - name: boilerplate certificates + - filename: {{ common.local_sbin }}/monitor-x509-validity-period + - text: {{ common.local_etc }}/x509/{{ dir_name }}/cacert.pem + - require: + - {{ common.local_etc }}/x509/{{ dir_name }}/cacert.pem + - require_in: + - file: {{ common.local_sbin }}/monitor-x509-validity-period +{% endif %} + {{ common.local_etc }}/x509/{{ dir_name }}/privkey.pem: file.managed: - create: false