Adding support for multiple plugin types.
This commit is contained in:
parent
c79905cd92
commit
a30a8481d0
|
@ -8,7 +8,6 @@
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from lemur.common.managers import InstanceManager
|
from lemur.common.managers import InstanceManager
|
||||||
|
|
||||||
|
|
||||||
# inspired by https://github.com/getsentry/sentry
|
# inspired by https://github.com/getsentry/sentry
|
||||||
class PluginManager(InstanceManager):
|
class PluginManager(InstanceManager):
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
|
@ -17,8 +16,10 @@ class PluginManager(InstanceManager):
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return sum(1 for i in self.all())
|
return sum(1 for i in self.all())
|
||||||
|
|
||||||
def all(self, version=1):
|
def all(self, version=1, plugin_type=None):
|
||||||
for plugin in sorted(super(PluginManager, self).all(), key=lambda x: x.get_title()):
|
for plugin in sorted(super(PluginManager, self).all(), key=lambda x: x.get_title()):
|
||||||
|
if not plugin.type == plugin_type and plugin_type:
|
||||||
|
continue
|
||||||
if not plugin.is_enabled():
|
if not plugin.is_enabled():
|
||||||
continue
|
continue
|
||||||
if version is not None and plugin.__version__ != version:
|
if version is not None and plugin.__version__ != version:
|
||||||
|
|
|
@ -47,12 +47,13 @@ class IPlugin(local):
|
||||||
# Configuration specifics
|
# Configuration specifics
|
||||||
conf_key = None
|
conf_key = None
|
||||||
conf_title = None
|
conf_title = None
|
||||||
|
options = {}
|
||||||
|
|
||||||
# Global enabled state
|
# Global enabled state
|
||||||
enabled = True
|
enabled = True
|
||||||
can_disable = True
|
can_disable = True
|
||||||
|
|
||||||
def is_enabled(self, project=None):
|
def is_enabled(self):
|
||||||
"""
|
"""
|
||||||
Returns a boolean representing if this plugin is enabled.
|
Returns a boolean representing if this plugin is enabled.
|
||||||
If ``project`` is passed, it will limit the scope to that project.
|
If ``project`` is passed, it will limit the scope to that project.
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
from .destination import DestinationPlugin # NOQA
|
from .destination import DestinationPlugin # NOQA
|
||||||
from .issuer import IssuerPlugin # NOQA
|
from .issuer import IssuerPlugin # NOQA
|
||||||
|
from .source import SourcePlugin
|
|
@ -13,6 +13,8 @@ class IssuerPlugin(Plugin):
|
||||||
This is the base class from which all of the supported
|
This is the base class from which all of the supported
|
||||||
issuers will inherit from.
|
issuers will inherit from.
|
||||||
"""
|
"""
|
||||||
|
type = 'issuer'
|
||||||
|
|
||||||
def create_certificate(self):
|
def create_certificate(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
"""
|
||||||
|
.. module: lemur.bases.source
|
||||||
|
:platform: Unix
|
||||||
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
|
:license: Apache, see LICENSE for more details.
|
||||||
|
|
||||||
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
|
"""
|
||||||
|
from lemur.plugins.base import Plugin
|
||||||
|
|
||||||
|
class SourcePlugin(Plugin):
|
||||||
|
type = 'source'
|
||||||
|
|
||||||
|
def get_certificates(self):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def get_options(self):
|
||||||
|
return {}
|
||||||
|
|
|
@ -18,12 +18,12 @@ from requests.adapters import HTTPAdapter
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
from lemur.exceptions import LemurException
|
from lemur.exceptions import LemurException
|
||||||
from lemur.plugins.bases import IssuerPlugin
|
from lemur.plugins.bases import IssuerPlugin, SourcePlugin
|
||||||
from lemur.plugins import lemur_cloudca as cloudca
|
from lemur.plugins import lemur_cloudca as cloudca
|
||||||
|
|
||||||
from lemur.authorities import service as authority_service
|
from lemur.authorities import service as authority_service
|
||||||
|
|
||||||
API_ENDPOINT = '/v1/ca/netflix'
|
API_ENDPOINT = '/v1/ca/netflix' # TODO this should be configurable
|
||||||
|
|
||||||
|
|
||||||
class CloudCAException(LemurException):
|
class CloudCAException(LemurException):
|
||||||
|
@ -142,15 +142,7 @@ def get_auth_data(ca_name):
|
||||||
raise CloudCAException("You do not have the required role to issue certificates from {0}".format(ca_name))
|
raise CloudCAException("You do not have the required role to issue certificates from {0}".format(ca_name))
|
||||||
|
|
||||||
|
|
||||||
class CloudCAPlugin(IssuerPlugin):
|
class CloudCA(object):
|
||||||
title = 'CloudCA'
|
|
||||||
slug = 'cloudca'
|
|
||||||
description = 'Enables the creation of certificates from the cloudca API.'
|
|
||||||
version = cloudca.VERSION
|
|
||||||
|
|
||||||
author = 'Kevin Glisson'
|
|
||||||
author_url = 'https://github.com/netflix/lemur'
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
self.session.mount('https://', CloudCAHostNameCheckingAdapter())
|
self.session.mount('https://', CloudCAHostNameCheckingAdapter())
|
||||||
|
@ -162,7 +154,69 @@ class CloudCAPlugin(IssuerPlugin):
|
||||||
else:
|
else:
|
||||||
current_app.logger.warning("No CLOUDCA credentials found, lemur will be unable to request certificates from CLOUDCA")
|
current_app.logger.warning("No CLOUDCA credentials found, lemur will be unable to request certificates from CLOUDCA")
|
||||||
|
|
||||||
super(CloudCAPlugin, self).__init__(*args, **kwargs)
|
super(CloudCA, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def post(self, endpoint, data):
|
||||||
|
"""
|
||||||
|
HTTP POST to CloudCA
|
||||||
|
|
||||||
|
:param endpoint:
|
||||||
|
:param data:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
data = dumps(dict(data.items() + get_auth_data(data['caName']).items()))
|
||||||
|
|
||||||
|
# we set a low timeout, if cloudca is down it shouldn't bring down
|
||||||
|
# lemur
|
||||||
|
response = self.session.post(self.url + endpoint, data=data, timeout=10, verify=self.ca_bundle)
|
||||||
|
return process_response(response)
|
||||||
|
|
||||||
|
def get(self, endpoint):
|
||||||
|
"""
|
||||||
|
HTTP GET to CloudCA
|
||||||
|
|
||||||
|
:param endpoint:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
response = self.session.get(self.url + endpoint, timeout=10, verify=self.ca_bundle)
|
||||||
|
return process_response(response)
|
||||||
|
|
||||||
|
def random(self, length=10):
|
||||||
|
"""
|
||||||
|
Uses CloudCA as a decent source of randomness.
|
||||||
|
|
||||||
|
:param length:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
endpoint = '/v1/random/{0}'.format(length)
|
||||||
|
response = self.session.get(self.url + endpoint, verify=self.ca_bundle)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_authorities(self):
|
||||||
|
"""
|
||||||
|
Retrieves authorities that were made outside of Lemur.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
endpoint = '{0}/listCAs'.format(API_ENDPOINT)
|
||||||
|
authorities = []
|
||||||
|
for ca in self.get(endpoint)['data']['caList']:
|
||||||
|
try:
|
||||||
|
authorities.append(ca['caName'])
|
||||||
|
except AttributeError as e:
|
||||||
|
current_app.logger.error("No authority has been defined for {}".format(ca['caName']))
|
||||||
|
|
||||||
|
return authorities
|
||||||
|
|
||||||
|
|
||||||
|
class CloudCAIssuerPlugin(IssuerPlugin, CloudCA):
|
||||||
|
title = 'CloudCA'
|
||||||
|
slug = 'cloudca-issuer'
|
||||||
|
description = 'Enables the creation of certificates from the cloudca API.'
|
||||||
|
version = cloudca.VERSION
|
||||||
|
|
||||||
|
author = 'Kevin Glisson'
|
||||||
|
author_url = 'https://github.com/netflix/lemur'
|
||||||
|
|
||||||
def create_authority(self, options):
|
def create_authority(self, options):
|
||||||
"""
|
"""
|
||||||
|
@ -205,22 +259,6 @@ class CloudCAPlugin(IssuerPlugin):
|
||||||
|
|
||||||
return cert, "".join(intermediates), roles,
|
return cert, "".join(intermediates), roles,
|
||||||
|
|
||||||
def get_authorities(self):
|
|
||||||
"""
|
|
||||||
Retrieves authorities that were made outside of Lemur.
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
endpoint = '{0}/listCAs'.format(API_ENDPOINT)
|
|
||||||
authorities = []
|
|
||||||
for ca in self.get(endpoint)['data']['caList']:
|
|
||||||
try:
|
|
||||||
authorities.append(ca['caName'])
|
|
||||||
except AttributeError as e:
|
|
||||||
current_app.logger.error("No authority has been defined for {}".format(ca['caName']))
|
|
||||||
|
|
||||||
return authorities
|
|
||||||
|
|
||||||
def create_certificate(self, csr, options):
|
def create_certificate(self, csr, options):
|
||||||
"""
|
"""
|
||||||
Creates a new certificate from cloudca
|
Creates a new certificate from cloudca
|
||||||
|
@ -259,16 +297,25 @@ class CloudCAPlugin(IssuerPlugin):
|
||||||
|
|
||||||
return cert, "".join(intermediates),
|
return cert, "".join(intermediates),
|
||||||
|
|
||||||
def random(self, length=10):
|
|
||||||
"""
|
|
||||||
Uses CloudCA as a decent source of randomness.
|
|
||||||
|
|
||||||
:param length:
|
class CloudCASourcePlugin(SourcePlugin, CloudCA):
|
||||||
:return:
|
title = 'CloudCA'
|
||||||
"""
|
slug = 'cloudca-source'
|
||||||
endpoint = '/v1/random/{0}'.format(length)
|
description = 'Discovers all SSL certificates in CloudCA'
|
||||||
response = self.session.get(self.url + endpoint, verify=self.ca_bundle)
|
version = cloudca.VERSION
|
||||||
return response
|
|
||||||
|
author = 'Kevin Glisson'
|
||||||
|
author_url = 'https://github.com/netflix/lemur'
|
||||||
|
|
||||||
|
options = {
|
||||||
|
'pollRate': {'type': 'int', 'default': '60'}
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_certificates(self, **kwargs):
|
||||||
|
certs = []
|
||||||
|
for authority in self.get_authorities():
|
||||||
|
certs += self.get_cert(ca_name=authority)
|
||||||
|
return
|
||||||
|
|
||||||
def get_cert(self, ca_name=None, cert_handle=None):
|
def get_cert(self, ca_name=None, cert_handle=None):
|
||||||
"""
|
"""
|
||||||
|
@ -297,29 +344,3 @@ class CloudCAPlugin(IssuerPlugin):
|
||||||
})
|
})
|
||||||
|
|
||||||
return certs
|
return certs
|
||||||
|
|
||||||
def post(self, endpoint, data):
|
|
||||||
"""
|
|
||||||
HTTP POST to CloudCA
|
|
||||||
|
|
||||||
:param endpoint:
|
|
||||||
:param data:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
data = dumps(dict(data.items() + get_auth_data(data['caName']).items()))
|
|
||||||
|
|
||||||
# we set a low timeout, if cloudca is down it shouldn't bring down
|
|
||||||
# lemur
|
|
||||||
response = self.session.post(self.url + endpoint, data=data, timeout=10, verify=self.ca_bundle)
|
|
||||||
return process_response(response)
|
|
||||||
|
|
||||||
def get(self, endpoint):
|
|
||||||
"""
|
|
||||||
HTTP GET to CloudCA
|
|
||||||
|
|
||||||
:param endpoint:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
response = self.session.get(self.url + endpoint, timeout=10, verify=self.ca_bundle)
|
|
||||||
return process_response(response)
|
|
||||||
|
|
||||||
|
|
|
@ -75,9 +75,9 @@ def handle_response(content):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
class VerisignPlugin(IssuerPlugin):
|
class VerisignIssuerPlugin(IssuerPlugin):
|
||||||
title = 'VeriSign'
|
title = 'Verisign'
|
||||||
slug = 'verisign'
|
slug = 'verisign-issuer'
|
||||||
description = 'Enables the creation of certificates by the VICE2.0 verisign API.'
|
description = 'Enables the creation of certificates by the VICE2.0 verisign API.'
|
||||||
version = verisign.VERSION
|
version = verisign.VERSION
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ class VerisignPlugin(IssuerPlugin):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
self.session.cert = current_app.config.get('VERISIGN_PEM_PATH')
|
self.session.cert = current_app.config.get('VERISIGN_PEM_PATH')
|
||||||
super(VerisignPlugin, self).__init__(*args, **kwargs)
|
super(VerisignIssuerPlugin, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def create_certificate(self, csr, issuer_options):
|
def create_certificate(self, csr, issuer_options):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
"""
|
||||||
|
.. module: lemur.plugins.views
|
||||||
|
:platform: Unix
|
||||||
|
:synopsis: This module contains all of the accounts view code.
|
||||||
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
|
:license: Apache, see LICENSE for more details.
|
||||||
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
|
"""
|
||||||
|
from flask import Blueprint
|
||||||
|
from flask.ext.restful import Api, reqparse, fields
|
||||||
|
from lemur.auth.service import AuthenticatedResource
|
||||||
|
|
||||||
|
from lemur.common.utils import marshal_items
|
||||||
|
|
||||||
|
from lemur.plugins.base import plugins
|
||||||
|
|
||||||
|
mod = Blueprint('plugins', __name__)
|
||||||
|
api = Api(mod)
|
||||||
|
|
||||||
|
|
||||||
|
FIELDS = {
|
||||||
|
'title': fields.String,
|
||||||
|
'pluginOptions': fields.Raw(attribute='options'),
|
||||||
|
'description': fields.String,
|
||||||
|
'version': fields.String,
|
||||||
|
'author': fields.String,
|
||||||
|
'authorUrl': fields.String,
|
||||||
|
'type': fields.String,
|
||||||
|
'slug': fields.String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PluginsList(AuthenticatedResource):
|
||||||
|
""" Defines the 'plugins' endpoint """
|
||||||
|
def __init__(self):
|
||||||
|
self.reqparse = reqparse.RequestParser()
|
||||||
|
super(PluginsList, self).__init__()
|
||||||
|
|
||||||
|
@marshal_items(FIELDS)
|
||||||
|
def get(self):
|
||||||
|
"""
|
||||||
|
.. http:get:: /plugins
|
||||||
|
|
||||||
|
The current plugin list
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /plugins HTTP/1.1
|
||||||
|
Host: example.com
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"accountNumber": 222222222,
|
||||||
|
"label": "account2",
|
||||||
|
"comments": "this is a thing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"accountNumber": 11111111111,
|
||||||
|
"label": "account1",
|
||||||
|
"comments": "this is a thing"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
"total": 2
|
||||||
|
}
|
||||||
|
|
||||||
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
|
:statuscode 200: no error
|
||||||
|
"""
|
||||||
|
return plugins.all()
|
||||||
|
|
||||||
|
|
||||||
|
class PluginsTypeList(AuthenticatedResource):
|
||||||
|
""" Defines the 'plugins' endpoint """
|
||||||
|
def __init__(self):
|
||||||
|
self.reqparse = reqparse.RequestParser()
|
||||||
|
super(PluginsTypeList, self).__init__()
|
||||||
|
|
||||||
|
@marshal_items(FIELDS)
|
||||||
|
def get(self, plugin_type):
|
||||||
|
"""
|
||||||
|
.. http:get:: /plugins/issuer
|
||||||
|
|
||||||
|
The current plugin list
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /plugins/issuer HTTP/1.1
|
||||||
|
Host: example.com
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"accountNumber": 222222222,
|
||||||
|
"label": "account2",
|
||||||
|
"comments": "this is a thing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"accountNumber": 11111111111,
|
||||||
|
"label": "account1",
|
||||||
|
"comments": "this is a thing"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
"total": 2
|
||||||
|
}
|
||||||
|
|
||||||
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
|
:statuscode 200: no error
|
||||||
|
"""
|
||||||
|
return list(plugins.all(plugin_type=plugin_type))
|
||||||
|
|
||||||
|
api.add_resource(PluginsList, '/plugins', endpoint='plugins')
|
||||||
|
api.add_resource(PluginsTypeList, '/plugins/<plugin_type>', endpoint='pluginType')
|
||||||
|
|
Loading…
Reference in New Issue