Manage a Certificate

    name
        Path to the certificate

    days_remaining : 90
        The minimum number of days remaining when the certificate should be
        recreated. A value of 0 disables automatic renewal.

    managed_private_key
        Manages the private key corresponding to the certificate. All of the
        arguments supported by :py:func:`x509.private_key_managed
        <salt.states.x509.private_key_managed>` are supported. If `name` is not
        speicified or is the same as the name of the certificate, the private
        key and certificate will be written together in the same file.

    append_certs:
        A list of certificates to be appended to the managed file.

    kwargs:
        Any arguments supported by :py:func:`x509.create_certificate
        <salt.modules.x509.create_certificate>` or :py:func:`file.managed
        <salt.states.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

        /etc/pki/ca.crt:
          x509.certificate_managed:
            - signing_private_key: /etc/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


    .. code-block:: yaml

        /etc/ssl/www.crt:
          x509.certificate_managed:
            - ca_server: pki
            - signing_policy: www
            - public_key: /etc/ssl/www.key
            - CN: www.example.com
            - days_valid: 90
            - days_remaining: 30
            - backup: True

def certificate_managed(name,
                        days_remaining=90,
                        managed_private_key=None,
                        append_certs=None,
                        **kwargs):
    '''
    Manage a Certificate

    name
        Path to the certificate

    days_remaining : 90
        The minimum number of days remaining when the certificate should be
        recreated. A value of 0 disables automatic renewal.

    managed_private_key
        Manages the private key corresponding to the certificate. All of the
        arguments supported by :py:func:`x509.private_key_managed
        <salt.states.x509.private_key_managed>` are supported. If `name` is not
        speicified or is the same as the name of the certificate, the private
        key and certificate will be written together in the same file.

    append_certs:
        A list of certificates to be appended to the managed file.

    kwargs:
        Any arguments supported by :py:func:`x509.create_certificate
        <salt.modules.x509.create_certificate>` or :py:func:`file.managed
        <salt.states.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

        /etc/pki/ca.crt:
          x509.certificate_managed:
            - signing_private_key: /etc/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


    .. code-block:: yaml

        /etc/ssl/www.crt:
          x509.certificate_managed:
            - ca_server: pki
            - signing_policy: www
            - public_key: /etc/ssl/www.key
            - CN: www.example.com
            - days_valid: 90
            - days_remaining: 30
            - backup: True

    '''
    if 'path' in kwargs:
        name = kwargs.pop('path')

    file_args, kwargs = _get_file_args(name, **kwargs)

    rotate_private_key = False
    new_private_key = False
    if managed_private_key:
        private_key_args = {
            'name': name,
            'new': False,
            'overwrite': False,
            'bits': 2048,
            'passphrase': None,
            'cipher': 'aes_128_cbc',
            'verbose': True
        }
        private_key_args.update(managed_private_key)
        kwargs['public_key_passphrase'] = private_key_args['passphrase']

        if private_key_args['new']:
            rotate_private_key = True
            private_key_args['new'] = False

        if _check_private_key(private_key_args['name'],
                              bits=private_key_args['bits'],
                              passphrase=private_key_args['passphrase'],
                              new=private_key_args['new'],
                              overwrite=private_key_args['overwrite']):
            private_key = __salt__['x509.get_pem_entry'](
                private_key_args['name'], pem_type='RSA PRIVATE KEY')
        else:
            new_private_key = True
            private_key = __salt__['x509.create_private_key'](text=True, bits=private_key_args['bits'],
                                                              passphrase=private_key_args['passphrase'],
                                                              cipher=private_key_args['cipher'],
                                                              verbose=private_key_args['verbose'])

        kwargs['public_key'] = private_key

    current_days_remaining = 0
    current_comp = {}

    if os.path.isfile(name):
        try:
            current = __salt__['x509.read_certificate'](certificate=name)
            current_comp = copy.deepcopy(current)
            if 'serial_number' not in kwargs:
                current_comp.pop('Serial Number')
                if 'signing_cert' not in kwargs:
                    try:
                        current_comp['X509v3 Extensions']['authorityKeyIdentifier'] = (
                            re.sub(r'serial:([0-9A-F]{2}:)*[0-9A-F]{2}', 'serial:--',
                                   current_comp['X509v3 Extensions']['authorityKeyIdentifier']))
                    except KeyError:
                        pass
            current_comp.pop('Not Before')
            current_comp.pop('MD5 Finger Print')
            current_comp.pop('SHA1 Finger Print')
            current_comp.pop('SHA-256 Finger Print')
            current_notafter = current_comp.pop('Not After')
            current_days_remaining = (
                datetime.datetime.strptime(current_notafter, '%Y-%m-%d %H:%M:%S') -
                datetime.datetime.now()).days
            if days_remaining == 0:
                days_remaining = current_days_remaining - 1
        except salt.exceptions.SaltInvocationError:
            current = '{0} is not a valid Certificate.'.format(name)
    else:
        current = '{0} does not exist.'.format(name)

    if 'ca_server' in kwargs and 'signing_policy' not in kwargs:
        raise salt.exceptions.SaltInvocationError(
            'signing_policy must be specified if ca_server is.')

    new = __salt__['x509.create_certificate'](testrun=False, text=True, **kwargs)
    new = __salt__['x509.read_certificate'](certificate=new)
    newcert = __salt__['x509.create_certificate'](testrun=True, **kwargs)

    if isinstance(new, dict):
        new_comp = copy.deepcopy(new)
        if 'serial_number' not in kwargs:
            new_comp.pop('Serial Number')
            if 'signing_cert' not in kwargs:
                try:
                    new_comp['X509v3 Extensions']['authorityKeyIdentifier'] = (
                        re.sub(r'serial:([0-9A-F]{2}:)*[0-9A-F]{2}', 'serial:--',
                               new_comp['X509v3 Extensions']['authorityKeyIdentifier']))
                except KeyError:
                    pass
        new_comp.pop('Not Before')
        new_comp.pop('Not After')
        new_comp.pop('MD5 Finger Print')
        new_comp.pop('SHA1 Finger Print')
        new_comp.pop('SHA-256 Finger Print')
        new_issuer_public_key = new_issuer_public_key = newcert.pop('Issuer Public Key')
    else:
        new_comp = new

    new_certificate = False
    if (current_comp == new_comp and
            current_days_remaining > days_remaining and
            __salt__['x509.verify_signature'](name, new_issuer_public_key)):
        certificate = __salt__['x509.get_pem_entry'](
            name, pem_type='CERTIFICATE')
    else:
        if rotate_private_key and not new_private_key:
            new_private_key = True
            private_key = __salt__['x509.create_private_key'](
                text=True, bits=private_key_args['bits'], verbose=private_key_args['verbose'])
            kwargs['public_key'] = private_key
        new_certificate = True
        certificate = __salt__['x509.create_certificate'](text=True, **kwargs)

    file_args['contents'] = ''
    private_ret = {}
    if managed_private_key:
        if private_key_args['name'] == name:
            file_args['contents'] = private_key
        else:
            private_file_args = copy.deepcopy(file_args)
            unique_private_file_args, _ = _get_file_args(**private_key_args)
            private_file_args.update(unique_private_file_args)
            private_file_args['contents'] = private_key
            private_ret = __states__['file.managed'](**private_file_args)
            if not private_ret['result']:
                return private_ret

    file_args['contents'] += salt.utils.stringutils.to_str(certificate)

    if not append_certs:
        append_certs = []
    for append_cert in append_certs:
        file_args[
            'contents'] += __salt__['x509.get_pem_entry'](append_cert, pem_type='CERTIFICATE')

    file_args['show_changes'] = False
    ret = __states__['file.managed'](**file_args)

    if ret['changes']:
        ret['changes'] = {'Certificate': ret['changes']}
    else:
        ret['changes'] = {}
    if private_ret and private_ret['changes']:
        ret['changes']['Private Key'] = private_ret['changes']
    if new_private_key:
        ret['changes']['Private Key'] = 'New private key generated'
    if new_certificate:
        ret['changes']['Certificate'] = {
            'Old': current,
            'New': __salt__['x509.read_certificate'](certificate=certificate)}

    return ret