Merge branch 'master' into hosseinsh-celeryjob-sync-src-dst
This commit is contained in:
@ -459,7 +459,10 @@ class ACMEIssuerPlugin(IssuerPlugin):
|
||||
"pending_cert": entry["pending_cert"],
|
||||
})
|
||||
except (PollError, AcmeError, Exception) as e:
|
||||
current_app.logger.error("Unable to resolve pending cert: {}".format(pending_cert), exc_info=True)
|
||||
order_url = order.uri
|
||||
current_app.logger.error(
|
||||
"Unable to resolve pending cert: {}. "
|
||||
"Check out {} for more information.".format(pending_cert, order_url), exc_info=True)
|
||||
certs.append({
|
||||
"cert": False,
|
||||
"pending_cert": entry["pending_cert"],
|
||||
|
@ -15,6 +15,8 @@ from cryptography.fernet import Fernet
|
||||
from lemur.utils import mktempfile, mktemppath
|
||||
from lemur.plugins.bases import ExportPlugin
|
||||
from lemur.plugins import lemur_java as java
|
||||
from lemur.common.utils import parse_certificate
|
||||
from lemur.common.defaults import common_name
|
||||
|
||||
|
||||
def run_process(command):
|
||||
@ -233,7 +235,7 @@ class JavaKeystoreExportPlugin(ExportPlugin):
|
||||
if self.get_option('alias', options):
|
||||
alias = self.get_option('alias', options)
|
||||
else:
|
||||
alias = "blah"
|
||||
alias = common_name(parse_certificate(body))
|
||||
|
||||
with mktemppath() as jks_tmp:
|
||||
create_keystore(body, chain, jks_tmp, key, alias, passphrase)
|
||||
|
@ -14,7 +14,8 @@ from flask import current_app
|
||||
from lemur.utils import mktempfile, mktemppath
|
||||
from lemur.plugins.bases import ExportPlugin
|
||||
from lemur.plugins import lemur_openssl as openssl
|
||||
from lemur.common.utils import get_psuedo_random_string
|
||||
from lemur.common.utils import get_psuedo_random_string, parse_certificate
|
||||
from lemur.common.defaults import common_name
|
||||
|
||||
|
||||
def run_process(command):
|
||||
@ -122,7 +123,7 @@ class OpenSSLExportPlugin(ExportPlugin):
|
||||
if self.get_option('alias', options):
|
||||
alias = self.get_option('alias', options)
|
||||
else:
|
||||
alias = "blah"
|
||||
alias = common_name(parse_certificate(body))
|
||||
|
||||
type = self.get_option('type', options)
|
||||
|
||||
|
5
lemur/plugins/lemur_vault_dest/__init__.py
Normal file
5
lemur/plugins/lemur_vault_dest/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
try:
|
||||
VERSION = __import__('pkg_resources') \
|
||||
.get_distribution(__name__).version
|
||||
except Exception as e:
|
||||
VERSION = 'unknown'
|
173
lemur/plugins/lemur_vault_dest/plugin.py
Normal file
173
lemur/plugins/lemur_vault_dest/plugin.py
Normal file
@ -0,0 +1,173 @@
|
||||
"""
|
||||
.. module: lemur.plugins.lemur_vault_dest.plugin
|
||||
:platform: Unix
|
||||
:copyright: (c) 2019
|
||||
:license: Apache, see LICENCE for more details.
|
||||
|
||||
Plugin for uploading certificates and private key as secret to hashi vault
|
||||
that can be pulled down by end point nodes.
|
||||
|
||||
.. moduleauthor:: Christopher Jolley <chris@alwaysjolley.com>
|
||||
"""
|
||||
import hvac
|
||||
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 cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
|
||||
class VaultDestinationPlugin(DestinationPlugin):
|
||||
"""Hashicorp Vault Destination plugin for Lemur"""
|
||||
title = 'Vault'
|
||||
slug = 'hashi-vault-destination'
|
||||
description = 'Allow the uploading of certificates to Hashi Vault as secret'
|
||||
|
||||
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': '^\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': False,
|
||||
'validation': '[0-9a-zA-Z:_-]+',
|
||||
'helpMessage': 'Name to bundle certs under, if blank use cn'
|
||||
},
|
||||
{
|
||||
'name': 'bundleChain',
|
||||
'type': 'select',
|
||||
'value': 'cert only',
|
||||
'available': [
|
||||
'Nginx',
|
||||
'Apache',
|
||||
'no chain'
|
||||
],
|
||||
'required': True,
|
||||
'helpMessage': 'Bundle the chain into the certificate'
|
||||
}
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(VaultDestinationPlugin, self).__init__(*args, **kwargs)
|
||||
|
||||
def upload(self, name, body, private_key, cert_chain, options, **kwargs):
|
||||
"""
|
||||
Upload certificate and private key
|
||||
|
||||
:param private_key:
|
||||
:param cert_chain:
|
||||
:return:
|
||||
"""
|
||||
cname = common_name(parse_certificate(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)
|
||||
bundle = self.get_option('bundleChain', options)
|
||||
obj_name = self.get_option('objectName', options)
|
||||
api_version = self.get_option('vaultKvApiVersion', options)
|
||||
|
||||
with open(token_file, 'r') as file:
|
||||
token = file.readline().rstrip('\n')
|
||||
|
||||
client = hvac.Client(url=url, token=token)
|
||||
client.secrets.kv.default_kv_version = api_version
|
||||
|
||||
if obj_name:
|
||||
path = '{0}/{1}'.format(path, obj_name)
|
||||
else:
|
||||
path = '{0}/{1}'.format(path, cname)
|
||||
|
||||
secret = get_secret(client, mount, path)
|
||||
secret['data'][cname] = {}
|
||||
|
||||
if bundle == 'Nginx' and cert_chain:
|
||||
secret['data'][cname]['crt'] = '{0}\n{1}'.format(body, cert_chain)
|
||||
elif bundle == 'Apache' and cert_chain:
|
||||
secret['data'][cname]['crt'] = body
|
||||
secret['data'][cname]['chain'] = cert_chain
|
||||
else:
|
||||
secret['data'][cname]['crt'] = body
|
||||
secret['data'][cname]['key'] = private_key
|
||||
san_list = get_san_list(body)
|
||||
if isinstance(san_list, list):
|
||||
secret['data'][cname]['san'] = san_list
|
||||
try:
|
||||
client.secrets.kv.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 """
|
||||
san_list = []
|
||||
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)
|
||||
san_list = ext.value.get_values_for_type(x509.DNSName)
|
||||
except x509.extensions.ExtensionNotFound:
|
||||
pass
|
||||
finally:
|
||||
return san_list
|
||||
|
||||
|
||||
def get_secret(client, mount, path):
|
||||
""" retreiive existing data from mount path and return dictionary """
|
||||
result = {'data': {}}
|
||||
try:
|
||||
if client.secrets.kv.default_kv_version == '1':
|
||||
result = client.secrets.kv.v1.read_secret(path=path, mount_point=mount)
|
||||
else:
|
||||
result = client.secrets.kv.v2.read_secret_version(path=path, mount_point=mount)
|
||||
except ConnectionError:
|
||||
pass
|
||||
finally:
|
||||
return result
|
1
lemur/plugins/lemur_vault_dest/tests/conftest.py
Normal file
1
lemur/plugins/lemur_vault_dest/tests/conftest.py
Normal file
@ -0,0 +1 @@
|
||||
from lemur.tests.conftest import * # noqa
|
Reference in New Issue
Block a user