- support multiple bundle configuration, nginx, apache, cert only
- update vault destination to support multi cert under one object - added san list as key value - read and update object with new keys, keeping other keys, allowing us to keep an iterable list of keys in an object for deploying multiple certs to a single node
This commit is contained in:
@ -18,6 +18,10 @@ from lemur.common.defaults import common_name
|
||||
from lemur.common.utils import parse_certificate
|
||||
from lemur.plugins.bases import DestinationPlugin
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
|
||||
class VaultDestinationPlugin(DestinationPlugin):
|
||||
"""Hashicorp Vault Destination plugin for Lemur"""
|
||||
title = 'Vault'
|
||||
@ -48,6 +52,25 @@ class VaultDestinationPlugin(DestinationPlugin):
|
||||
'required': True,
|
||||
'validation': '^https?://[a-zA-Z0-9.-]+(?::[0-9]+)?$',
|
||||
'helpMessage': 'Must be a valid Vault server url'
|
||||
},
|
||||
{
|
||||
'name': 'bundleChain',
|
||||
'type': 'select',
|
||||
'value': 'cert only',
|
||||
'available': [
|
||||
'Nginx',
|
||||
'Apache',
|
||||
'no chain'
|
||||
],
|
||||
'required': True,
|
||||
'helpMessage': 'Bundle the chain into the certificate'
|
||||
},
|
||||
{
|
||||
'name': 'objectName',
|
||||
'type': 'str',
|
||||
'required': False,
|
||||
'validation': '[0-9a-zA-Z:_-]+',
|
||||
'helpMessage': 'Name to bundle certs under, if blank use cn'
|
||||
}
|
||||
]
|
||||
|
||||
@ -62,24 +85,64 @@ class VaultDestinationPlugin(DestinationPlugin):
|
||||
:param cert_chain:
|
||||
:return:
|
||||
"""
|
||||
cn = common_name(parse_certificate(body))
|
||||
data = {}
|
||||
#current_app.logger.warning("Cert body content: {0}".format(body))
|
||||
cname = common_name(parse_certificate(body))
|
||||
secret = {'data':{}}
|
||||
key_name = '{0}.key'.format(cname)
|
||||
cert_name = '{0}.crt'.format(cname)
|
||||
chain_name = '{0}.chain'.format(cname)
|
||||
sans_name = '{0}.san'.format(cname)
|
||||
|
||||
token = current_app.config.get('VAULT_TOKEN')
|
||||
|
||||
mount = self.get_option('vaultMount', options)
|
||||
path = '{0}/{1}'.format(self.get_option('vaultPath', options),cn)
|
||||
path = self.get_option('vaultPath', options)
|
||||
url = self.get_option('vaultUrl', options)
|
||||
bundle = self.get_option('bundleChain', options)
|
||||
obj_name = self.get_option('objectName', options)
|
||||
|
||||
client = hvac.Client(url=url, token=token)
|
||||
if obj_name:
|
||||
path = '{0}/{1}'.format(path, obj_name)
|
||||
else:
|
||||
path = '{0}/{1}'.format(path, cname)
|
||||
|
||||
data['cert'] = cert_chain
|
||||
data['key'] = private_key
|
||||
secret = get_secret(url, token, mount, path)
|
||||
|
||||
|
||||
## upload certificate and key
|
||||
if bundle == 'Nginx' and cert_chain:
|
||||
secret['data'][cert_name] = '{0}\n{1}'.format(body, cert_chain)
|
||||
elif bundle == 'Apache' and cert_chain:
|
||||
secret['data'][cert_name] = body
|
||||
secret['data'][chain_name] = cert_chain
|
||||
else:
|
||||
secret['data'][cert_name] = body
|
||||
secret['data'][key_name] = private_key
|
||||
san_list = get_san_list(body)
|
||||
if isinstance(san_list, list):
|
||||
secret['data'][sans_name] = san_list
|
||||
try:
|
||||
client.secrets.kv.v1.create_or_update_secret(path=path, mount_point=mount, secret=data)
|
||||
except Exception as err:
|
||||
client.secrets.kv.v1.create_or_update_secret(
|
||||
path=path, mount_point=mount, secret=secret['data'])
|
||||
except ConnectionError as err:
|
||||
current_app.logger.exception(
|
||||
"Exception uploading secret to vault: {0}".format(err), exc_info=True)
|
||||
|
||||
def get_san_list(body):
|
||||
""" parse certificate for SAN names and return list, return empty list on error """
|
||||
try:
|
||||
byte_body = body.encode('utf-8')
|
||||
cert = x509.load_pem_x509_certificate(byte_body, default_backend())
|
||||
ext = cert.extensions.get_extension_for_oid(x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
|
||||
return ext.value.get_values_for_type(x509.DNSName)
|
||||
except:
|
||||
pass
|
||||
return []
|
||||
|
||||
def get_secret(url, token, mount, path):
|
||||
result = {'data': {}}
|
||||
try:
|
||||
client = hvac.Client(url=url, token=token)
|
||||
result = client.secrets.kv.v1.read_secret(path=path, mount_point=mount)
|
||||
except:
|
||||
pass
|
||||
return result
|
||||
|
Reference in New Issue
Block a user