Starting to move to new plugin architecture.

This commit is contained in:
kevgliss 2015-07-04 12:47:57 -07:00
parent eadfaaeed0
commit 3f49bb95ff
24 changed files with 327 additions and 226 deletions

View File

@ -17,7 +17,7 @@ from lemur.roles import service as role_service
from lemur.roles.models import Role from lemur.roles.models import Role
import lemur.certificates.service as cert_service import lemur.certificates.service as cert_service
from lemur.common.services.issuers.manager import get_plugin_by_name from lemur.plugins.base import plugins
def update(authority_id, active=None, roles=None): def update(authority_id, active=None, roles=None):
""" """
@ -49,7 +49,7 @@ def create(kwargs):
:return: :return:
""" """
issuer = get_plugin_by_name(kwargs.get('pluginName')) issuer = plugins.get(kwargs.get('pluginName'))
kwargs['creator'] = g.current_user.email kwargs['creator'] = g.current_user.email
cert_body, intermediate, issuer_roles = issuer.create_authority(kwargs) cert_body, intermediate, issuer_roles = issuer.create_authority(kwargs)

View File

@ -18,8 +18,7 @@ from flask import g, current_app
from lemur import database from lemur import database
from lemur.common.services.aws import iam from lemur.common.services.aws import iam
from lemur.common.services.issuers.manager import get_plugin_by_name from lemur.plugins.base import plugins
from lemur.certificates.models import Certificate from lemur.certificates.models import Certificate
from lemur.certificates.exceptions import UnableToCreateCSR, \ from lemur.certificates.exceptions import UnableToCreateCSR, \
UnableToCreatePrivateKey, MissingFiles UnableToCreatePrivateKey, MissingFiles
@ -127,7 +126,7 @@ def mint(issuer_options):
""" """
authority = issuer_options['authority'] authority = issuer_options['authority']
issuer = get_plugin_by_name(authority.plugin_name) issuer = plugins.get(authority.plugin_name)
# NOTE if we wanted to support more issuers it might make sense to # NOTE if we wanted to support more issuers it might make sense to
# push CSR creation down to the plugin # push CSR creation down to the plugin
path = create_csr(issuer.get_csr_config(issuer_options)) path = create_csr(issuer.get_csr_config(issuer_options))

View File

@ -30,8 +30,7 @@ from lemur.certificates.models import Certificate, get_name_from_arn
from lemur.common.services.aws.iam import get_all_server_certs from lemur.common.services.aws.iam import get_all_server_certs
from lemur.common.services.aws.iam import get_cert_from_arn from lemur.common.services.aws.iam import get_cert_from_arn
from lemur.common.services.issuers.manager import get_plugin_by_name from lemur.plugins.base import plugins
def aws(): def aws():
""" """
@ -101,7 +100,7 @@ def cloudca():
""" """
user = user_service.get_by_email('lemur@nobody') user = user_service.get_by_email('lemur@nobody')
# sync all new certificates/authorities not created through lemur # sync all new certificates/authorities not created through lemur
issuer = get_plugin_by_name('cloudca') issuer = plugins.get('cloudca')
authorities = issuer.get_authorities() authorities = issuer.get_authorities()
total = 0 total = 0
new = 1 new = 1

64
lemur/common/managers.py Normal file
View File

@ -0,0 +1,64 @@
"""
.. module: lemur.common.managers
: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 flask import current_app
# inspired by https://github.com/getsentry/sentry
class InstanceManager(object):
def __init__(self, class_list=None, instances=True):
if class_list is None:
class_list = []
self.instances = instances
self.update(class_list)
def get_class_list(self):
return self.class_list
def add(self, class_path):
self.cache = None
self.class_list.append(class_path)
def remove(self, class_path):
self.cache = None
self.class_list.remove(class_path)
def update(self, class_list):
"""
Updates the class list and wipes the cache.
"""
self.cache = None
self.class_list = class_list
def all(self):
"""
Returns a list of cached instances.
"""
class_list = list(self.get_class_list())
if not class_list:
self.cache = []
return []
if self.cache is not None:
return self.cache
results = []
for cls_path in class_list:
module_name, class_name = cls_path.rsplit('.', 1)
try:
module = __import__(module_name, {}, {}, class_name)
cls = getattr(module, class_name)
if self.instances:
results.append(cls())
else:
results.append(cls)
except Exception:
current_app.logger.exception('Unable to import %s', cls_path)
continue
self.cache = results
return results

View File

@ -1,37 +0,0 @@
"""
.. module: lemur.common.services.issuers.manager
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson (kglisson@netflix.com)
"""
import pkgutil
from importlib import import_module
from flask import current_app
from lemur.common.services.issuers import plugins
# TODO make the plugin dir configurable
def get_plugin_by_name(plugin_name):
"""
Fetches a given plugin by it's name. We use a known location for issuer plugins and attempt
to load it such that it can be used for issuing certificates.
:param plugin_name:
:return: a plugin `class` :raise Exception: Generic error whenever the plugin specified can not be found.
"""
for importer, modname, ispkg in pkgutil.iter_modules(plugins.__path__):
try:
issuer = import_module('lemur.common.services.issuers.plugins.{0}.{0}'.format(modname))
if issuer.__name__ == plugin_name:
# we shouldn't return bad issuers
issuer_obj = issuer.init()
return issuer_obj
except Exception as e:
current_app.logger.warn("Issuer {0} was unable to be imported: {1}".format(modname, e))
else:
raise Exception("Could not find the specified plugin: {0}".format(plugin_name))

View File

@ -1,27 +0,0 @@
CSR_CONFIG = """
# Configuration for standard CSR generation for Netflix
# Used for procuring CloudCA certificates
# Author: kglisson
# Contact: secops@netflix.com
[ req ]
# Use a 2048 bit private key
default_bits = 2048
default_keyfile = key.pem
prompt = no
encrypt_key = no
# base request
distinguished_name = req_distinguished_name
# distinguished_name
[ req_distinguished_name ]
countryName = "{country}" # C=
stateOrProvinceName = "{state}" # ST=
localityName = "{location}" # L=
organizationName = "{organization}" # O=
organizationalUnitName = "{organizationalUnit}" # OU=
# This is the hostname/subject name on the certificate
commonName = "{commonName}" # CN=
"""

View File

@ -12,6 +12,7 @@
import os import os
import imp import imp
import errno import errno
import pkg_resources
from logging import Formatter from logging import Formatter
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
@ -51,6 +52,7 @@ def create_app(app_name=None, blueprints=None, config=None):
configure_blueprints(app, blueprints) configure_blueprints(app, blueprints)
configure_extensions(app) configure_extensions(app)
configure_logging(app) configure_logging(app)
install_plugins(app)
return app return app
@ -91,7 +93,7 @@ def configure_app(app, config=None):
elif os.path.isfile(os.path.expanduser("~/.lemur/lemur.conf.py")): elif os.path.isfile(os.path.expanduser("~/.lemur/lemur.conf.py")):
app.config.from_object(from_file(os.path.expanduser("~/.lemur/lemur.conf.py"))) app.config.from_object(from_file(os.path.expanduser("~/.lemur/lemur.conf.py")))
else: else:
app.config.from_object(from_file(os.path.join(os.getcwd(), 'default.conf.py'))) app.config.from_object(from_file(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'default.conf.py')))
@ -136,3 +138,26 @@ def configure_logging(app):
app.logger.setLevel(app.config.get('LOG_LEVEL', 'DEBUG')) app.logger.setLevel(app.config.get('LOG_LEVEL', 'DEBUG'))
app.logger.addHandler(handler) app.logger.addHandler(handler)
def install_plugins(app):
"""
Installs new issuers that are not currently bundled with Lemur.
:param settings:
:return:
"""
from lemur.plugins.base import register
# entry_points={
# 'lemur.plugins': [
# 'verisign = lemur_verisign.plugin:VerisignPlugin'
# ],
# },
for ep in pkg_resources.iter_entry_points('lemur.plugins'):
try:
plugin = ep.load()
except Exception:
import sys
import traceback
app.logger.error("Failed to load plugin %r:\n%s\n" % (ep.name, traceback.format_exc()))
else:
register(plugin)

View File

@ -333,7 +333,7 @@ class InitializeApp(Command):
else: else:
sys.stdout.write("[-] Default user has already been created, skipping...!\n") sys.stdout.write("[-] Default user has already been created, skipping...!\n")
if current_app.app.config.get('AWS_ACCOUNT_MAPPINGS'): if current_app.config.get('AWS_ACCOUNT_MAPPINGS'):
for account_name, account_number in current_app.config.get('AWS_ACCOUNT_MAPPINGS').items(): for account_name, account_number in current_app.config.get('AWS_ACCOUNT_MAPPINGS').items():
account = account_service.get_by_account_number(account_number) account = account_service.get_by_account_number(account_number)
@ -346,45 +346,6 @@ class InitializeApp(Command):
sys.stdout.write("[/] Done!\n") sys.stdout.write("[/] Done!\n")
#def install_issuers(settings):
# """
# Installs new issuers that are not currently bundled with Lemur.
#
# :param settings:
# :return:
# """
# from lemur.issuers import register
# # entry_points={
# # 'lemur.issuers': [
# # 'verisign = lemur_issuers.issuers:VerisignPlugin'
# # ],
# # },
# installed_apps = list(settings.INSTALLED_APPS)
# for ep in pkg_resources.iter_entry_points('lemur.apps'):
# try:
# issuer = ep.load()
# except Exception:
# import sys
# import traceback
#
# sys.stderr.write("Failed to load app %r:\n%s\n" % (ep.name, traceback.format_exc()))
# else:
# installed_apps.append(ep.module_name)
# settings.INSTALLED_APPS = tuple(installed_apps)
#
# for ep in pkg_resources.iter_entry_points('lemur.issuers'):
# try:
# issuer = ep.load()
# except Exception:
# import sys
# import traceback
#
# sys.stderr.write("Failed to load issuer %r:\n%s\n" % (ep.name, traceback.format_exc()))
# else:
# register(issuer)
class CreateUser(Command): class CreateUser(Command):
""" """
This command allows for the creation of a new user within Lemur This command allows for the creation of a new user within Lemur

View File

@ -0,0 +1,4 @@
from __future__ import absolute_import
from lemur.plugins.base import * # NOQA
from lemur.plugins.bases import * # NOQA

View File

@ -0,0 +1,16 @@
"""
.. module: lemur.plugins.base
: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 __future__ import absolute_import, print_function
from lemur.plugins.base.manager import PluginManager
from lemur.plugins.base.v1 import * # NOQA
plugins = PluginManager()
register = plugins.register
unregister = plugins.unregister

View File

@ -0,0 +1,59 @@
"""
.. module: lemur.plugins.base.manager
: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 current_app
from lemur.common.managers import InstanceManager
# inspired by https://github.com/getsentry/sentry
class PluginManager(InstanceManager):
def __iter__(self):
return iter(self.all())
def __len__(self):
return sum(1 for i in self.all())
def all(self, version=1):
for plugin in sorted(super(PluginManager, self).all(), key=lambda x: x.get_title()):
if not plugin.is_enabled():
continue
if version is not None and plugin.__version__ != version:
continue
yield plugin
def get(self, slug):
for plugin in self.all(version=1):
if plugin.slug == slug:
return plugin
for plugin in self.all(version=2):
if plugin.slug == slug:
return plugin
raise KeyError(slug)
def first(self, func_name, *args, **kwargs):
version = kwargs.pop('version', 1)
for plugin in self.all(version=version):
try:
result = getattr(plugin, func_name)(*args, **kwargs)
except Exception as e:
current_app.logger.error('Error processing %s() on %r: %s', func_name, plugin.__class__, e, extra={
'func_arg': args,
'func_kwargs': kwargs,
}, exc_info=True)
continue
if result is not None:
return result
def register(self, cls):
self.add('%s.%s' % (cls.__module__, cls.__name__))
return cls
def unregister(self, cls):
self.remove('%s.%s' % (cls.__module__, cls.__name__))
return cls

117
lemur/plugins/base/v1.py Normal file
View File

@ -0,0 +1,117 @@
"""
.. module: lemur.plugins.base.v1
: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 threading import local
# stolen from https://github.com/getsentry/sentry/
class PluginMount(type):
def __new__(cls, name, bases, attrs):
new_cls = type.__new__(cls, name, bases, attrs)
if IPlugin in bases:
return new_cls
if new_cls.title is None:
new_cls.title = new_cls.__name__
if not new_cls.slug:
new_cls.slug = new_cls.title.replace(' ', '-').lower()
return new_cls
class IPlugin(local):
"""
Plugin interface. Should not be inherited from directly.
A plugin should be treated as if it were a singleton. The owner does not
control when or how the plugin gets instantiated, nor is it guaranteed that
it will happen, or happen more than once.
>>> from lemur.plugins import Plugin
>>>
>>> class MyPlugin(Plugin):
>>> def get_title(self):
>>> return 'My Plugin'
As a general rule all inherited methods should allow ``**kwargs`` to ensure
ease of future compatibility.
"""
# Generic plugin information
title = None
slug = None
description = None
version = None
author = None
author_url = None
resource_links = ()
# Configuration specifics
conf_key = None
conf_title = None
# Global enabled state
enabled = True
can_disable = True
def is_enabled(self, project=None):
"""
Returns a boolean representing if this plugin is enabled.
If ``project`` is passed, it will limit the scope to that project.
>>> plugin.is_enabled()
"""
if not self.enabled:
return False
if not self.can_disable:
return True
return True
def get_conf_key(self):
"""
Returns a string representing the configuration keyspace prefix for this plugin.
"""
if not self.conf_key:
self.conf_key = self.get_conf_title().lower().replace(' ', '_')
return self.conf_key
def get_conf_title(self):
"""
Returns a string representing the title to be shown on the configuration page.
"""
return self.conf_title or self.get_title()
def get_title(self):
"""
Returns the general title for this plugin.
>>> plugin.get_title()
"""
return self.title
def get_description(self):
"""
Returns the description for this plugin. This is shown on the plugin configuration
page.
>>> plugin.get_description()
"""
return self.description
def get_resource_links(self):
"""
Returns a list of tuples pointing to various resources for this plugin.
>>> def get_resource_links(self):
>>> return [
>>> ('Documentation', 'http://sentry.readthedocs.org'),
>>> ('Bug Tracker', 'https://github.com/getsentry/sentry/issues'),
>>> ('Source', 'https://github.com/getsentry/sentry'),
>>> ]
"""
return self.resource_links
class Plugin(IPlugin):
"""
A plugin should be treated as if it were a singleton. The owner does not
control when or how the plugin gets instantiated, nor is it guaranteed that
it will happen, or happen more than once.
"""
__version__ = 1
__metaclass__ = PluginMount

View File

@ -0,0 +1,2 @@
from .destination import DestinationPlugin # NOQA
from .issuer import IssuerPlugin # NOQA

View File

@ -0,0 +1,13 @@
"""
.. module: lemur.bases.destination
: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 DestinationPlugin(Plugin):
pass

View File

@ -1,23 +1,18 @@
""" """
.. module: authority .. module: lemur.bases.issuer
:platform: Unix :platform: Unix
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details. :license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
""" """
from flask import current_app from lemur.plugins.base import Plugin
class IssuerPlugin(Plugin):
class Issuer(object):
""" """
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.
""" """
def __init__(self):
self.dry_run = current_app.config.get('DRY_RUN')
def create_certificate(self): def create_certificate(self):
raise NotImplementedError raise NotImplementedError

View File

View File

@ -18,10 +18,8 @@ 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.common.services.issuers.issuer import Issuer from lemur.plugins.bases import IssuerPlugin
from lemur.plugins import lemur_cloudca as cloudca
from lemur.common.services.issuers.plugins import cloudca
from lemur.authorities import service as authority_service from lemur.authorities import service as authority_service
@ -144,7 +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 CloudCA(Issuer): class CloudCAPlugin(IssuerPlugin):
title = 'CloudCA' title = 'CloudCA'
slug = 'cloudca' slug = 'cloudca'
description = 'Enables the creation of certificates from the cloudca API.' description = 'Enables the creation of certificates from the cloudca API.'
@ -164,7 +162,7 @@ class CloudCA(Issuer):
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(CloudCA, self).__init__(*args, **kwargs) super(CloudCAPlugin, self).__init__(*args, **kwargs)
def create_authority(self, options): def create_authority(self, options):
""" """
@ -261,15 +259,6 @@ class CloudCA(Issuer):
return cert, "".join(intermediates), return cert, "".join(intermediates),
def get_csr_config(self, issuer_options):
"""
Get a valid CSR for use with CloudCA
:param issuer_options:
:return:
"""
return cloudca.constants.CSR_CONFIG.format(**issuer_options)
def random(self, length=10): def random(self, length=10):
""" """
Uses CloudCA as a decent source of randomness. Uses CloudCA as a decent source of randomness.
@ -340,7 +329,3 @@ class CloudCA(Issuer):
response = self.session.get(self.url + endpoint, timeout=10, verify=self.ca_bundle) response = self.session.get(self.url + endpoint, timeout=10, verify=self.ca_bundle)
return process_response(response) return process_response(response)
def init():
return CloudCA()

View File

@ -1,42 +1,3 @@
CSR_CONFIG = """
# Configuration for standard CSR generation for Netflix
# Used for procuring VeriSign certificates
# Author: jachan
# Contact: cloudsecurity@netflix.com
[ req ]
# Use a 2048 bit private key
default_bits = 2048
default_keyfile = key.pem
prompt = no
encrypt_key = no
# base request
distinguished_name = req_distinguished_name
# extensions
# Uncomment the following line if you are requesting a SAN cert
{is_san_comment}req_extensions = req_ext
# distinguished_name
[ req_distinguished_name ]
countryName = "US" # C=
stateOrProvinceName = "CALIFORNIA" # ST=
localityName = "Los Gatos" # L=
organizationName = "Netflix, Inc." # O=
organizationalUnitName = "{OU}" # OU=
# This is the hostname/subject name on the certificate
commonName = "{DNS[0]}" # CN=
[ req_ext ]
# Uncomment the following line if you are requesting a SAN cert
{is_san_comment}subjectAltName = @alt_names
[alt_names]
# Put your SANs here
{DNS_LINES}
"""
VERISIGN_INTERMEDIATE = """ VERISIGN_INTERMEDIATE = """
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIFFTCCA/2gAwIBAgIQKC4nkXkzkuQo8iGnTsk3rjANBgkqhkiG9w0BAQsFADCB MIIFFTCCA/2gAwIBAgIQKC4nkXkzkuQo8iGnTsk3rjANBgkqhkiG9w0BAQsFADCB
@ -70,7 +31,6 @@ J+71/xuzAYN6
-----END CERTIFICATE----- -----END CERTIFICATE-----
""" """
VERISIGN_ROOT = """ VERISIGN_ROOT = """
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw

View File

@ -1,5 +1,5 @@
""" """
.. module: lemur.common.services.issuers.plugins.verisign.verisign .. module: lemur.plugins.lemur_verisign.verisign
:platform: Unix :platform: Unix
:synopsis: This module is responsible for communicating with the VeriSign VICE 2.0 API. :synopsis: This module is responsible for communicating with the VeriSign VICE 2.0 API.
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
@ -13,10 +13,9 @@ import xmltodict
from flask import current_app from flask import current_app
from lemur.common.services.issuers.issuer import Issuer from lemur.plugins.bases import IssuerPlugin
from lemur.common.services.issuers.plugins import verisign from lemur.plugins import lemur_verisign as verisign
from lemur.plugins.lemur_verisign import constants
from lemur.certificates.exceptions import InsufficientDomains
# https://support.venafi.com/entries/66445046-Info-VeriSign-Error-Codes # https://support.venafi.com/entries/66445046-Info-VeriSign-Error-Codes
@ -58,7 +57,7 @@ VERISIGN_ERRORS = {
} }
class Verisign(Issuer): class VerisignPlugin(IssuerPlugin):
title = 'VeriSign' title = 'VeriSign'
slug = 'verisign' slug = 'verisign'
description = 'Enables the creation of certificates by the VICE2.0 verisign API.' description = 'Enables the creation of certificates by the VICE2.0 verisign API.'
@ -70,7 +69,7 @@ class Verisign(Issuer):
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(Verisign, self).__init__(*args, **kwargs) super(VerisignPlugin, self).__init__(*args, **kwargs)
@staticmethod @staticmethod
def handle_response(content): def handle_response(content):
@ -127,41 +126,7 @@ class Verisign(Issuer):
response = self.session.post(url, data=data) response = self.session.post(url, data=data)
cert = self.handle_response(response.content)['Response']['Certificate'] cert = self.handle_response(response.content)['Response']['Certificate']
return cert, verisign.constants.VERISIGN_INTERMEDIATE, return cert, constants.VERISIGN_INTERMEDIATE,
def get_csr_config(self, issuer_options):
"""
Used to generate a valid CSR for the given Certificate Authority.
:param issuer_options:
:return: :raise InsufficientDomains:
"""
domains = []
if issuer_options.get('commonName'):
domains.append(issuer_options.get('commonName'))
if issuer_options.get('extensions'):
for n in issuer_options['extensions']['subAltNames']['names']:
if n['value']:
domains.append(n['value'])
is_san_comment = "#"
dns_lines = []
if len(domains) < 1:
raise InsufficientDomains
elif len(domains) > 1:
is_san_comment = ""
for domain_line in list(set(domains)):
dns_lines.append("DNS.{} = {}".format(len(dns_lines) + 1, domain_line))
return verisign.constants.CSR_CONFIG.format(
is_san_comment=is_san_comment,
OU=issuer_options.get('organizationalUnit', 'Operations'),
DNS=domains,
DNS_LINES="\n".join(dns_lines))
@staticmethod @staticmethod
def create_authority(options): def create_authority(options):
@ -173,7 +138,7 @@ class Verisign(Issuer):
:return: :return:
""" """
role = {'username': '', 'password': '', 'name': 'verisign'} role = {'username': '', 'password': '', 'name': 'verisign'}
return verisign.constants.VERISIGN_ROOT, "", [role] return constants.VERISIGN_ROOT, "", [role]
def get_available_units(self): def get_available_units(self):
""" """
@ -189,6 +154,3 @@ class Verisign(Issuer):
def get_authorities(self): def get_authorities(self):
pass pass
def init():
return Verisign()

View File

@ -103,6 +103,10 @@ setup(
'console_scripts': [ 'console_scripts': [
'lemur = lemur.manage:main', 'lemur = lemur.manage:main',
], ],
'lemur.plugins': [
'verisign = lemur.plugins.lemur_verisign.plugin:VerisignPlugin',
'cloudca = lemur.plugins.lemur_cloudca.plugin:CloudCAPlugin',
],
}, },
classifiers=[ classifiers=[
'Framework :: Flask', 'Framework :: Flask',