"Fossies" - the Fresh Open Source Software Archive

Member "salt-3002.2/salt/states/x509.py" (18 Nov 2020, 28056 Bytes) of package /linux/misc/salt-3002.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "x509.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3002.1_vs_3002.2.

    1 """
    2 Manage X509 Certificates
    3 
    4 .. versionadded:: 2015.8.0
    5 
    6 :depends: M2Crypto
    7 
    8 This module can enable managing a complete PKI infrastructure including creating private keys, CAs,
    9 certificates and CRLs. It includes the ability to generate a private key on a server, and have the
   10 corresponding public key sent to a remote CA to create a CA signed certificate. This can be done in
   11 a secure manner, where private keys are always generated locally and never moved across the network.
   12 
   13 Here is a simple example scenario. In this example ``ca`` is the ca server,
   14 and ``www`` is a web server that needs a certificate signed by ``ca``.
   15 
   16 For remote signing, peers must be permitted to remotely call the
   17 :mod:`sign_remote_certificate <salt.modules.x509.sign_remote_certificate>` function.
   18 
   19 
   20 /etc/salt/master.d/peer.conf
   21 
   22 .. code-block:: yaml
   23 
   24     peer:
   25       .*:
   26         - x509.sign_remote_certificate
   27 
   28 
   29 /srv/salt/top.sls
   30 
   31 .. code-block:: yaml
   32 
   33     base:
   34       '*':
   35         - cert
   36       'ca':
   37         - ca
   38       'www':
   39         - www
   40 
   41 
   42 This state creates the CA key, certificate and signing policy. It also publishes the certificate to
   43 the mine where it can be easily retrieved by other minions.
   44 
   45 /srv/salt/ca.sls
   46 
   47 .. code-block:: yaml
   48 
   49     salt-minion:
   50       service.running:
   51         - enable: True
   52         - watch:
   53           - file: /etc/salt/minion.d/x509.conf
   54 
   55     /etc/salt/minion.d/x509.conf:
   56       file.managed:
   57         - source: salt://x509.conf
   58 
   59     /etc/pki:
   60       file.directory
   61 
   62     /etc/pki/issued_certs:
   63       file.directory
   64 
   65     /etc/pki/ca.crt:
   66       x509.private_key_managed:
   67         - name: /etc/pki/ca.key
   68         - bits: 4096
   69         - backup: True
   70 
   71     /etc/pki/ca.crt:
   72       x509.certificate_managed:
   73         - signing_private_key: /etc/pki/ca.key
   74         - CN: ca.example.com
   75         - C: US
   76         - ST: Utah
   77         - L: Salt Lake City
   78         - basicConstraints: "critical CA:true"
   79         - keyUsage: "critical cRLSign, keyCertSign"
   80         - subjectKeyIdentifier: hash
   81         - authorityKeyIdentifier: keyid,issuer:always
   82         - days_valid: 3650
   83         - days_remaining: 0
   84         - backup: True
   85         - require:
   86           - file: /etc/pki
   87 
   88 
   89 The signing policy defines properties that override any property requested or included in a CRL. It also
   90 can define a restricted list of minions which are allowed to remotely invoke this signing policy.
   91 
   92 /srv/salt/x509.conf
   93 
   94 .. code-block:: yaml
   95 
   96     mine_functions:
   97       x509.get_pem_entries: [/etc/pki/ca.crt]
   98 
   99     x509_signing_policies:
  100       www:
  101         - minions: 'www'
  102         - signing_private_key: /etc/pki/ca.key
  103         - signing_cert: /etc/pki/ca.crt
  104         - C: US
  105         - ST: Utah
  106         - L: Salt Lake City
  107         - basicConstraints: "critical CA:false"
  108         - keyUsage: "critical keyEncipherment"
  109         - subjectKeyIdentifier: hash
  110         - authorityKeyIdentifier: keyid,issuer:always
  111         - days_valid: 90
  112         - copypath: /etc/pki/issued_certs/
  113 
  114 
  115 This state will instruct all minions to trust certificates signed by our new CA.
  116 Using Jinja to strip newlines from the text avoids dealing with newlines in the rendered YAML,
  117 and the  :mod:`sign_remote_certificate <salt.states.x509.sign_remote_certificate>` state will
  118 handle properly formatting the text before writing the output.
  119 
  120 /srv/salt/cert.sls
  121 
  122 .. code-block:: jinja
  123 
  124     /usr/local/share/ca-certificates:
  125       file.directory
  126 
  127     /usr/local/share/ca-certificates/intca.crt:
  128       x509.pem_managed:
  129         - text: {{ salt['mine.get']('ca', 'x509.get_pem_entries')['ca']['/etc/pki/ca.crt']|replace('\\n', '') }}
  130 
  131 
  132 This state creates a private key then requests a certificate signed by ca according to the www policy.
  133 
  134 /srv/salt/www.sls
  135 
  136 .. code-block:: yaml
  137 
  138     /etc/pki/www.crt:
  139       x509.private_key_managed:
  140         - name: /etc/pki/www.key
  141         - bits: 4096
  142         - backup: True
  143 
  144     /etc/pki/www.crt:
  145       x509.certificate_managed:
  146         - ca_server: ca
  147         - signing_policy: www
  148         - public_key: /etc/pki/www.key
  149         - CN: www.example.com
  150         - days_remaining: 30
  151         - backup: True
  152 
  153 This other state creates a private key then requests a certificate signed by ca
  154 according to the www policy but adds a strict date range for the certificate to
  155 be considered valid.
  156 
  157 /srv/salt/www-time-limited.sls
  158 
  159 .. code-block:: yaml
  160 
  161     /etc/pki/www-time-limited.crt:
  162       x509.certificate_managed:
  163         - ca_server: ca
  164         - signing_policy: www
  165         - public_key: /etc/pki/www-time-limited.key
  166         - CN: www.example.com
  167         - not_before: 2019-05-05 00:00:00
  168         - not_after: 2020-05-05 14:30:00
  169         - backup: True
  170 
  171 """
  172 
  173 import copy
  174 import datetime
  175 import logging
  176 import os
  177 import re
  178 
  179 import salt.exceptions
  180 import salt.utils.versions
  181 
  182 try:
  183     from M2Crypto.RSA import RSAError
  184 except ImportError:
  185     pass
  186 
  187 log = logging.getLogger(__name__)
  188 
  189 
  190 def __virtual__():
  191     """
  192     only load this module if the corresponding execution module is loaded
  193     """
  194     if "x509.get_pem_entry" in __salt__:
  195         return "x509"
  196     else:
  197         return (False, "Could not load x509 state: m2crypto unavailable")
  198 
  199 
  200 def _revoked_to_list(revs):
  201     """
  202     Turn the mess of OrderedDicts and Lists into a list of dicts for
  203     use in the CRL module.
  204     """
  205     list_ = []
  206 
  207     for rev in revs:
  208         for props in rev.values():
  209             dict_ = {}
  210             for prop in props:
  211                 for propname, val in prop.items():
  212                     if isinstance(val, datetime.datetime):
  213                         val = val.strftime("%Y-%m-%d %H:%M:%S")
  214                     dict_[propname] = val
  215             list_.append(dict_)
  216 
  217     return list_
  218 
  219 
  220 def _get_file_args(name, **kwargs):
  221     valid_file_args = [
  222         "user",
  223         "group",
  224         "mode",
  225         "makedirs",
  226         "dir_mode",
  227         "backup",
  228         "create",
  229         "follow_symlinks",
  230         "check_cmd",
  231     ]
  232     file_args = {}
  233     extra_args = {}
  234     for k, v in kwargs.items():
  235         if k in valid_file_args:
  236             file_args[k] = v
  237         else:
  238             extra_args[k] = v
  239     file_args["name"] = name
  240     return file_args, extra_args
  241 
  242 
  243 def _check_private_key(name, bits=2048, passphrase=None, new=False, overwrite=False):
  244     current_bits = 0
  245     if os.path.isfile(name):
  246         try:
  247             current_bits = __salt__["x509.get_private_key_size"](
  248                 private_key=name, passphrase=passphrase
  249             )
  250         except salt.exceptions.SaltInvocationError:
  251             pass
  252         except RSAError:
  253             if not overwrite:
  254                 raise salt.exceptions.CommandExecutionError(
  255                     "The provided passphrase cannot decrypt the private key."
  256                 )
  257 
  258     return current_bits == bits and not new
  259 
  260 
  261 def private_key_managed(
  262     name,
  263     bits=2048,
  264     passphrase=None,
  265     cipher="aes_128_cbc",
  266     new=False,
  267     overwrite=False,
  268     verbose=True,
  269     **kwargs
  270 ):
  271     """
  272     Manage a private key's existence.
  273 
  274     name:
  275         Path to the private key
  276 
  277     bits:
  278         Key length in bits. Default 2048.
  279 
  280     passphrase:
  281         Passphrase for encrypting the private key.
  282 
  283     cipher:
  284         Cipher for encrypting the private key.
  285 
  286     new:
  287         Always create a new key. Defaults to ``False``.
  288         Combining new with :mod:`prereq <salt.states.requsities.preqreq>`
  289         can allow key rotation whenever a new certificate is generated.
  290 
  291     overwrite:
  292         Overwrite an existing private key if the provided passphrase cannot decrypt it.
  293 
  294     verbose:
  295         Provide visual feedback on stdout, dots while key is generated.
  296         Default is True.
  297 
  298         .. versionadded:: 2016.11.0
  299 
  300     kwargs:
  301         Any kwargs supported by file.managed are supported.
  302 
  303     Example:
  304 
  305     The JINJA templating in this example ensures a private key is generated if the file doesn't exist
  306     and that a new private key is generated whenever the certificate that uses it is to be renewed.
  307 
  308     .. code-block:: jinja
  309 
  310         /etc/pki/www.key:
  311           x509.private_key_managed:
  312             - bits: 4096
  313             - new: True
  314             {% if salt['file.file_exists']('/etc/pki/www.key') -%}
  315             - prereq:
  316               - x509: /etc/pki/www.crt
  317             {%- endif %}
  318     """
  319     file_args, kwargs = _get_file_args(name, **kwargs)
  320     new_key = False
  321     if _check_private_key(
  322         name, bits=bits, passphrase=passphrase, new=new, overwrite=overwrite
  323     ):
  324         file_args["contents"] = __salt__["x509.get_pem_entry"](
  325             name, pem_type="RSA PRIVATE KEY"
  326         )
  327     else:
  328         new_key = True
  329         file_args["contents"] = __salt__["x509.create_private_key"](
  330             text=True, bits=bits, passphrase=passphrase, cipher=cipher, verbose=verbose
  331         )
  332 
  333     # Ensure the key contents are a string before passing it along
  334     file_args["contents"] = salt.utils.stringutils.to_str(file_args["contents"])
  335 
  336     ret = __states__["file.managed"](**file_args)
  337     if ret["changes"] and new_key:
  338         ret["changes"] = {"new": "New private key generated"}
  339 
  340     return ret
  341 
  342 
  343 def csr_managed(name, **kwargs):
  344     """
  345     Manage a Certificate Signing Request
  346 
  347     name:
  348         Path to the CSR
  349 
  350     properties:
  351         The properties to be added to the certificate request, including items like subject, extensions
  352         and public key. See above for valid properties.
  353 
  354     kwargs:
  355         Any arguments supported by :py:func:`file.managed <salt.states.file.managed>` are supported.
  356 
  357     Example:
  358 
  359     .. code-block:: yaml
  360 
  361         /etc/pki/mycert.csr:
  362           x509.csr_managed:
  363              - private_key: /etc/pki/mycert.key
  364              - CN: www.example.com
  365              - C: US
  366              - ST: Utah
  367              - L: Salt Lake City
  368              - keyUsage: 'critical dataEncipherment'
  369     """
  370     try:
  371         old = __salt__["x509.read_csr"](name)
  372     except salt.exceptions.SaltInvocationError:
  373         old = "{} is not a valid csr.".format(name)
  374 
  375     file_args, kwargs = _get_file_args(name, **kwargs)
  376     file_args["contents"] = __salt__["x509.create_csr"](text=True, **kwargs)
  377 
  378     ret = __states__["file.managed"](**file_args)
  379     if ret["changes"]:
  380         new = __salt__["x509.read_csr"](file_args["contents"])
  381         if old != new:
  382             ret["changes"] = {"Old": old, "New": new}
  383 
  384     return ret
  385 
  386 
  387 def _certificate_info_matches(cert_info, required_cert_info, check_serial=False):
  388     """
  389     Return true if the provided certificate information matches the
  390     required certificate information, i.e. it has the required common
  391     name, subject alt name, organization, etc.
  392 
  393     cert_info should be a dict as returned by x509.read_certificate.
  394     required_cert_info should be a dict as returned by x509.create_certificate with testrun=True.
  395     """
  396     # don't modify the incoming dicts
  397     cert_info = copy.deepcopy(cert_info)
  398     required_cert_info = copy.deepcopy(required_cert_info)
  399 
  400     ignored_keys = [
  401         "Not Before",
  402         "Not After",
  403         "MD5 Finger Print",
  404         "SHA1 Finger Print",
  405         "SHA-256 Finger Print",
  406         # The integrity of the issuer is checked elsewhere
  407         "Issuer Public Key",
  408     ]
  409     for key in ignored_keys:
  410         cert_info.pop(key, None)
  411         required_cert_info.pop(key, None)
  412 
  413     if not check_serial:
  414         cert_info.pop("Serial Number", None)
  415         required_cert_info.pop("Serial Number", None)
  416         try:
  417             cert_info["X509v3 Extensions"]["authorityKeyIdentifier"] = re.sub(
  418                 r"serial:([0-9A-F]{2}:)*[0-9A-F]{2}",
  419                 "serial:--",
  420                 cert_info["X509v3 Extensions"]["authorityKeyIdentifier"],
  421             )
  422             required_cert_info["X509v3 Extensions"]["authorityKeyIdentifier"] = re.sub(
  423                 r"serial:([0-9A-F]{2}:)*[0-9A-F]{2}",
  424                 "serial:--",
  425                 required_cert_info["X509v3 Extensions"]["authorityKeyIdentifier"],
  426             )
  427         except KeyError:
  428             pass
  429 
  430     diff = []
  431     for k, v in required_cert_info.items():
  432         # cert info comes as byte string
  433         if isinstance(v, str):
  434             v = salt.utils.stringutils.to_bytes(v)
  435         try:
  436             if v != cert_info[k]:
  437                 if k == "Subject Hash":
  438                     # If we failed the subject hash check but the subject matches, then this is
  439                     # likely a certificate generated under Python 2 where sorting differs and thus
  440                     # the hash also differs
  441                     if required_cert_info["Subject"] != cert_info["Subject"]:
  442                         diff.append(k)
  443                 elif k == "Issuer Hash":
  444                     # If we failed the issuer hash check but the issuer matches, then this is
  445                     # likely a certificate generated under Python 2 where sorting differs and thus
  446                     # the hash also differs
  447                     if required_cert_info["Issuer"] != cert_info["Issuer"]:
  448                         diff.append(k)
  449                 elif k == "X509v3 Extensions":
  450                     v_ext = v.copy()
  451                     cert_info_ext = cert_info[k].copy()
  452                     # DirName depends on ordering which was different on certificates created
  453                     # under Python 2. Remove that from the comparisson
  454                     try:
  455                         v_ext["authorityKeyIdentifier"] = re.sub(
  456                             r"DirName:([^\n]+)",
  457                             "Dirname:--",
  458                             v_ext["authorityKeyIdentifier"],
  459                         )
  460                         cert_info_ext["authorityKeyIdentifier"] = re.sub(
  461                             r"DirName:([^\n]+)",
  462                             "Dirname:--",
  463                             cert_info_ext["authorityKeyIdentifier"],
  464                         )
  465                     except KeyError:
  466                         pass
  467                     if v_ext != cert_info_ext:
  468                         diff.append(k)
  469                 else:
  470                     diff.append(k)
  471         except KeyError:
  472             diff.append(k)
  473 
  474     return len(diff) == 0, diff
  475 
  476 
  477 def _certificate_days_remaining(cert_info):
  478     """
  479     Get the days remaining on a certificate, defaulting to 0 if an error occurs.
  480     """
  481     try:
  482         expiry = cert_info["Not After"]
  483         return (
  484             datetime.datetime.strptime(expiry, "%Y-%m-%d %H:%M:%S")
  485             - datetime.datetime.now()
  486         ).days
  487     except KeyError:
  488         return 0
  489 
  490 
  491 def _certificate_is_valid(name, days_remaining, append_certs, **cert_spec):
  492     """
  493     Return True if the given certificate file exists, is a certificate, matches the given specification,
  494     and has the required days remaining.
  495 
  496     If False, also provide a message explaining why.
  497     """
  498     if not os.path.isfile(name):
  499         return False, "{} does not exist".format(name), {}
  500 
  501     try:
  502         cert_info = __salt__["x509.read_certificate"](certificate=name)
  503         required_cert_info = __salt__["x509.create_certificate"](
  504             testrun=True, **cert_spec
  505         )
  506         if not isinstance(required_cert_info, dict):
  507             raise salt.exceptions.SaltInvocationError(
  508                 "Unable to create new certificate: x509 module error: {}".format(
  509                     required_cert_info
  510                 )
  511             )
  512 
  513         try:
  514             issuer_public_key = required_cert_info["Issuer Public Key"]
  515             # Verify the certificate has been signed by the ca_server or private_signing_key
  516             if not __salt__["x509.verify_signature"](name, issuer_public_key):
  517                 errmsg = (
  518                     "Certificate is not signed by private_signing_key"
  519                     if "signing_private_key" in cert_spec
  520                     else "Certificate is not signed by the requested issuer"
  521                 )
  522                 return False, errmsg, cert_info
  523         except KeyError:
  524             return (
  525                 False,
  526                 "New certificate does not include signing information",
  527                 cert_info,
  528             )
  529 
  530         matches, diff = _certificate_info_matches(
  531             cert_info, required_cert_info, check_serial="serial_number" in cert_spec
  532         )
  533         if not matches:
  534             return (
  535                 False,
  536                 "Certificate properties are different: {}".format(", ".join(diff)),
  537                 cert_info,
  538             )
  539 
  540         actual_days_remaining = _certificate_days_remaining(cert_info)
  541         if days_remaining != 0 and actual_days_remaining < days_remaining:
  542             return (
  543                 False,
  544                 "Certificate needs renewal: {} days remaining but it needs to be at least {}".format(
  545                     actual_days_remaining, days_remaining
  546                 ),
  547                 cert_info,
  548             )
  549 
  550         return True, "", cert_info
  551     except salt.exceptions.SaltInvocationError as e:
  552         return False, "{} is not a valid certificate: {}".format(name, str(e)), {}
  553 
  554 
  555 def _certificate_file_managed(ret, file_args):
  556     """
  557     Run file.managed and merge the result with an existing return dict.
  558     The overall True/False result will be the result of the file.managed call.
  559     """
  560     file_ret = __states__["file.managed"](**file_args)
  561 
  562     ret["result"] = file_ret["result"]
  563     if ret["result"]:
  564         ret["comment"] = "Certificate {} is valid and up to date".format(ret["name"])
  565     else:
  566         ret["comment"] = file_ret["comment"]
  567 
  568     if file_ret["changes"]:
  569         ret["changes"] = {"File": file_ret["changes"]}
  570 
  571     return ret
  572 
  573 
  574 def certificate_managed(
  575     name, days_remaining=90, append_certs=None, managed_private_key=None, **kwargs
  576 ):
  577     """
  578     Manage a Certificate
  579 
  580     name
  581         Path to the certificate
  582 
  583     days_remaining : 90
  584         Recreate the certificate if the number of days remaining on it
  585         are less than this number. The value should be less than
  586         ``days_valid``, otherwise the certificate will be recreated
  587         every time the state is run. A value of 0 disables automatic
  588         renewal.
  589 
  590     append_certs:
  591         A list of certificates to be appended to the managed file.
  592         They must be valid PEM files, otherwise an error will be thrown.
  593 
  594     managed_private_key:
  595         Has no effect since v2016.11 and will be removed in Salt Aluminium.
  596         Use a separate x509.private_key_managed call instead.
  597 
  598     kwargs:
  599         Any arguments supported by :py:func:`x509.create_certificate
  600         <salt.modules.x509.create_certificate>` or :py:func:`file.managed
  601         <salt.states.file.managed>` are supported.
  602 
  603     not_before:
  604         Initial validity date for the certificate. This date must be specified
  605         in the format '%Y-%m-%d %H:%M:%S'.
  606 
  607         .. versionadded:: 3001
  608     not_after:
  609         Final validity date for the certificate. This date must be specified in
  610         the format '%Y-%m-%d %H:%M:%S'.
  611 
  612         .. versionadded:: 3001
  613 
  614     Examples:
  615 
  616     .. code-block:: yaml
  617 
  618         /etc/pki/ca.crt:
  619           x509.certificate_managed:
  620             - signing_private_key: /etc/pki/ca.key
  621             - CN: ca.example.com
  622             - C: US
  623             - ST: Utah
  624             - L: Salt Lake City
  625             - basicConstraints: "critical CA:true"
  626             - keyUsage: "critical cRLSign, keyCertSign"
  627             - subjectKeyIdentifier: hash
  628             - authorityKeyIdentifier: keyid,issuer:always
  629             - days_valid: 3650
  630             - days_remaining: 0
  631             - backup: True
  632 
  633 
  634     .. code-block:: yaml
  635 
  636         /etc/ssl/www.crt:
  637           x509.certificate_managed:
  638             - ca_server: pki
  639             - signing_policy: www
  640             - public_key: /etc/ssl/www.key
  641             - CN: www.example.com
  642             - days_valid: 90
  643             - days_remaining: 30
  644             - backup: True
  645 
  646     """
  647     if "path" in kwargs:
  648         name = kwargs.pop("path")
  649 
  650     if "ca_server" in kwargs and "signing_policy" not in kwargs:
  651         raise salt.exceptions.SaltInvocationError(
  652             "signing_policy must be specified if ca_server is."
  653         )
  654 
  655     if (
  656         "public_key" not in kwargs
  657         and "signing_private_key" not in kwargs
  658         and "csr" not in kwargs
  659     ):
  660         raise salt.exceptions.SaltInvocationError(
  661             "public_key, signing_private_key, or csr must be specified."
  662         )
  663 
  664     if managed_private_key:
  665         salt.utils.versions.warn_until(
  666             "Aluminium",
  667             "Passing 'managed_private_key' to x509.certificate_managed has no effect and "
  668             "will be removed Salt Aluminium. Use a separate x509.private_key_managed call instead.",
  669         )
  670 
  671     ret = {"name": name, "result": False, "changes": {}, "comment": ""}
  672 
  673     is_valid, invalid_reason, current_cert_info = _certificate_is_valid(
  674         name, days_remaining, append_certs, **kwargs
  675     )
  676 
  677     if is_valid:
  678         file_args, extra_args = _get_file_args(name, **kwargs)
  679 
  680         return _certificate_file_managed(ret, file_args)
  681 
  682     if __opts__["test"]:
  683         file_args, extra_args = _get_file_args(name, **kwargs)
  684         # Use empty contents for file.managed in test mode.
  685         # We don't want generate a new certificate, even in memory,
  686         # for security reasons.
  687         # Using an empty string instead of omitting it will at least
  688         # show the old certificate in the diff.
  689         file_args["contents"] = ""
  690 
  691         ret = _certificate_file_managed(ret, file_args)
  692 
  693         ret["result"] = None
  694         ret["comment"] = "Certificate {} will be created".format(name)
  695         ret["changes"]["Status"] = {
  696             "Old": invalid_reason,
  697             "New": "Certificate will be valid and up to date",
  698         }
  699         return ret
  700 
  701     contents = __salt__["x509.create_certificate"](text=True, **kwargs)
  702     # Check the module actually returned a cert and not an error message as a string
  703     try:
  704         __salt__["x509.read_certificate"](contents)
  705     except salt.exceptions.SaltInvocationError as e:
  706         ret["result"] = False
  707         ret[
  708             "comment"
  709         ] = "An error occurred creating the certificate {}. The result returned from x509.create_certificate is not a valid PEM file:\n{}".format(
  710             name, str(e)
  711         )
  712         return ret
  713 
  714     if not append_certs:
  715         append_certs = []
  716     for append_file in append_certs:
  717         try:
  718             append_file_contents = __salt__["x509.get_pem_entry"](
  719                 append_file, pem_type="CERTIFICATE"
  720             )
  721             contents += append_file_contents
  722         except salt.exceptions.SaltInvocationError as e:
  723             ret["result"] = False
  724             ret[
  725                 "comment"
  726             ] = "{} is not a valid certificate file, cannot append it to the certificate {}.\nThe error returned by the x509 module was:\n{}".format(
  727                 append_file, name, str(e)
  728             )
  729             return ret
  730 
  731     file_args, extra_args = _get_file_args(name, **kwargs)
  732     file_args["contents"] = contents
  733 
  734     ret = _certificate_file_managed(ret, file_args)
  735 
  736     if ret["result"]:
  737         ret["changes"]["Certificate"] = {
  738             "Old": current_cert_info,
  739             "New": __salt__["x509.read_certificate"](certificate=name),
  740         }
  741         ret["changes"]["Status"] = {
  742             "Old": invalid_reason,
  743             "New": "Certificate is valid and up to date",
  744         }
  745 
  746     return ret
  747 
  748 
  749 def crl_managed(
  750     name,
  751     signing_private_key,
  752     signing_private_key_passphrase=None,
  753     signing_cert=None,
  754     revoked=None,
  755     days_valid=100,
  756     digest="",
  757     days_remaining=30,
  758     include_expired=False,
  759     **kwargs
  760 ):
  761     """
  762     Manage a Certificate Revocation List
  763 
  764     name
  765         Path to the certificate
  766 
  767     signing_private_key
  768         The private key that will be used to sign the CRL. This is
  769         usually your CA's private key.
  770 
  771     signing_private_key_passphrase
  772         Passphrase to decrypt the private key.
  773 
  774     signing_cert
  775         The certificate of the authority that will be used to sign the CRL.
  776         This is usually your CA's certificate.
  777 
  778     revoked
  779         A list of certificates to revoke. Must include either a serial number or a
  780         the certificate itself. Can optionally include the revocation date and
  781         notAfter date from the certificate. See example below for details.
  782 
  783     days_valid : 100
  784         The number of days the certificate should be valid for.
  785 
  786     digest
  787         The digest to use for signing the CRL. This has no effect on versions
  788         of pyOpenSSL less than 0.14.
  789 
  790     days_remaining : 30
  791         The CRL should be automatically recreated if there are less than
  792         ``days_remaining`` days until the CRL expires. Set to 0 to disable
  793         automatic renewal.
  794 
  795     include_expired : False
  796         If ``True``, include expired certificates in the CRL.
  797 
  798     kwargs
  799         Any arguments supported by :py:func:`file.managed <salt.states.file.managed>` are supported.
  800 
  801     Example:
  802 
  803     .. code-block:: yaml
  804 
  805         /etc/pki/ca.crl:
  806           x509.crl_managed:
  807             - signing_private_key: /etc/pki/myca.key
  808             - signing_cert: /etc/pki/myca.crt
  809             - revoked:
  810               - compromized_Web_key:
  811                 - certificate: /etc/pki/certs/badweb.crt
  812                 - revocation_date: 2015-03-01 00:00:00
  813                 - reason: keyCompromise
  814               - terminated_vpn_user:
  815                 - serial_number: D6:D2:DC:D8:4D:5C:C0:F4
  816                 - not_after: 2016-01-01 00:00:00
  817                 - revocation_date: 2015-02-25 00:00:00
  818                 - reason: cessationOfOperation
  819     """
  820     if revoked is None:
  821         revoked = []
  822 
  823     revoked = _revoked_to_list(revoked)
  824 
  825     current_days_remaining = 0
  826     current_comp = {}
  827 
  828     if os.path.isfile(name):
  829         try:
  830             current = __salt__["x509.read_crl"](crl=name)
  831             current_comp = current.copy()
  832             current_comp.pop("Last Update")
  833             current_notafter = current_comp.pop("Next Update")
  834             current_days_remaining = (
  835                 datetime.datetime.strptime(current_notafter, "%Y-%m-%d %H:%M:%S")
  836                 - datetime.datetime.now()
  837             ).days
  838             if days_remaining == 0:
  839                 days_remaining = current_days_remaining - 1
  840         except salt.exceptions.SaltInvocationError:
  841             current = "{} is not a valid CRL.".format(name)
  842     else:
  843         current = "{} does not exist.".format(name)
  844 
  845     new_crl = __salt__["x509.create_crl"](
  846         text=True,
  847         signing_private_key=signing_private_key,
  848         signing_private_key_passphrase=signing_private_key_passphrase,
  849         signing_cert=signing_cert,
  850         revoked=revoked,
  851         days_valid=days_valid,
  852         digest=digest,
  853         include_expired=include_expired,
  854     )
  855 
  856     new = __salt__["x509.read_crl"](crl=new_crl)
  857     new_comp = new.copy()
  858     new_comp.pop("Last Update")
  859     new_comp.pop("Next Update")
  860 
  861     file_args, kwargs = _get_file_args(name, **kwargs)
  862     new_crl_created = False
  863     if (
  864         current_comp == new_comp
  865         and current_days_remaining > days_remaining
  866         and __salt__["x509.verify_crl"](name, signing_cert)
  867     ):
  868         file_args["contents"] = __salt__["x509.get_pem_entry"](
  869             name, pem_type="X509 CRL"
  870         )
  871     else:
  872         new_crl_created = True
  873         file_args["contents"] = new_crl
  874 
  875     ret = __states__["file.managed"](**file_args)
  876     if new_crl_created:
  877         ret["changes"] = {"Old": current, "New": __salt__["x509.read_crl"](crl=new_crl)}
  878     return ret
  879 
  880 
  881 def pem_managed(name, text, backup=False, **kwargs):
  882     """
  883     Manage the contents of a PEM file directly with the content in text, ensuring correct formatting.
  884 
  885     name:
  886         The path to the file to manage
  887 
  888     text:
  889         The PEM formatted text to write.
  890 
  891     kwargs:
  892         Any arguments supported by :py:func:`file.managed <salt.states.file.managed>` are supported.
  893     """
  894     file_args, kwargs = _get_file_args(name, **kwargs)
  895     file_args["contents"] = __salt__["x509.get_pem_entry"](text=text)
  896 
  897     return __states__["file.managed"](**file_args)