Adding support for multiple plugin types.

This commit is contained in:
kevgliss 2015-07-10 17:09:22 -07:00
parent c79905cd92
commit a30a8481d0
8 changed files with 256 additions and 71 deletions

View File

@ -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:

View File

@ -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.

View File

@ -1,2 +1,3 @@
from .destination import DestinationPlugin # NOQA
from .issuer import IssuerPlugin # NOQA
from .issuer import IssuerPlugin # NOQA
from .source import SourcePlugin

View File

@ -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

View File

@ -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 {}

View File

@ -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)

View File

@ -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):
"""

140
lemur/plugins/views.py Normal file
View File

@ -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')