Merge pull request #38664 from clinta/x509-passphrase2

X509 Improvements. Expose setting permissions, encrypted private keys, and combined key and cert management in one state
This commit is contained in:
Mike Place 2017-01-16 19:20:17 -07:00 committed by GitHub
commit 6663107021
2 changed files with 326 additions and 235 deletions

View file

@ -74,11 +74,6 @@ CERT_DEFAULTS = {
'serial_bits': 64,
'algorithm': 'sha256'
}
PEM_RE = re.compile(
r"\s*(?P<pem_header>-----(?:.+?)-----)\s+"
r"(?P<pem_body>.+?)\s+(?P<pem_footer>-----(?:.+?)-----)\s*",
re.DOTALL
)
def __virtual__():
@ -146,11 +141,13 @@ def _new_extension(name, value, critical=0, issuer=None, _pyfree=1):
lhash = None
except AttributeError:
lhash = M2Crypto.m2.x509v3_lhash() # pylint: disable=no-member
ctx = M2Crypto.m2.x509v3_set_conf_lhash(lhash) # pylint: disable=no-member
#ctx not zeroed
ctx = M2Crypto.m2.x509v3_set_conf_lhash(
lhash) # pylint: disable=no-member
# ctx not zeroed
_fix_ctx(ctx, issuer)
x509_ext_ptr = M2Crypto.m2.x509v3_ext_conf(lhash, ctx, name, value) # pylint: disable=no-member
#ctx,lhash freed
x509_ext_ptr = M2Crypto.m2.x509v3_ext_conf(
lhash, ctx, name, value) # pylint: disable=no-member
# ctx,lhash freed
if x509_ext_ptr is None:
raise M2Crypto.X509.X509Error(
@ -353,18 +350,28 @@ def _get_certificate_obj(cert):
return M2Crypto.X509.load_cert_string(text)
def _get_private_key_obj(private_key):
def _get_private_key_obj(private_key, passphrase=None):
'''
Returns a private key object based on PEM text.
'''
private_key = _text_or_file(private_key)
private_key = get_pem_entry(private_key, pem_type='RSA PRIVATE KEY')
rsaprivkey = M2Crypto.RSA.load_key_string(private_key)
rsaprivkey = M2Crypto.RSA.load_key_string(
private_key, callback=_passphrase_callback(passphrase))
evpprivkey = M2Crypto.EVP.PKey()
evpprivkey.assign_rsa(rsaprivkey)
return evpprivkey
def _passphrase_callback(passphrase):
'''
Returns a callback function used to supply a passphrase for private keys
'''
def f(*args):
return passphrase
return f
def _get_request_obj(csr):
'''
Returns a CSR object based on PEM text.
@ -397,6 +404,8 @@ def _make_regex(pem_type):
'''
return re.compile(
r"\s*(?P<pem_header>-----BEGIN {0}-----)\s+"
r"(?:(?P<proc_type>Proc-Type: 4,ENCRYPTED)\s*)?"
r"(?:(?P<dek_info>DEK-Info: (?:DES-[3A-Z\-]+,[0-9A-F]{{16}}|[0-9A-Z\-]+,[0-9A-F]{{32}}))\s*)?"
r"(?P<pem_body>.+?)\s+(?P<pem_footer>"
r"-----END {1}-----)\s*".format(pem_type, pem_type),
re.DOTALL
@ -424,18 +433,21 @@ def get_pem_entry(text, pem_type=None):
MIICyzCC Ar8CAQI...-----END CERTIFICATE REQUEST"
'''
text = _text_or_file(text)
# Replace encoded newlines
text = text.replace('\\n', '\n')
_match = None
if len(text.splitlines()) == 1 and text.startswith('-----') and text.endswith('-----'):
if len(text.splitlines()) == 1 and text.startswith(
'-----') and text.endswith('-----'):
# mine.get returns the PEM on a single line, we fix this
pem_fixed = []
pem_temp = text
while len(pem_temp) > 0:
if pem_temp.startswith('-----'):
# Grab ----(.*)---- blocks
pem_fixed.append(pem_temp[:pem_temp.index('-----', 5)+5])
pem_temp = pem_temp[pem_temp.index('-----', 5)+5:]
pem_fixed.append(pem_temp[:pem_temp.index('-----', 5) + 5])
pem_temp = pem_temp[pem_temp.index('-----', 5) + 5:]
else:
# grab base64 chunks
if pem_temp[:64].count('-') == 0:
@ -446,39 +458,34 @@ def get_pem_entry(text, pem_type=None):
pem_temp = pem_temp[pem_temp.index('-'):]
text = "\n".join(pem_fixed)
if not pem_type:
# Find using a regex iterator, pick the first match
for _match in PEM_RE.finditer(text):
if _match:
break
if not _match:
raise salt.exceptions.SaltInvocationError(
'PEM text not valid:\n{0}'.format(text)
)
_match_dict = _match.groupdict()
pem_header = _match_dict['pem_header']
pem_footer = _match_dict['pem_footer']
pem_body = _match_dict['pem_body']
else:
_dregex = _make_regex('[0-9A-Z ]+')
errmsg = 'PEM text not valid:\n{0}'.format(text)
if pem_type:
_dregex = _make_regex(pem_type)
for _match in _dregex.finditer(text):
if _match:
break
if not _match:
raise salt.exceptions.SaltInvocationError(
'PEM does not contain a single entry of type {0}:\n'
'{1}'.format(pem_type, text)
)
_match_dict = _match.groupdict()
pem_header = _match_dict['pem_header']
pem_footer = _match_dict['pem_footer']
pem_body = _match_dict['pem_body']
errmsg = ('PEM does not contain a single entry of type {0}:\n'
'{1}'.format(pem_type, text))
for _match in _dregex.finditer(text):
if _match:
break
if not _match:
raise salt.exceptions.SaltInvocationError(errmsg)
_match_dict = _match.groupdict()
pem_header = _match_dict['pem_header']
proc_type = _match_dict['proc_type']
dek_info = _match_dict['dek_info']
pem_footer = _match_dict['pem_footer']
pem_body = _match_dict['pem_body']
# Remove all whitespace from body
pem_body = ''.join(pem_body.split())
# Generate correctly formatted pem
ret = pem_header + '\n'
if proc_type:
ret += proc_type + '\n'
if dek_info:
ret += dek_info + '\n' + '\n'
for i in range(0, len(pem_body), 64):
ret += pem_body[i:i + 64] + '\n'
ret += pem_footer + '\n'
@ -649,7 +656,7 @@ def read_crl(crl):
return crlparsed
def get_public_key(key, asObj=False):
def get_public_key(key, passphrase=None, asObj=False):
'''
Returns a string containing the public key in PEM format.
@ -687,7 +694,8 @@ def get_public_key(key, asObj=False):
rsa = csr.get_pubkey().get_rsa()
if (text.startswith('-----BEGIN PRIVATE KEY-----') or
text.startswith('-----BEGIN RSA PRIVATE KEY-----')):
rsa = M2Crypto.RSA.load_key_string(text)
rsa = M2Crypto.RSA.load_key_string(
text, callback=_passphrase_callback(passphrase))
if asObj:
evppubkey = M2Crypto.EVP.PKey()
@ -698,7 +706,7 @@ def get_public_key(key, asObj=False):
return bio.read_all()
def get_private_key_size(private_key):
def get_private_key_size(private_key, passphrase=None):
'''
Returns the bit length of a private key in PEM format.
@ -711,7 +719,7 @@ def get_private_key_size(private_key):
salt '*' x509.get_private_key_size /etc/pki/mycert.key
'''
return _get_private_key_obj(private_key).size() * 8
return _get_private_key_obj(private_key, passphrase).size() * 8
def write_pem(text, path, overwrite=True, pem_type=None):
@ -768,7 +776,12 @@ def write_pem(text, path, overwrite=True, pem_type=None):
return 'PEM written to {0}'.format(path)
def create_private_key(path=None, text=False, bits=2048, verbose=True):
def create_private_key(path=None,
text=False,
bits=2048,
passphrase=None,
cipher='aes_128_cbc',
verbose=True):
'''
Creates a private key in PEM format.
@ -783,6 +796,12 @@ def create_private_key(path=None, text=False, bits=2048, verbose=True):
bits:
Length of the private key in bits. Default 2048
passphrase:
Passphrase for encryting the private key
cipher:
Cipher for encrypting the private key. Has no effect if passhprase is None.
verbose:
Provide visual feedback on stdout. Default True
@ -810,7 +829,12 @@ def create_private_key(path=None, text=False, bits=2048, verbose=True):
rsa = M2Crypto.RSA.gen_key(bits, M2Crypto.m2.RSA_F4, _callback_func)
# pylint: enable=no-member
bio = M2Crypto.BIO.MemoryBuffer()
rsa.save_key_bio(bio, cipher=None)
if passphrase is None:
cipher = None
rsa.save_key_bio(
bio,
cipher=cipher,
callback=_passphrase_callback(passphrase))
if path:
return write_pem(
@ -951,10 +975,20 @@ def create_crl( # pylint: disable=too-many-arguments,too-many-locals
get_pem_entry(signing_private_key))
try:
crltext = crl.export(cert, key, OpenSSL.crypto.FILETYPE_PEM, days=days_valid, digest=bytes(digest))
crltext = crl.export(
cert,
key,
OpenSSL.crypto.FILETYPE_PEM,
days=days_valid,
digest=bytes(digest))
except TypeError:
log.warning('Error signing crl with specified digest. Are you using pyopenssl 0.15 or newer? The default md5 digest will be used.')
crltext = crl.export(cert, key, OpenSSL.crypto.FILETYPE_PEM, days=days_valid)
log.warning(
'Error signing crl with specified digest. Are you using pyopenssl 0.15 or newer? The default md5 digest will be used.')
crltext = crl.export(
cert,
key,
OpenSSL.crypto.FILETYPE_PEM,
days=days_valid)
if text:
return crltext
@ -1125,6 +1159,9 @@ def create_certificate(
certificate, and the public key matching ``signing_private_key`` will
be used to create the certificate.
signing_private_key_passphrase:
Passphrase used to decrypt the signing_private_key.
signing_cert:
A certificate matching the private key that will be used to sign this
certificate. This is used to populate the issuer values in the
@ -1147,6 +1184,10 @@ def create_certificate(
to the certificate, subject or extension information in the CSR will
be lost.
public_key_passphrase:
If the public key is supplied as a private key, this is the passphrase
used to decrypt it.
csr:
A file or PEM string containing a certificate signing request. This
will be used to supply the subject, extensions and public key of a
@ -1296,6 +1337,8 @@ def create_certificate(
raise salt.exceptions.SaltInvocationError(
'Either path or text must be specified, not both.')
if 'public_key_passphrase' not in kwargs:
kwargs['public_key_passphrase'] = None
if ca_server:
if 'signing_policy' not in kwargs:
raise salt.exceptions.SaltInvocationError(
@ -1309,13 +1352,15 @@ def create_certificate(
if 'public_key' in kwargs:
# Strip newlines to make passing through as cli functions easier
kwargs['public_key'] = get_public_key(
kwargs['public_key']).replace('\n', '')
kwargs['public_key'],
passphrase=kwargs['public_key_passphrase']).replace('\n', '')
# Remove system entries in kwargs
# Including listen_in and preqreuired because they are not included
# in STATE_INTERNAL_KEYWORDS
# for salt 2014.7.2
for ignore in list(_STATE_INTERNAL_KEYWORDS) + ['listen_in', 'preqrequired', '__prerequired__']:
for ignore in list(_STATE_INTERNAL_KEYWORDS) + \
['listen_in', 'preqrequired', '__prerequired__']:
kwargs.pop(ignore, None)
cert_txt = __salt__['publish.publish'](
@ -1380,7 +1425,8 @@ def create_certificate(
cert.set_subject(csr.get_subject())
csrexts = read_csr(kwargs['csr'])['X509v3 Extensions']
cert.set_pubkey(get_public_key(kwargs['public_key'], asObj=True))
cert.set_pubkey(get_public_key(kwargs['public_key'],
passphrase=kwargs['public_key_passphrase'], asObj=True))
subject = cert.get_subject()
@ -1429,13 +1475,19 @@ def create_certificate(
cert.add_ext(ext)
if 'signing_private_key_passphrase' not in kwargs:
kwargs['signing_private_key_passphrase'] = None
if 'testrun' in kwargs and kwargs['testrun'] is True:
cert_props = read_certificate(cert)
cert_props['Issuer Public Key'] = get_public_key(
kwargs['signing_private_key'])
kwargs['signing_private_key'],
passphrase=kwargs['signing_private_key_passphrase'])
return cert_props
if not verify_private_key(kwargs['signing_private_key'], signing_cert):
if not verify_private_key(private_key=kwargs['signing_private_key'],
passphrase=kwargs[
'signing_private_key_passphrase'],
public_key=signing_cert):
raise salt.exceptions.SaltInvocationError(
'signing_private_key: {0} '
'does no match signing_cert: {1}'.format(
@ -1445,7 +1497,8 @@ def create_certificate(
)
cert.sign(
_get_private_key_obj(kwargs['signing_private_key']),
_get_private_key_obj(kwargs['signing_private_key'],
passphrase=kwargs['signing_private_key_passphrase']),
kwargs['algorithm']
)
@ -1459,7 +1512,7 @@ def create_certificate(
else:
prepend = ''
write_pem(text=cert.as_pem(), path=os.path.join(kwargs['copypath'],
prepend + kwargs['serial_number']+'.crt'),
prepend + kwargs['serial_number'] + '.crt'),
pem_type='CERTIFICATE')
if path:
@ -1519,7 +1572,8 @@ def create_csr(path=None, text=False, **kwargs):
if 'private_key' not in kwargs and 'public_key' in kwargs:
kwargs['private_key'] = kwargs['public_key']
log.warning("OpenSSL no longer allows working with non-signed CSRs. A private_key must be specified. Attempting to use public_key as private_key")
log.warning(
"OpenSSL no longer allows working with non-signed CSRs. A private_key must be specified. Attempting to use public_key as private_key")
if 'private_key' not in kwargs not in kwargs:
raise salt.exceptions.SaltInvocationError('private_key is required')
@ -1527,7 +1581,10 @@ def create_csr(path=None, text=False, **kwargs):
if 'public_key' not in kwargs:
kwargs['public_key'] = kwargs['private_key']
csr.set_pubkey(get_public_key(kwargs['public_key'], asObj=True))
if 'public_key_passphrase' not in kwargs:
kwargs['public_key_passphrase'] = None
csr.set_pubkey(get_public_key(kwargs['public_key'],
passphrase=kwargs['public_key_passphrase'], asObj=True))
# pylint: disable=unused-variable
for entry, num in six.iteritems(subject.nid):
@ -1565,7 +1622,8 @@ def create_csr(path=None, text=False, **kwargs):
csr.add_extensions(extstack)
csr.sign(_get_private_key_obj(kwargs['private_key']), kwargs['algorithm'])
csr.sign(_get_private_key_obj(kwargs['private_key'],
passphrase=kwargs['public_key_passphrase']), kwargs['algorithm'])
if path:
return write_pem(
@ -1577,7 +1635,7 @@ def create_csr(path=None, text=False, **kwargs):
return csr.as_pem()
def verify_private_key(private_key, public_key):
def verify_private_key(private_key, public_key, passphrase=None):
'''
Verify that 'private_key' matches 'public_key'
@ -1589,6 +1647,9 @@ def verify_private_key(private_key, public_key):
The public key to verify, can be a string or path to a PEM formatted
certificate, csr, or another private key.
passphrase:
Passphrase to decrypt the private key.
CLI Example:
.. code-block:: bash
@ -1596,10 +1657,12 @@ def verify_private_key(private_key, public_key):
salt '*' x509.verify_private_key private_key=/etc/pki/myca.key \\
public_key=/etc/pki/myca.crt
'''
return bool(get_public_key(private_key) == get_public_key(public_key))
return bool(get_public_key(private_key, passphrase)
== get_public_key(public_key))
def verify_signature(certificate, signing_pub_key=None):
def verify_signature(certificate, signing_pub_key=None,
signing_pub_key_passphrase=None):
'''
Verify that ``certificate`` has been signed by ``signing_pub_key``
@ -1611,6 +1674,9 @@ def verify_signature(certificate, signing_pub_key=None):
The public key to verify, can be a string or path to a PEM formatted
certificate, csr, or private key.
signing_pub_key_passphrase:
Passphrase to the signing_pub_key if it is an encrypted private key.
CLI Example:
.. code-block:: bash
@ -1621,7 +1687,8 @@ def verify_signature(certificate, signing_pub_key=None):
cert = _get_certificate_obj(certificate)
if signing_pub_key:
signing_pub_key = get_public_key(signing_pub_key, asObj=True)
signing_pub_key = get_public_key(signing_pub_key,
passphrase=signing_pub_key_passphrase, asObj=True)
return bool(cert.verify(pkey=signing_pub_key) == 1)

View file

@ -63,13 +63,6 @@ the mine where it can be easily retrieved by other minions.
/etc/pki/issued_certs:
file.directory: []
/etc/pki/ca.key:
x509.private_key_managed:
- bits: 4096
- backup: True
- require:
- file: /etc/pki
/etc/pki/ca.crt:
x509.certificate_managed:
- signing_private_key: /etc/pki/ca.key
@ -84,8 +77,12 @@ the mine where it can be easily retrieved by other minions.
- days_valid: 3650
- days_remaining: 0
- backup: True
- managed_private_key:
name: /etc/pki/ca.key
bits: 4096
backup: True
- require:
- x509: /etc/pki/ca.key
- file: /etc/pki
mine.send:
module.run:
@ -142,10 +139,6 @@ This state creates a private key then requests a certificate signed by ca accord
.. code-block:: yaml
/etc/pki/www.key:
x509.private_key_managed:
- bits: 4096
/etc/pki/www.crt:
x509.certificate_managed:
- ca_server: ca
@ -154,6 +147,10 @@ This state creates a private key then requests a certificate signed by ca accord
- CN: www.example.com
- days_remaining: 30
- backup: True
- managed_private_key:
name: /etc/pki/www.key
bits: 4096
backup: True
'''
@ -190,7 +187,8 @@ def _revoked_to_list(revs):
list_ = []
for rev in revs:
for rev_name, props in six.iteritems(rev): # pylint: disable=unused-variable
for rev_name, props in six.iteritems(
rev): # pylint: disable=unused-variable
dict_ = {}
for prop in props:
for propname, val in six.iteritems(prop):
@ -202,11 +200,46 @@ def _revoked_to_list(revs):
return list_
def _get_file_args(name, **kwargs):
valid_file_args = ['user',
'group',
'mode',
'makedirs',
'dir_mode',
'backup',
'create',
'follow_symlinks',
'check_cmd']
file_args = {}
extra_args = {}
for k, v in kwargs.items():
if k in valid_file_args:
file_args[k] = v
else:
extra_args[k] = v
file_args['name'] = name
return file_args, extra_args
def _check_private_key(name, bits=2048, passphrase=None, new=False):
current_bits = 0
if os.path.isfile(name):
try:
current_bits = __salt__['x509.get_private_key_size'](
private_key=name, passphrase=passphrase)
except salt.exceptions.SaltInvocationError:
pass
return current_bits == bits and not new
def private_key_managed(name,
bits=2048,
passphrase=None,
cipher='aes_128_cbc',
new=False,
backup=False,
verbose=True,):
verbose=True,
**kwargs):
'''
Manage a private key's existence.
@ -216,14 +249,15 @@ def private_key_managed(name,
bits:
Key length in bits. Default 2048.
passphrase:
Passphrase for encrypting the private key.
cipher:
Cipher for encrypting the private key.
new:
Always create a new key. Defaults to False.
Combining new with :mod:`prereq <salt.states.requsities.preqreq>` can allow key rotation
whenever a new certificiate is generated.
backup:
When replacing an existing file, backup the old file on the minion.
Default is False.
Combining new with :mod:`prereq <salt.states.requsities.preqreq>`, or when used as part of a `managed_private_key` can allow key rotation whenever a new certificiate is generated.
verbose:
Provide visual feedback on stdout, dots while key is generated.
@ -231,6 +265,9 @@ def private_key_managed(name,
.. versionadded:: 2016.11.0
kwargs:
Any kwargs supported by file.managed are supported.
Example:
The jinja templating in this example ensures a private key is generated if the file doesn't exist
@ -247,45 +284,24 @@ def private_key_managed(name,
- x509: /etc/pki/www.crt
{%- endif %}
'''
ret = {'name': name, 'changes': {}, 'result': False, 'comment': ''}
current_bits = 0
if os.path.isfile(name):
try:
current_bits = __salt__['x509.get_private_key_size'](private_key=name)
current = "{0} bit private key".format(current_bits)
except salt.exceptions.SaltInvocationError:
current = '{0} is not a valid Private Key.'.format(name)
file_args, kwargs = _get_file_args(name, **kwargs)
new_key = False
if _check_private_key(name, bits, passphrase, new):
file_args['contents'] = __salt__['x509.get_pem_entry'](
name, pem_type='RSA PRIVATE KEY')
else:
current = '{0} does not exist.'.format(name)
new_key = True
file_args['contents'] = __salt__['x509.create_private_key'](
text=True, bits=bits, passphrase=passphrase, cipher=cipher, verbose=verbose)
if current_bits == bits and not new:
ret['result'] = True
ret['comment'] = 'The Private key is already in the correct state'
return ret
ret['changes'] = {
'old': current,
'new': "{0} bit private key".format(bits)}
if __opts__['test'] is True:
ret['result'] = None
ret['comment'] = 'The Private Key "{0}" will be updated.'.format(name)
return ret
if os.path.isfile(name) and backup:
bkroot = os.path.join(__opts__['cachedir'], 'file_backup')
salt.utils.backup_minion(name, bkroot)
ret['comment'] = __salt__['x509.create_private_key'](
path=name, bits=bits, verbose=verbose)
ret['result'] = True
ret = __states__['file.managed'](**file_args)
if ret['changes'] and new_key:
ret['changes'] = 'New private key generated'
return ret
def csr_managed(name,
backup=False,
**kwargs):
'''
Manage a Certificate Signing Request
@ -297,6 +313,9 @@ def csr_managed(name,
The properties to be added to the certificate request, including items like subject, extensions
and public key. See above for valid properties.
kwargs:
Any arguments supported by :state:`file.managed <salt.states.file.managed>` are supported.
Example:
.. code-block:: yaml
@ -310,45 +329,23 @@ def csr_managed(name,
- L: Salt Lake City
- keyUsage: 'critical dataEncipherment'
'''
ret = {'name': name, 'changes': {}, 'result': False, 'comment': ''}
old = __salt__['x509.read_csr'](name)
file_args, kwargs = _get_file_args(name, **kwargs)
file_args['contents'] = __salt__['x509.create_csr'](text=True, **kwargs)
if os.path.isfile(name):
try:
current = __salt__['x509.read_csr'](csr=name)
except salt.exceptions.SaltInvocationError:
current = '{0} is not a valid CSR.'.format(name)
else:
current = '{0} does not exist.'.format(name)
new_csr = __salt__['x509.create_csr'](text=True, **kwargs)
new = __salt__['x509.read_csr'](csr=new_csr)
if current == new:
ret['result'] = True
ret['comment'] = 'The CSR is already in the correct state'
return ret
ret['changes'] = {
'old': current,
'new': new, }
if __opts__['test'] is True:
ret['result'] = None
ret['comment'] = 'The CSR {0} will be updated.'.format(name)
if os.path.isfile(name) and backup:
bkroot = os.path.join(__opts__['cachedir'], 'file_backup')
salt.utils.backup_minion(name, bkroot)
ret['comment'] = __salt__['x509.write_pem'](text=new_csr, path=name, pem_type="CERTIFICATE REQUEST")
ret['result'] = True
ret = __states__['file.managed'](**file_args)
if ret['changes']:
new = __salt__['x509.read_csr'](file_args['contents'])
if old != new:
ret['changes'] = {"Old": old, "New": new}
return ret
def certificate_managed(name,
days_remaining=90,
backup=False,
managed_private_key=None,
append_certs=None,
**kwargs):
'''
Manage a Certificate
@ -360,12 +357,14 @@ def certificate_managed(name,
The minimum number of days remaining when the certificate should be recreated. Default is 90. A
value of 0 disables automatic renewal.
backup:
When replacing an existing file, backup the old file on the minion. Default is False.
managed_private_key:
Manages the private key corresponding to the certificate. All of the arguments supported by :state:`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 :mod:`x509.create_certificate <salt.modules.x509.create_certificate>`
are supported.
Any arguments supported by :mod:`x509.create_certificate <salt.modules.x509.create_certificate>` or :state:`file.managed <salt.states.file.managed>` are supported.
Examples:
@ -400,11 +399,42 @@ def certificate_managed(name,
- backup: True
'''
ret = {'name': name, 'changes': {}, 'result': False, 'comment': ''}
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,
'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'],
private_key_args['bits'],
private_key_args['passphrase'],
private_key_args['new']):
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 = {}
@ -418,7 +448,7 @@ def certificate_managed(name,
try:
current_comp['X509v3 Extensions']['authorityKeyIdentifier'] = (
re.sub(r'serial:([0-9A-F]{2}:)*[0-9A-F]{2}', 'serial:--',
current_comp['X509v3 Extensions']['authorityKeyIdentifier']))
current_comp['X509v3 Extensions']['authorityKeyIdentifier']))
except KeyError:
pass
current_comp.pop('Not Before')
@ -427,8 +457,8 @@ def certificate_managed(name,
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
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:
@ -437,7 +467,8 @@ def certificate_managed(name,
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.')
raise salt.exceptions.SaltInvocationError(
'signing_policy must be specified if ca_server is.')
new = __salt__['x509.create_certificate'](testrun=True, **kwargs)
@ -450,7 +481,7 @@ def certificate_managed(name,
try:
new_comp['X509v3 Extensions']['authorityKeyIdentifier'] = (
re.sub(r'serial:([0-9A-F]{2}:)*[0-9A-F]{2}', 'serial:--',
new_comp['X509v3 Extensions']['authorityKeyIdentifier']))
new_comp['X509v3 Extensions']['authorityKeyIdentifier']))
except KeyError:
pass
new_comp.pop('Not Before')
@ -462,28 +493,58 @@ def certificate_managed(name,
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)):
ret['result'] = True
ret['comment'] = 'The certificate is already in the correct state'
return ret
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)
ret['changes'] = {
'old': current,
'new': new, }
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
if __opts__['test'] is True:
ret['result'] = None
ret['comment'] = 'The certificate {0} will be updated.'.format(name)
return ret
file_args['contents'] += certificate
if os.path.isfile(name) and backup:
bkroot = os.path.join(__opts__['cachedir'], 'file_backup')
salt.utils.backup_minion(name, bkroot)
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')
ret['comment'] = __salt__['x509.create_certificate'](path=name, **kwargs)
ret['result'] = True
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
@ -496,7 +557,7 @@ def crl_managed(name,
digest="",
days_remaining=30,
include_expired=False,
backup=False,):
**kwargs):
'''
Manage a Certificate Revocation List
@ -530,8 +591,8 @@ def crl_managed(name,
include_expired:
Include expired certificates in the CRL. Default is ``False``.
backup:
When replacing an existing file, backup the old file on the minion. Default is False.
kwargs:
Any arguments supported by :state:`file.managed <salt.states.file.managed>` are supported.
Example:
@ -552,8 +613,6 @@ def crl_managed(name,
- revocation_date: 2015-02-25 00:00:00
- reason: cessationOfOperation
'''
ret = {'name': name, 'changes': {}, 'result': False, 'comment': ''}
if revoked is None:
revoked = []
@ -569,8 +628,8 @@ def crl_managed(name,
current_comp.pop('Last Update')
current_notafter = current_comp.pop('Next Update')
current_days_remaining = (
datetime.datetime.strptime(current_notafter, '%Y-%m-%d %H:%M:%S') -
datetime.datetime.now()).days
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:
@ -579,43 +638,35 @@ def crl_managed(name,
current = '{0} does not exist.'.format(name)
new_crl = __salt__['x509.create_crl'](text=True, signing_private_key=signing_private_key,
signing_cert=signing_cert, revoked=revoked, days_valid=days_valid, digest=digest, include_expired=include_expired)
signing_cert=signing_cert, revoked=revoked, days_valid=days_valid, digest=digest, include_expired=include_expired)
new = __salt__['x509.read_crl'](crl=new_crl)
new_comp = new.copy()
new_comp.pop('Last Update')
new_comp.pop('Next Update')
file_args, kwargs = _get_file_args(name, **kwargs)
new_crl = False
if (current_comp == new_comp and
current_days_remaining > days_remaining and
__salt__['x509.verify_crl'](name, signing_cert)):
file_args['contents'] = __salt__[
'x509.get_pem_entry'](name, pem_type='X509 CRL')
else:
new_crl = True
file_args['contents'] = new_crl
ret['result'] = True
ret['comment'] = 'The crl is already in the correct state'
return ret
ret['changes'] = {
'old': current,
'new': new, }
if __opts__['test'] is True:
ret['result'] = None
ret['comment'] = 'The crl {0} will be updated.'.format(name)
return ret
if os.path.isfile(name) and backup:
bkroot = os.path.join(__opts__['cachedir'], 'file_backup')
salt.utils.backup_minion(name, bkroot)
ret['comment'] = __salt__['x509.write_pem'](text=new_crl, path=name, pem_type='X509 CRL')
ret['result'] = True
ret = __states__['file.managed'](**file_args)
if new_crl:
ret['changes'] = {'Old': current, 'New': __salt__[
'x509.read_crl'](crl=new_crl)}
return ret
def pem_managed(name,
text,
backup=False):
backup=False,
**kwargs):
'''
Manage the contents of a PEM file directly with the content in text, ensuring correct formatting.
@ -625,37 +676,10 @@ def pem_managed(name,
text:
The PEM formatted text to write.
backup:
When replacing an existing file, backup the old file on the minion. Default is False.
kwargs:
Any arguments supported by :state:`file.managed <salt.states.file.managed>` are supported.
'''
ret = {'name': name, 'changes': {}, 'result': False, 'comment': ''}
file_args, kwargs = _get_file_args(name, **kwargs)
file_args['contents'] = __salt__['x509.get_pem_entry'](text=text)
new = __salt__['x509.get_pem_entry'](text=text)
try:
with salt.utils.fopen(name) as fp_:
current = fp_.read()
except (OSError, IOError):
current = '{0} does not exist or is unreadable'.format(name)
if new == current:
ret['result'] = True
ret['comment'] = 'The file is already in the correct state'
return ret
ret['changes']['new'] = new
ret['changes']['old'] = current
if __opts__['test'] is True:
ret['result'] = None
ret['comment'] = 'The file {0} will be updated.'.format(name)
return ret
if os.path.isfile(name) and backup:
bkroot = os.path.join(__opts__['cachedir'], 'file_backup')
salt.utils.backup_minion(name, bkroot)
ret['comment'] = __salt__['x509.write_pem'](text=text, path=name)
ret['result'] = True
return ret
return __states__['file.managed'](**file_args)