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 lemur.common.managers import InstanceManager
|
||||
|
||||
|
||||
# inspired by https://github.com/getsentry/sentry
|
||||
class PluginManager(InstanceManager):
|
||||
def __iter__(self):
|
||||
|
@ -17,8 +16,10 @@ class PluginManager(InstanceManager):
|
|||
def __len__(self):
|
||||
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()):
|
||||
if not plugin.type == plugin_type and plugin_type:
|
||||
continue
|
||||
if not plugin.is_enabled():
|
||||
continue
|
||||
if version is not None and plugin.__version__ != version:
|
||||
|
|
|
@ -47,12 +47,13 @@ class IPlugin(local):
|
|||
# Configuration specifics
|
||||
conf_key = None
|
||||
conf_title = None
|
||||
options = {}
|
||||
|
||||
# Global enabled state
|
||||
enabled = True
|
||||
can_disable = True
|
||||
|
||||
def is_enabled(self, project=None):
|
||||
def is_enabled(self):
|
||||
"""
|
||||
Returns a boolean representing if this plugin is enabled.
|
||||
If ``project`` is passed, it will limit the scope to that project.
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
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
|
||||
issuers will inherit from.
|
||||
"""
|
||||
type = 'issuer'
|
||||
|
||||
def create_certificate(self):
|
||||
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 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.authorities import service as authority_service
|
||||
|
||||
API_ENDPOINT = '/v1/ca/netflix'
|
||||
API_ENDPOINT = '/v1/ca/netflix' # TODO this should be configurable
|
||||
|
||||
|
||||
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))
|
||||
|
||||
|
||||
class CloudCAPlugin(IssuerPlugin):
|
||||
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'
|
||||
|
||||
class CloudCA(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.session = requests.Session()
|
||||
self.session.mount('https://', CloudCAHostNameCheckingAdapter())
|
||||
|
@ -162,7 +154,69 @@ class CloudCAPlugin(IssuerPlugin):
|
|||
else:
|
||||
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):
|
||||
"""
|
||||
|
@ -205,22 +259,6 @@ class CloudCAPlugin(IssuerPlugin):
|
|||
|
||||
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):
|
||||
"""
|
||||
Creates a new certificate from cloudca
|
||||
|
@ -259,16 +297,25 @@ class CloudCAPlugin(IssuerPlugin):
|
|||
|
||||
return cert, "".join(intermediates),
|
||||
|
||||
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
|
||||
class CloudCASourcePlugin(SourcePlugin, CloudCA):
|
||||
title = 'CloudCA'
|
||||
slug = 'cloudca-source'
|
||||
description = 'Discovers all SSL certificates in CloudCA'
|
||||
version = cloudca.VERSION
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -297,29 +344,3 @@ class CloudCAPlugin(IssuerPlugin):
|
|||
})
|
||||
|
||||
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
|
||||
|
||||
|
||||
class VerisignPlugin(IssuerPlugin):
|
||||
title = 'VeriSign'
|
||||
slug = 'verisign'
|
||||
class VerisignIssuerPlugin(IssuerPlugin):
|
||||
title = 'Verisign'
|
||||
slug = 'verisign-issuer'
|
||||
description = 'Enables the creation of certificates by the VICE2.0 verisign API.'
|
||||
version = verisign.VERSION
|
||||
|
||||
|
@ -87,7 +87,7 @@ class VerisignPlugin(IssuerPlugin):
|
|||
def __init__(self, *args, **kwargs):
|
||||
self.session = requests.Session()
|
||||
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):
|
||||
"""
|
||||
|
|
|
@ -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