Kubernetes desination plugin (#357)
* Kubernetes desination plugin * fixing build warnings * fixing build warnings
This commit is contained in:
parent
9ae27f1415
commit
b44a7c73d8
|
@ -0,0 +1,5 @@
|
||||||
|
try:
|
||||||
|
VERSION = __import__('pkg_resources') \
|
||||||
|
.get_distribution(__name__).version
|
||||||
|
except Exception as e:
|
||||||
|
VERSION = 'unknown'
|
|
@ -0,0 +1,158 @@
|
||||||
|
"""
|
||||||
|
.. module: lemur.plugins.lemur_kubernetes.aws
|
||||||
|
:platform: Unix
|
||||||
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
|
:license: Apache, see LICENSE for more details.
|
||||||
|
|
||||||
|
.. moduleauthor:: Mikhail Khodorovskiy <mikhail.khodorovskiy@jivesoftware.com>
|
||||||
|
|
||||||
|
The plugin inserts certificates and the private key as Kubernetes secret that
|
||||||
|
can later be used to secure service endpoints running in Kubernetes pods
|
||||||
|
|
||||||
|
"""
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from lemur.certificates.models import get_cn
|
||||||
|
from lemur.plugins.bases import DestinationPlugin
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import base64
|
||||||
|
import requests
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
DEFAULT_API_VERSION = 'v1'
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_resource(k8s_api, k8s_base_uri, namespace, kind, name, data):
|
||||||
|
|
||||||
|
# _resolve_uri(k8s_base_uri, namespace, kind, name, api_ver=DEFAULT_API_VERSION)
|
||||||
|
url = _resolve_uri(k8s_base_uri, namespace, kind)
|
||||||
|
|
||||||
|
create_resp = k8s_api.post(url, json=data)
|
||||||
|
|
||||||
|
if 200 <= create_resp.status_code <= 299:
|
||||||
|
return None
|
||||||
|
elif create_resp.json()['reason'] != 'AlreadyExists':
|
||||||
|
return create_resp.content
|
||||||
|
update_resp = k8s_api.put(_resolve_uri(k8s_base_uri, namespace, kind, name), json=data)
|
||||||
|
if not 200 <= update_resp.status_code <= 299:
|
||||||
|
return update_resp.content
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_ns(k8s_base_uri, namespace, api_ver=DEFAULT_API_VERSION,):
|
||||||
|
api_group = 'api'
|
||||||
|
if '/' in api_ver:
|
||||||
|
api_group = 'apis'
|
||||||
|
return '{base}/{api_group}/{api_ver}/namespaces'.format(base=k8s_base_uri, api_group=api_group, api_ver=api_ver) + ('/' + namespace if namespace else '')
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_uri(k8s_base_uri, namespace, kind, name=None, api_ver=DEFAULT_API_VERSION):
|
||||||
|
if not namespace:
|
||||||
|
namespace = 'default'
|
||||||
|
return "/".join(itertools.chain.from_iterable([
|
||||||
|
(_resolve_ns(k8s_base_uri, namespace, api_ver=api_ver),),
|
||||||
|
((kind + 's').lower(),),
|
||||||
|
(name,) if name else (),
|
||||||
|
]))
|
||||||
|
|
||||||
|
|
||||||
|
class KubernetesDestinationPlugin(DestinationPlugin):
|
||||||
|
title = 'Kubernetes'
|
||||||
|
slug = 'kubernetes-destination'
|
||||||
|
description = 'Allow the uploading of certificates to Kubernetes as secret'
|
||||||
|
|
||||||
|
author = 'Mikhail Khodorovskiy'
|
||||||
|
author_url = 'https://github.com/mik373/lemur'
|
||||||
|
|
||||||
|
options = [
|
||||||
|
{
|
||||||
|
'name': 'kubernetesURL',
|
||||||
|
'type': 'str',
|
||||||
|
'required': True,
|
||||||
|
'validation': '@(https?|http)://(-\.)?([^\s/?\.#-]+\.?)+(/[^\s]*)?$@iS',
|
||||||
|
'helpMessage': 'Must be a valid Kubernetes server URL!',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'kubernetesAuthToken',
|
||||||
|
'type': 'str',
|
||||||
|
'required': True,
|
||||||
|
'validation': '/^$|\s+/',
|
||||||
|
'helpMessage': 'Must be a valid Kubernetes server Token!',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'kubernetesServerCertificate',
|
||||||
|
'type': 'str',
|
||||||
|
'required': True,
|
||||||
|
'validation': '/^$|\s+/',
|
||||||
|
'helpMessage': 'Must be a valid Kubernetes server Certificate!',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'kubernetesNamespace',
|
||||||
|
'type': 'str',
|
||||||
|
'required': True,
|
||||||
|
'validation': '/^$|\s+/',
|
||||||
|
'helpMessage': 'Must be a valid Kubernetes Namespace!',
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(KubernetesDestinationPlugin, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def upload(self, name, body, private_key, cert_chain, options, **kwargs):
|
||||||
|
|
||||||
|
k8_bearer = self.get_option('kubernetesAuthToken', options)
|
||||||
|
k8_cert = self.get_option('kubernetesServerCertificate', options)
|
||||||
|
k8_namespace = self.get_option('kubernetesNamespace', options)
|
||||||
|
k8_base_uri = self.get_option('kubernetesURL', options)
|
||||||
|
|
||||||
|
k8s_api = K8sSession(k8_bearer, k8_cert)
|
||||||
|
|
||||||
|
cert = x509.load_pem_x509_certificate(str(body), default_backend())
|
||||||
|
name = get_cn(cert)
|
||||||
|
|
||||||
|
# in the future once runtime properties can be passed-in - use passed-in secret name
|
||||||
|
secret_name = 'certs-' + urllib.quote_plus(name)
|
||||||
|
|
||||||
|
err = ensure_resource(k8s_api, k8s_base_uri=k8_base_uri, namespace=k8_namespace, kind="secret", name=secret_name, data={
|
||||||
|
'apiVersion': 'v1',
|
||||||
|
'kind': 'Secret',
|
||||||
|
'metadata': {
|
||||||
|
'name': secret_name,
|
||||||
|
},
|
||||||
|
'data': {
|
||||||
|
'combined.pem': base64.b64encode(body + private_key),
|
||||||
|
'ca.crt': base64.b64encode(cert_chain),
|
||||||
|
'service.key': base64.b64encode(private_key),
|
||||||
|
'service.crt': base64.b64encode(body),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err is not None:
|
||||||
|
raise Exception("Error uploading secret: " + err)
|
||||||
|
|
||||||
|
|
||||||
|
class K8sSession(requests.Session):
|
||||||
|
|
||||||
|
def __init__(self, bearer, cert):
|
||||||
|
super(K8sSession, self).__init__()
|
||||||
|
|
||||||
|
self.headers.update({
|
||||||
|
'Authorization': 'Bearer %s' % bearer
|
||||||
|
})
|
||||||
|
|
||||||
|
k8_ca = '/tmp/k8.cert'
|
||||||
|
|
||||||
|
with open(k8_ca, "w") as text_file:
|
||||||
|
text_file.write(cert)
|
||||||
|
|
||||||
|
self.verify = k8_ca
|
||||||
|
|
||||||
|
def request(self, method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=30, allow_redirects=True, proxies=None,
|
||||||
|
hooks=None, stream=None, verify=None, cert=None, json=None):
|
||||||
|
"""
|
||||||
|
This method overrides the default timeout to be 10s.
|
||||||
|
"""
|
||||||
|
return super(K8sSession, self).request(method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream,
|
||||||
|
verify, cert, json)
|
3
setup.py
3
setup.py
|
@ -174,7 +174,8 @@ setup(
|
||||||
'java_truststore_export = lemur.plugins.lemur_java.plugin:JavaTruststoreExportPlugin',
|
'java_truststore_export = lemur.plugins.lemur_java.plugin:JavaTruststoreExportPlugin',
|
||||||
'java_keystore_export = lemur.plugins.lemur_java.plugin:JavaKeystoreExportPlugin',
|
'java_keystore_export = lemur.plugins.lemur_java.plugin:JavaKeystoreExportPlugin',
|
||||||
'openssl_export = lemur.plugins.lemur_openssl.plugin:OpenSSLExportPlugin',
|
'openssl_export = lemur.plugins.lemur_openssl.plugin:OpenSSLExportPlugin',
|
||||||
'atlas_metric = lemur.plugins.lemur_atlas.plugin:AtlasMetricPlugin'
|
'atlas_metric = lemur.plugins.lemur_atlas.plugin:AtlasMetricPlugin',
|
||||||
|
'kubernetes_destination = lemur.plugins.lemur_kubernetes.plugin:KubernetesDestinationPlugin'
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
classifiers=[
|
classifiers=[
|
||||||
|
|
Loading…
Reference in New Issue