Merge pull request #2781 from alwaysjolley/lemur_vault_source
adding Vault Source plugin
This commit is contained in:
commit
adec451613
|
@ -17,11 +17,122 @@ from flask import current_app
|
|||
from lemur.common.defaults import common_name
|
||||
from lemur.common.utils import parse_certificate
|
||||
from lemur.plugins.bases import DestinationPlugin
|
||||
from lemur.plugins.bases import SourcePlugin
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
|
||||
class VaultSourcePlugin(SourcePlugin):
|
||||
""" Class for importing certificates from Hashicorp Vault"""
|
||||
title = 'Vault'
|
||||
slug = 'vault-source'
|
||||
description = 'Discovers all certificates in a given path'
|
||||
|
||||
author = 'Christopher Jolley'
|
||||
author_url = 'https://github.com/alwaysjolley/lemur'
|
||||
|
||||
options = [
|
||||
{
|
||||
'name': 'vaultUrl',
|
||||
'type': 'str',
|
||||
'required': True,
|
||||
'validation': '^https?://[a-zA-Z0-9.:-]+$',
|
||||
'helpMessage': 'Valid URL to Hashi Vault instance'
|
||||
},
|
||||
{
|
||||
'name': 'vaultKvApiVersion',
|
||||
'type': 'select',
|
||||
'value': '2',
|
||||
'available': [
|
||||
'1',
|
||||
'2'
|
||||
],
|
||||
'required': True,
|
||||
'helpMessage': 'Version of the Vault KV API to use'
|
||||
},
|
||||
{
|
||||
'name': 'vaultAuthTokenFile',
|
||||
'type': 'str',
|
||||
'required': True,
|
||||
'validation': '(/[^/]+)+',
|
||||
'helpMessage': 'Must be a valid file path!'
|
||||
},
|
||||
{
|
||||
'name': 'vaultMount',
|
||||
'type': 'str',
|
||||
'required': True,
|
||||
'validation': r'^\S+$',
|
||||
'helpMessage': 'Must be a valid Vault secrets mount name!'
|
||||
},
|
||||
{
|
||||
'name': 'vaultPath',
|
||||
'type': 'str',
|
||||
'required': True,
|
||||
'validation': '^([a-zA-Z0-9_-]+/?)+$',
|
||||
'helpMessage': 'Must be a valid Vault secrets path'
|
||||
},
|
||||
{
|
||||
'name': 'objectName',
|
||||
'type': 'str',
|
||||
'required': True,
|
||||
'validation': '[0-9a-zA-Z:_-]+',
|
||||
'helpMessage': 'Object Name to search'
|
||||
},
|
||||
]
|
||||
|
||||
def get_certificates(self, options, **kwargs):
|
||||
"""Pull certificates from objects in Hashicorp Vault"""
|
||||
data = []
|
||||
cert = []
|
||||
body = ''
|
||||
url = self.get_option('vaultUrl', options)
|
||||
token_file = self.get_option('vaultAuthTokenFile', options)
|
||||
mount = self.get_option('vaultMount', options)
|
||||
path = self.get_option('vaultPath', options)
|
||||
obj_name = self.get_option('objectName', options)
|
||||
api_version = self.get_option('vaultKvApiVersion', options)
|
||||
cert_filter = '-----BEGIN CERTIFICATE-----'
|
||||
cert_delimiter = '-----END CERTIFICATE-----'
|
||||
|
||||
with open(token_file, 'r') as tfile:
|
||||
token = tfile.readline().rstrip('\n')
|
||||
|
||||
client = hvac.Client(url=url, token=token)
|
||||
client.secrets.kv.default_kv_version = api_version
|
||||
|
||||
path = '{0}/{1}'.format(path, obj_name)
|
||||
|
||||
secret = get_secret(client, mount, path)
|
||||
for cname in secret['data']:
|
||||
if 'crt' in secret['data'][cname]:
|
||||
cert = secret['data'][cname]['crt'].split(cert_delimiter + '\n')
|
||||
elif 'pem' in secret['data'][cname]:
|
||||
cert = secret['data'][cname]['pem'].split(cert_delimiter + '\n')
|
||||
else:
|
||||
for key in secret['data'][cname]:
|
||||
if secret['data'][cname][key].startswith(cert_filter):
|
||||
cert = secret['data'][cname][key].split(cert_delimiter + '\n')
|
||||
break
|
||||
body = cert[0] + cert_delimiter
|
||||
if 'chain' in secret['data'][cname]:
|
||||
chain = secret['data'][cname]['chain']
|
||||
elif len(cert) > 1:
|
||||
if cert[1].startswith(cert_filter):
|
||||
chain = cert[1] + cert_delimiter
|
||||
else:
|
||||
chain = None
|
||||
else:
|
||||
chain = None
|
||||
data.append({'body': body, 'chain': chain, 'name': cname})
|
||||
return [dict(body=c['body'], chain=c.get('chain'), name=c['name']) for c in data]
|
||||
|
||||
def get_endpoints(self, options, **kwargs):
|
||||
""" Not implemented yet """
|
||||
endpoints = []
|
||||
return endpoints
|
||||
|
||||
|
||||
class VaultDestinationPlugin(DestinationPlugin):
|
||||
"""Hashicorp Vault Destination plugin for Lemur"""
|
||||
title = 'Vault'
|
||||
|
@ -61,7 +172,7 @@ class VaultDestinationPlugin(DestinationPlugin):
|
|||
'name': 'vaultMount',
|
||||
'type': 'str',
|
||||
'required': True,
|
||||
'validation': '^\S+$',
|
||||
'validation': r'^\S+$',
|
||||
'helpMessage': 'Must be a valid Vault secrets mount name!'
|
||||
},
|
||||
{
|
||||
|
@ -85,6 +196,7 @@ class VaultDestinationPlugin(DestinationPlugin):
|
|||
'available': [
|
||||
'Nginx',
|
||||
'Apache',
|
||||
'PEM',
|
||||
'no chain'
|
||||
],
|
||||
'required': True,
|
||||
|
@ -136,8 +248,8 @@ class VaultDestinationPlugin(DestinationPlugin):
|
|||
"Exception compiling regex filter: invalid filter",
|
||||
exc_info=True)
|
||||
|
||||
with open(token_file, 'r') as file:
|
||||
token = file.readline().rstrip('\n')
|
||||
with open(token_file, 'r') as tfile:
|
||||
token = tfile.readline().rstrip('\n')
|
||||
|
||||
client = hvac.Client(url=url, token=token)
|
||||
client.secrets.kv.default_kv_version = api_version
|
||||
|
@ -150,11 +262,15 @@ class VaultDestinationPlugin(DestinationPlugin):
|
|||
secret = get_secret(client, mount, path)
|
||||
secret['data'][cname] = {}
|
||||
|
||||
if bundle == 'Nginx' and cert_chain:
|
||||
if bundle == 'Nginx':
|
||||
secret['data'][cname]['crt'] = '{0}\n{1}'.format(body, cert_chain)
|
||||
elif bundle == 'Apache' and cert_chain:
|
||||
secret['data'][cname]['key'] = private_key
|
||||
elif bundle == 'Apache':
|
||||
secret['data'][cname]['crt'] = body
|
||||
secret['data'][cname]['chain'] = cert_chain
|
||||
secret['data'][cname]['key'] = private_key
|
||||
elif bundle == 'PEM':
|
||||
secret['data'][cname]['pem'] = '{0}\n{1}\n{2}'.format(body, cert_chain, private_key)
|
||||
else:
|
||||
secret['data'][cname]['crt'] = body
|
||||
secret['data'][cname]['key'] = private_key
|
||||
|
@ -184,7 +300,7 @@ def get_san_list(body):
|
|||
|
||||
|
||||
def get_secret(client, mount, path):
|
||||
""" retreiive existing data from mount path and return dictionary """
|
||||
""" retreive existing data from mount path and return dictionary """
|
||||
result = {'data': {}}
|
||||
try:
|
||||
if client.secrets.kv.default_kv_version == '1':
|
||||
|
|
1
setup.py
1
setup.py
|
@ -155,6 +155,7 @@ setup(
|
|||
'digicert_cis_source = lemur.plugins.lemur_digicert.plugin:DigiCertCISSourcePlugin',
|
||||
'csr_export = lemur.plugins.lemur_csr.plugin:CSRExportPlugin',
|
||||
'sftp_destination = lemur.plugins.lemur_sftp.plugin:SFTPDestinationPlugin',
|
||||
'vault_source = lemur.plugins.lemur_vault_dest.plugin:VaultSourcePlugin',
|
||||
'vault_desination = lemur.plugins.lemur_vault_dest.plugin:VaultDestinationPlugin',
|
||||
'adcs_issuer = lemur.plugins.lemur_adcs.plugin:ADCSIssuerPlugin',
|
||||
'adcs_source = lemur.plugins.lemur_adcs.plugin:ADCSSourcePlugin'
|
||||
|
|
Loading…
Reference in New Issue