Merge pull request #19 from kevgliss/plugins

New plugin architecture
This commit is contained in:
kevgliss 2015-07-08 16:57:08 -07:00
commit 389ad1981b
49 changed files with 771 additions and 1088 deletions

View File

@ -1,11 +1,11 @@
Writing a Plugin Writing a Plugin
================ ================
**The plugin interface is a work in progress.**
Several interfaces exist for extending Lemur: Several interfaces exist for extending Lemur:
* Issuers (lemur.issuers) * Issuer (lemur.plugins.base.issuer)
* Destination (lemur.plugins.base.destination)
* Source (lemur.plugins.base.source)
Structure Structure
--------- ---------
@ -29,9 +29,9 @@ if you want to pull the version using pkg_resources (which is what we recommend)
Inside of ``plugin.py``, you'll declare your Plugin class:: Inside of ``plugin.py``, you'll declare your Plugin class::
import lemur_pluginname import lemur_pluginname
from lemur.common.services.issuers.plugins import Issuer from lemur.plugins.base.issuer import IssuerPlugin
class PluginName(Plugin): class PluginName(IssuerPlugin):
title = 'Plugin Name' title = 'Plugin Name'
slug = 'pluginname' slug = 'pluginname'
description = 'My awesome plugin!' description = 'My awesome plugin!'
@ -55,27 +55,43 @@ And you'll register it via ``entry_points`` in your ``setup.py``::
) )
That's it! Users will be able to install your plugin via ``pip install <package name>`` and configure it That's it! Users will be able to install your plugin via ``pip install <package name>``.
via the web interface based on the hooks you enabled.
Interfaces
==========
Lemur has several different plugin interfaces that are used to extend Lemur, each of them require
that you subclass and override their functions in order for your plugin to function.
Permissions Issuer
=========== ------
As described in the plugin interface, Lemur provides a suite of permissions. Issuer plugins are to be used when you want to allow Lemur to use external services to create certificates.
In the simple case this means that you have one Certificate Authority and you ask it for certificates given a
few parameters. In a more advanced case this could mean that this third party not only allows you to create certifcates
but also allows you to create Certificate Authorities and Sub Certificate Authorities.
In most cases, a admin (that is, if User.is_admin is ``True``), will be granted implicit permissions The `IssuerPlugin` interface only required that you implement one function::
on everything.
This page attempts to describe those permissions, and the contextual objects along with them. def create_certificate(self, options):
# requests.get('a third party')
.. data:: add_project
Controls whether a user can create a new project. Lemur will pass a dictionary of all possible options for certificate creation.
:: Optionally the `IssuerPlugin` exposes another function for authority create::
>>> has_perm('add_project', user) def create_authority(self, options):
# request.get('a third party')
If implemented this function will be used to allow users to create external Certificate Authorities. From this function
you are expected to return the ROOT certificate authority, any intermediates that Authority might provide and any roles
you wish to be associated with this authority.
.. Note:: You do not need to associate roles to the authority at creation time as they can always be associated after the
fact.
Testing Testing
@ -149,3 +165,5 @@ Running tests follows the py.test standard. As long as your test files and metho
=========================== 1 passed in 0.35 seconds ============================ =========================== 1 passed in 0.35 seconds ============================
.. SeeAlso:: Lemur bundles several plugins that use the same interfaces mentioned above. View the source: #TODO

View File

@ -38,7 +38,7 @@ function browserSyncInit(baseDir, files, browser) {
gulp.task('serve', ['watch'], function () { gulp.task('serve', ['watch'], function () {
browserSyncInit([ browserSyncInit([
'.tmp', '.tmp',
'app' 'lemur/static/app'
], [ ], [
'.tmp/*.html', '.tmp/*.html',
'.tmp/styles/**/*.css', '.tmp/styles/**/*.css',

View File

@ -3,7 +3,7 @@
var gulp = require('gulp'); var gulp = require('gulp');
gulp.task('watch', ['dev:styles', 'dev:scripts', 'dev:inject'] ,function () { gulp.task('watch', ['dev:styles', 'dev:scripts', 'dev:inject', 'dev:fonts'] ,function () {
gulp.watch('app/styles/**/*.less', ['dev:styles']); gulp.watch('app/styles/**/*.less', ['dev:styles']);
gulp.watch('app/styles/**/*.css', ['dev:styles']); gulp.watch('app/styles/**/*.css', ['dev:styles']);
gulp.watch('app/**/*.js', ['dev:scripts']); gulp.watch('app/**/*.js', ['dev:scripts']);

View File

@ -8,8 +8,6 @@
""" """
from flask import jsonify
from lemur import factory from lemur import factory
from lemur.users.views import mod as users_bp from lemur.users.views import mod as users_bp

View File

@ -72,13 +72,13 @@ def create_token(user):
:param user: :param user:
:return: :return:
""" """
expiration_delta = timedelta(days=int(current_app.config.get('TOKEN_EXPIRATION', 1))) expiration_delta = timedelta(days=int(current_app.config.get('LEMUR_TOKEN_EXPIRATION', 1)))
payload = { payload = {
'sub': user.id, 'sub': user.id,
'iat': datetime.now(), 'iat': datetime.now(),
'exp': datetime.now() + expiration_delta 'exp': datetime.now() + expiration_delta
} }
token = jwt.encode(payload, current_app.config['TOKEN_SECRET']) token = jwt.encode(payload, current_app.config['LEMUR_TOKEN_SECRET'])
return token.decode('unicode_escape') return token.decode('unicode_escape')
@ -102,7 +102,7 @@ def login_required(f):
return dict(message='Token is invalid'), 403 return dict(message='Token is invalid'), 403
try: try:
payload = jwt.decode(token, current_app.config['TOKEN_SECRET']) payload = jwt.decode(token, current_app.config['LEMUR_TOKEN_SECRET'])
except jwt.DecodeError: except jwt.DecodeError:
return dict(message='Token is invalid'), 403 return dict(message='Token is invalid'), 403
except jwt.ExpiredSignatureError: except jwt.ExpiredSignatureError:

View File

@ -14,8 +14,6 @@ from flask import g, Blueprint, current_app, abort
from flask.ext.restful import reqparse, Resource, Api from flask.ext.restful import reqparse, Resource, Api
from flask.ext.principal import Identity, identity_changed from flask.ext.principal import Identity, identity_changed
from lemur.common.crypto import unlock
from lemur.auth.permissions import admin_permission from lemur.auth.permissions import admin_permission
from lemur.users import service as user_service from lemur.users import service as user_service
from lemur.roles import service as role_service from lemur.roles import service as role_service
@ -234,24 +232,7 @@ class Ping(Resource):
return dict(token=create_token(user)) return dict(token=create_token(user))
class Unlock(AuthenticatedResource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
super(Unlock, self).__init__()
@admin_permission.require(http_exception=403)
def post(self):
self.reqparse.add_argument('password', type=str, required=True, location='json')
args = self.reqparse.parse_args()
unlock(args['password'])
return {
"message": "You have successfully unlocked this Lemur instance",
"type": "success"
}
api.add_resource(Login, '/auth/login', endpoint='login') api.add_resource(Login, '/auth/login', endpoint='login')
api.add_resource(Ping, '/auth/ping', endpoint='ping') api.add_resource(Ping, '/auth/ping', endpoint='ping')
api.add_resource(Unlock, '/auth/unlock', endpoint='unlock')

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,12 +49,12 @@ 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)
cert = cert_service.save_cert(cert_body, None, intermediate, None, None, None) cert = cert_service.save_cert(cert_body, None, intermediate, None)
cert.user = g.current_user cert.user = g.current_user
# we create and attach any roles that the issuer gives us # we create and attach any roles that the issuer gives us
@ -65,9 +65,11 @@ def create(kwargs):
password=r['password'], password=r['password'],
description="{0} auto generated role".format(kwargs.get('pluginName')), description="{0} auto generated role".format(kwargs.get('pluginName')),
username=r['username']) username=r['username'])
# the user creating the authority should be able to administer it # the user creating the authority should be able to administer it
if role.username == 'admin': if role.username == 'admin':
g.current_user.roles.append(role) g.current_user.roles.append(role)
role_objs.append(role) role_objs.append(role)
authority = Authority( authority = Authority(
@ -80,7 +82,6 @@ def create(kwargs):
roles=role_objs roles=role_objs
) )
# do this last encase we need to roll back/abort
database.update(cert) database.update(cert)
authority = database.create(authority) authority = database.create(authority)

View File

@ -20,13 +20,12 @@ from sqlalchemy_utils import EncryptedType
from lemur.database import db from lemur.database import db
from lemur.domains.models import Domain from lemur.domains.models import Domain
from lemur.users import service as user_service
from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE, NONSTANDARD_NAMING_TEMPLATE from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE, NONSTANDARD_NAMING_TEMPLATE
from lemur.models import certificate_associations, certificate_account_associations from lemur.models import certificate_associations, certificate_account_associations
def create_name(issuer, not_before, not_after, common_name, san): def create_name(issuer, not_before, not_after, subject, san):
""" """
Create a name for our certificate. A naming standard Create a name for our certificate. A naming standard
is based on a series of templates. The name includes is based on a series of templates. The name includes
@ -36,11 +35,6 @@ def create_name(issuer, not_before, not_after, common_name, san):
:rtype : str :rtype : str
:return: :return:
""" """
delchars = ''.join(c for c in map(chr, range(256)) if not c.isalnum())
# aws doesn't allow special chars
subject = common_name.replace('*', "WILDCARD")
issuer = issuer.translate(None, delchars)
if san: if san:
t = SAN_NAMING_TEMPLATE t = SAN_NAMING_TEMPLATE
else: else:
@ -53,7 +47,14 @@ def create_name(issuer, not_before, not_after, common_name, san):
not_after=not_after.strftime('%Y%m%d') not_after=not_after.strftime('%Y%m%d')
) )
return temp # NOTE we may want to give more control over naming
# aws doesn't allow special chars except '-'
disallowed_chars = ''.join(c for c in map(chr, range(256)) if not c.isalnum())
disallowed_chars = disallowed_chars.replace("-", "")
temp = temp.replace('*', "WILDCARD")
temp = temp.translate(None, disallowed_chars)
# white space is silly too
return temp.replace(" ", "-")
def cert_get_cn(cert): def cert_get_cn(cert):
@ -85,11 +86,6 @@ def cert_get_domains(cert):
domains.append(entry) domains.append(entry)
except Exception as e: except Exception as e:
current_app.logger.warning("Failed to get SubjectAltName: {0}".format(e)) current_app.logger.warning("Failed to get SubjectAltName: {0}".format(e))
# do a simple check to make sure it's a real domain
common_name = cert_get_cn(cert)
if '.' in common_name:
domains.append(common_name)
return domains return domains
@ -111,11 +107,8 @@ def cert_is_san(cert):
:param cert: :param cert:
:return: Bool :return: Bool
""" """
domains = cert_get_domains(cert) if len(cert_get_domains(cert)) > 1:
if len(domains) > 1:
return True return True
return False
def cert_is_wildcard(cert): def cert_is_wildcard(cert):
""" """
@ -127,7 +120,6 @@ def cert_is_wildcard(cert):
domains = cert_get_domains(cert) domains = cert_get_domains(cert)
if len(domains) == 1 and domains[0][0:1] == "*": if len(domains) == 1 and domains[0][0:1] == "*":
return True return True
return False
def cert_get_bitstrength(cert): def cert_get_bitstrength(cert):
@ -205,8 +197,8 @@ class Certificate(db.Model):
owner = Column(String(128)) owner = Column(String(128))
body = Column(Text()) body = Column(Text())
private_key = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY'))) private_key = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY')))
challenge = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY'))) challenge = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY'))) # TODO deprecate
csr_config = Column(Text()) csr_config = Column(Text()) # TODO deprecate
status = Column(String(128)) status = Column(String(128))
deleted = Column(Boolean, index=True) deleted = Column(Boolean, index=True)
name = Column(String(128)) name = Column(String(128))
@ -227,13 +219,11 @@ class Certificate(db.Model):
domains = relationship("Domain", secondary=certificate_associations, backref="certificate") domains = relationship("Domain", secondary=certificate_associations, backref="certificate")
elb_listeners = relationship("Listener", lazy='dynamic', backref='certificate') elb_listeners = relationship("Listener", lazy='dynamic', backref='certificate')
def __init__(self, body, private_key=None, challenge=None, chain=None, csr_config=None): def __init__(self, body, private_key=None, chain=None):
self.body = body self.body = body
# We encrypt the private_key on creation # We encrypt the private_key on creation
self.private_key = private_key self.private_key = private_key
self.chain = chain self.chain = chain
self.csr_config = csr_config
self.challenge = challenge
cert = x509.load_pem_x509_certificate(str(self.body), default_backend()) cert = x509.load_pem_x509_certificate(str(self.body), default_backend())
self.bits = cert_get_bitstrength(cert) self.bits = cert_get_bitstrength(cert)
self.issuer = cert_get_issuer(cert) self.issuer = cert_get_issuer(cert)

View File

@ -5,24 +5,17 @@
: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>
""" """
import os
import arrow import arrow
import string import string
import random import random
import hashlib
import datetime
import subprocess
from sqlalchemy import func, or_ from sqlalchemy import func, or_
from flask import g, current_app 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, \
UnableToCreatePrivateKey, MissingFiles
from lemur.accounts.models import Account from lemur.accounts.models import Account
from lemur.accounts import service as account_service from lemur.accounts import service as account_service
@ -30,6 +23,12 @@ from lemur.authorities.models import Authority
from lemur.roles.models import Role from lemur.roles.models import Role
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
def get(cert_id): def get(cert_id):
""" """
@ -127,24 +126,18 @@ 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
# push CSR creation down to the plugin
path = create_csr(issuer.get_csr_config(issuer_options))
challenge, csr, csr_config, private_key = load_ssl_pack(path)
issuer_options['challenge'] = challenge csr, private_key = create_csr(issuer_options)
issuer_options['challenge'] = create_challenge()
issuer_options['creator'] = g.user.email issuer_options['creator'] = g.user.email
cert_body, cert_chain = issuer.create_certificate(csr, issuer_options) cert_body, cert_chain = issuer.create_certificate(csr, issuer_options)
cert = save_cert(cert_body, private_key, cert_chain, challenge, csr_config, issuer_options.get('accounts')) cert = save_cert(cert_body, private_key, cert_chain, issuer_options.get('accounts'))
cert.user = g.user cert.user = g.user
cert.authority = authority cert.authority = authority
database.update(cert) database.update(cert)
# securely delete pack after saving it to RDS and IAM (if applicable)
delete_ssl_pack(path)
return cert, private_key, cert_chain, return cert, private_key, cert_chain,
@ -152,7 +145,7 @@ def import_certificate(**kwargs):
""" """
Uploads already minted certificates and pulls the required information into Lemur. Uploads already minted certificates and pulls the required information into Lemur.
This is to be used for certificates that are reated outside of Lemur but This is to be used for certificates that are created outside of Lemur but
should still be tracked. should still be tracked.
Internally this is used to bootstrap Lemur with external Internally this is used to bootstrap Lemur with external
@ -180,7 +173,7 @@ def import_certificate(**kwargs):
return cert return cert
def save_cert(cert_body, private_key, cert_chain, challenge, csr_config, accounts): def save_cert(cert_body, private_key, cert_chain, accounts):
""" """
Determines if the certificate needs to be uploaded to AWS or other services. Determines if the certificate needs to be uploaded to AWS or other services.
@ -189,9 +182,9 @@ def save_cert(cert_body, private_key, cert_chain, challenge, csr_config, account
:param cert_chain: :param cert_chain:
:param challenge: :param challenge:
:param csr_config: :param csr_config:
:param account_ids: :param accounts:
""" """
cert = Certificate(cert_body, private_key, challenge, cert_chain, csr_config) cert = Certificate(cert_body, private_key, cert_chain)
# if we have an AWS accounts lets upload them # if we have an AWS accounts lets upload them
if accounts: if accounts:
for account in accounts: for account in accounts:
@ -211,8 +204,6 @@ def upload(**kwargs):
kwargs.get('public_cert'), kwargs.get('public_cert'),
kwargs.get('private_key'), kwargs.get('private_key'),
kwargs.get('intermediate_cert'), kwargs.get('intermediate_cert'),
None,
None,
kwargs.get('accounts') kwargs.get('accounts')
) )
@ -230,6 +221,7 @@ def create(**kwargs):
cert.owner = kwargs['owner'] cert.owner = kwargs['owner']
database.create(cert) database.create(cert)
cert.description = kwargs['description']
g.user.certificates.append(cert) g.user.certificates.append(cert)
database.update(g.user) database.update(g.user)
return cert return cert
@ -302,94 +294,92 @@ def create_csr(csr_config):
:param csr_config: :param csr_config:
""" """
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
# we create a no colliding file name # TODO When we figure out a better way to validate these options they should be parsed as unicode
path = create_path(hashlib.md5(csr_config).hexdigest()) builder = x509.CertificateSigningRequestBuilder()
builder = builder.subject_name(x509.Name([
x509.NameAttribute(x509.OID_COMMON_NAME, unicode(csr_config['commonName'])),
x509.NameAttribute(x509.OID_ORGANIZATION_NAME, unicode(csr_config['organization'])),
x509.NameAttribute(x509.OID_ORGANIZATIONAL_UNIT_NAME, unicode(csr_config['organizationalUnit'])),
x509.NameAttribute(x509.OID_COUNTRY_NAME, unicode(csr_config['country'])),
x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, unicode(csr_config['state'])),
x509.NameAttribute(x509.OID_LOCALITY_NAME, unicode(csr_config['location'])),
]))
challenge = create_challenge() builder = builder.add_extension(
challenge_path = os.path.join(path, 'challenge.txt') x509.BasicConstraints(ca=False, path_length=None), critical=True,
)
with open(challenge_path, 'w') as c: #for k, v in csr_config.get('extensions', {}).items():
c.write(challenge) # if k == 'subAltNames':
# builder = builder.add_extension(
# x509.SubjectAlternativeName([x509.DNSName(n) for n in v]), critical=True,
# )
csr_path = os.path.join(path, 'csr_config.txt') # TODO support more CSR options, none of the authorities support these atm
# builder.add_extension(
# x509.KeyUsage(
# digital_signature=digital_signature,
# content_commitment=content_commitment,
# key_encipherment=key_enipherment,
# data_encipherment=data_encipherment,
# key_agreement=key_agreement,
# key_cert_sign=key_cert_sign,
# crl_sign=crl_sign,
# encipher_only=enchipher_only,
# decipher_only=decipher_only
# ), critical=True
# )
#
# # we must maintain our own list of OIDs here
# builder.add_extension(
# x509.ExtendedKeyUsage(
# server_authentication=server_authentication,
# email=
# )
# )
#
# builder.add_extension(
# x509.AuthorityInformationAccess()
# )
#
# builder.add_extension(
# x509.AuthorityKeyIdentifier()
# )
#
# builder.add_extension(
# x509.SubjectKeyIdentifier()
# )
#
# builder.add_extension(
# x509.CRLDistributionPoints()
# )
#
# builder.add_extension(
# x509.ObjectIdentifier(oid)
# )
with open(csr_path, 'w') as f: request = builder.sign(
f.write(csr_config) private_key, hashes.SHA256(), default_backend()
)
#TODO use cloudCA to seed a -rand file for each call # serialize our private key and CSR
#TODO replace openssl shell calls with cryptograph pem = private_key.private_bytes(
with open('/dev/null', 'w') as devnull: encoding=serialization.Encoding.PEM,
code = subprocess.call(['openssl', 'genrsa', format=serialization.PrivateFormat.TraditionalOpenSSL, # would like to use PKCS8 but AWS ELBs don't like it
'-out', os.path.join(path, 'private.key'), '2048'], encryption_algorithm=serialization.NoEncryption()
stdout=devnull, stderr=devnull) )
if code != 0: csr = request.public_bytes(
raise UnableToCreatePrivateKey(code) encoding=serialization.Encoding.PEM
)
with open('/dev/null', 'w') as devnull:
code = subprocess.call(['openssl', 'req', '-new', '-sha256', '-nodes',
'-config', csr_path, "-key", os.path.join(path, 'private.key'),
"-out", os.path.join(path, 'request.csr')], stdout=devnull, stderr=devnull)
if code != 0:
raise UnableToCreateCSR(code)
return path
def create_path(domain_hash):
"""
:param domain_hash:
:return:
"""
path = os.path.join('/tmp', domain_hash)
try:
os.mkdir(path)
except OSError as e:
now = datetime.datetime.now()
path = os.path.join('/tmp', "{}.{}".format(domain_hash, now.strftime('%s')))
os.mkdir(path)
current_app.logger.warning(e)
current_app.logger.debug("Writing ssl files to: {}".format(path))
return path
def load_ssl_pack(path):
"""
Loads the information created by openssl to be used by other functions.
:param path:
"""
if len(os.listdir(path)) != 4:
raise MissingFiles(path)
with open(os.path.join(path, 'challenge.txt')) as c:
challenge = c.read()
with open(os.path.join(path, 'request.csr')) as r:
csr = r.read()
with open(os.path.join(path, 'csr_config.txt')) as config:
csr_config = config.read()
with open(os.path.join(path, 'private.key')) as key:
private_key = key.read()
return (challenge, csr, csr_config, private_key,)
def delete_ssl_pack(path):
"""
Removes the temporary files associated with CSR creation.
:param path:
"""
subprocess.check_call(['srm', '-r', path])
return csr, pem
def create_challenge(): def create_challenge():
""" """

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

View File

@ -1,185 +0,0 @@
"""
.. module: lemur.common.crypto
:platform: Unix
:synopsis: This module contains all cryptographic function's in Lemur
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
import os
import ssl
import StringIO
import functools
from Crypto import Random
from Crypto.Cipher import AES
from hashlib import sha512
from flask import current_app
from lemur.factory import create_app
old_init = ssl.SSLSocket.__init__
@functools.wraps(old_init)
def ssl_bug(self, *args, **kwargs):
kwargs['ssl_version'] = ssl.PROTOCOL_TLSv1
old_init(self, *args, **kwargs)
ssl.SSLSocket.__init__ = ssl_bug
def derive_key_and_iv(password, salt, key_length, iv_length):
"""
Derives the key and iv from the password and salt.
:param password:
:param salt:
:param key_length:
:param iv_length:
:return: key, iv
"""
d = d_i = ''
while len(d) < key_length + iv_length:
d_i = sha512(d_i + password + salt).digest()
d += d_i
return d[:key_length], d[key_length:key_length+iv_length]
def encrypt(in_file, out_file, password, key_length=32):
"""
Encrypts a file.
:param in_file:
:param out_file:
:param password:
:param key_length:
"""
bs = AES.block_size
salt = Random.new().read(bs - len('Salted__'))
key, iv = derive_key_and_iv(password, salt, key_length, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
out_file.write('Salted__' + salt)
finished = False
while not finished:
chunk = in_file.read(1024 * bs)
if len(chunk) == 0 or len(chunk) % bs != 0:
padding_length = bs - (len(chunk) % bs)
chunk += padding_length * chr(padding_length)
finished = True
out_file.write(cipher.encrypt(chunk))
def decrypt(in_file, out_file, password, key_length=32):
"""
Decrypts a file.
:param in_file:
:param out_file:
:param password:
:param key_length:
:raise ValueError:
"""
bs = AES.block_size
salt = in_file.read(bs)[len('Salted__'):]
key, iv = derive_key_and_iv(password, salt, key_length, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
next_chunk = ''
finished = False
while not finished:
chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs))
if len(next_chunk) == 0:
padding_length = ord(chunk[-1])
if padding_length < 1 or padding_length > bs:
raise ValueError("bad decrypt pad (%d)" % padding_length)
# all the pad-bytes must be the same
if chunk[-padding_length:] != (padding_length * chr(padding_length)):
# this is similar to the bad decrypt:evp_enc.c from openssl program
raise ValueError("bad decrypt")
chunk = chunk[:-padding_length]
finished = True
out_file.write(chunk)
def encrypt_string(string, password):
"""
Encrypts a string.
:param string:
:param password:
:return:
"""
in_file = StringIO.StringIO(string)
enc_file = StringIO.StringIO()
encrypt(in_file, enc_file, password)
enc_file.seek(0)
return enc_file.read()
def decrypt_string(string, password):
"""
Decrypts a string.
:param string:
:param password:
:return:
"""
in_file = StringIO.StringIO(string)
out_file = StringIO.StringIO()
decrypt(in_file, out_file, password)
out_file.seek(0)
return out_file.read()
def lock(password):
"""
Encrypts Lemur's KEY_PATH. This directory can be used to store secrets needed for normal
Lemur operation. This is especially useful for storing secrets needed for communication
with third parties (e.g. external certificate authorities).
Lemur does not assume anything about the contents of the directory and will attempt to
encrypt all files contained within. Currently this has only been tested against plain
text files.
:param password:
"""
dest_dir = os.path.join(current_app.config.get("KEY_PATH"), "encrypted")
if not os.path.exists(dest_dir):
current_app.logger.debug("Creating encryption directory: {0}".format(dest_dir))
os.makedirs(dest_dir)
for root, dirs, files in os.walk(os.path.join(current_app.config.get("KEY_PATH"), 'decrypted')):
for f in files:
source = os.path.join(root, f)
dest = os.path.join(dest_dir, f + ".enc")
with open(source, 'rb') as in_file, open(dest, 'wb') as out_file:
encrypt(in_file, out_file, password)
def unlock(password):
"""
Decrypts Lemur's KEY_PATH, allowing lemur to use the secrets within.
This reverses the :func:`lock` function.
:param password:
"""
dest_dir = os.path.join(current_app.config.get("KEY_PATH"), "decrypted")
source_dir = os.path.join(current_app.config.get("KEY_PATH"), "encrypted")
if not os.path.exists(dest_dir):
current_app.logger.debug("Creating decryption directory: {0}".format(dest_dir))
os.makedirs(dest_dir)
for root, dirs, files in os.walk(source_dir):
for f in files:
source = os.path.join(source_dir, f)
dest = os.path.join(dest_dir, ".".join(f.split(".")[:-1]))
with open(source, 'rb') as in_file, open(dest, 'wb') as out_file:
current_app.logger.debug("Writing file: {0} Source: {1}".format(dest, source))
decrypt(in_file, out_file, password)

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

@ -1,159 +0,0 @@
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 = """
-----BEGIN CERTIFICATE-----
MIIFFTCCA/2gAwIBAgIQKC4nkXkzkuQo8iGnTsk3rjANBgkqhkiG9w0BAQsFADCB
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMTk5OSBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IC0gRzMwHhcNMTMxMDMxMDAwMDAwWhcNMjMxMDMwMjM1OTU5WjB+MQsw
CQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNV
BAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxLzAtBgNVBAMTJlN5bWFudGVjIENs
YXNzIDMgU2VjdXJlIFNlcnZlciBDQSAtIEc0MIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAstgFyhx0LbUXVjnFSlIJluhL2AzxaJ+aQihiw6UwU35VEYJb
A3oNL+F5BMm0lncZgQGUWfm893qZJ4Itt4PdWid/sgN6nFMl6UgfRk/InSn4vnlW
9vf92Tpo2otLgjNBEsPIPMzWlnqEIRoiBAMnF4scaGGTDw5RgDMdtLXO637QYqzu
s3sBdO9pNevK1T2p7peYyo2qRA4lmUoVlqTObQJUHypqJuIGOmNIrLRM0XWTUP8T
L9ba4cYY9Z/JJV3zADreJk20KQnNDz0jbxZKgRb78oMQw7jW2FUyPfG9D72MUpVK
Fpd6UiFjdS8W+cRmvvW1Cdj/JwDNRHxvSz+w9wIDAQABo4IBQDCCATwwHQYDVR0O
BBYEFF9gz2GQVd+EQxSKYCqy9Xr0QxjvMBIGA1UdEwEB/wQIMAYBAf8CAQAwawYD
VR0gBGQwYjBgBgpghkgBhvhFAQc2MFIwJgYIKwYBBQUHAgEWGmh0dHA6Ly93d3cu
c3ltYXV0aC5jb20vY3BzMCgGCCsGAQUFBwICMBwaGmh0dHA6Ly93d3cuc3ltYXV0
aC5jb20vcnBhMC8GA1UdHwQoMCYwJKAioCCGHmh0dHA6Ly9zLnN5bWNiLmNvbS9w
Y2EzLWczLmNybDAOBgNVHQ8BAf8EBAMCAQYwKQYDVR0RBCIwIKQeMBwxGjAYBgNV
BAMTEVN5bWFudGVjUEtJLTEtNTM0MC4GCCsGAQUFBwEBBCIwIDAeBggrBgEFBQcw
AYYSaHR0cDovL3Muc3ltY2QuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBbF1K+1lZ7
9Pc0CUuWysf2IdBpgO/nmhnoJOJ/2S9h3RPrWmXk4WqQy04q6YoW51KN9kMbRwUN
gKOomv4p07wdKNWlStRxPA91xQtzPwBIZXkNq2oeJQzAAt5mrL1LBmuaV4oqgX5n
m7pSYHPEFfe7wVDJCKW6V0o6GxBzHOF7tpQDS65RsIJAOloknO4NWF2uuil6yjOe
soHCL47BJ89A8AShP/U3wsr8rFNtqVNpT+F2ZAwlgak3A/I5czTSwXx4GByoaxbn
5+CdKa/Y5Gk5eZVpuXtcXQGc1PfzSEUTZJXXCm5y2kMiJG8+WnDcwJLgLeVX+OQr
J+71/xuzAYN6
-----END CERTIFICATE-----
"""
VERISIGN_ROOT = """
-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
-----END CERTIFICATE-----
"""
OLD_VERISIGN_INTERMEDIATE = """
-----BEGIN CERTIFICATE-----
MIIFlTCCBH2gAwIBAgIQLP62CQ7ireLp/CI3JPG2vzANBgkqhkiG9w0BAQUFADCB
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMTk5OSBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IC0gRzMwHhcNMTAwMjA4MDAwMDAwWhcNMjAwMjA3MjM1OTU5WjCBtTEL
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQg
aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDEvMC0GA1UEAxMmVmVy
aVNpZ24gQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzMwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCxh4QfwgxF9byrJZenraI+nLr2wTm4i8rCrFbG
5btljkRPTc5v7QlK1K9OEJxoiy6Ve4mbE8riNDTB81vzSXtig0iBdNGIeGwCU/m8
f0MmV1gzgzszChew0E6RJK2GfWQS3HRKNKEdCuqWHQsV/KNLO85jiND4LQyUhhDK
tpo9yus3nABINYYpUHjoRWPNGUFP9ZXse5jUxHGzUL4os4+guVOc9cosI6n9FAbo
GLSa6Dxugf3kzTU2s1HTaewSulZub5tXxYsU5w7HnO1KVGrJTcW/EbGuHGeBy0RV
M5l/JJs/U0V/hhrzPPptf4H1uErT9YU3HLWm0AnkGHs4TvoPAgMBAAGjggGIMIIB
hDASBgNVHRMBAf8ECDAGAQH/AgEAMHAGA1UdIARpMGcwZQYLYIZIAYb4RQEHFwMw
VjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL2NwczAqBggr
BgEFBQcCAjAeGhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhMA4GA1UdDwEB
/wQEAwIBBjBtBggrBgEFBQcBDARhMF+hXaBbMFkwVzBVFglpbWFnZS9naWYwITAf
MAcGBSsOAwIaBBSP5dMahqyNjmvDz4Bq1EgYLHsZLjAlFiNodHRwOi8vbG9nby52
ZXJpc2lnbi5jb20vdnNsb2dvLmdpZjAoBgNVHREEITAfpB0wGzEZMBcGA1UEAxMQ
VmVyaVNpZ25NUEtJLTItNjAdBgNVHQ4EFgQUDURcFlNEwYJ+HSCrJfQBY9i+eaUw
NAYDVR0fBC0wKzApoCegJYYjaHR0cDovL2NybC52ZXJpc2lnbi5jb20vcGNhMy1n
My5jcmwwDQYJKoZIhvcNAQEFBQADggEBAHREFQzFWA4YY+3z8CjDeuuSSG/ghSBJ
olwwlpIX4IjoeYuzT864Hzk2tTeEeODf4YFIVsSxah8nUsGdpgVTUGPPoUJOMXvn
8wJeBSlUDXBwv3td5XbPIPXHy6vmIS6phYRetZUgq1CDTI/pvtWZKXTGM/eYXlLF
6QDvXevUHQjfb3cqQvfLljws85xLxbNFmz7cy9YmiLOd5n+gFC6X5hzSDO7+DDMi
o//+4Q/nk/UId1UCsobqYWVmqs017AmyiAPO/v3sGncYYQY2BMYgla74dZfeDNu4
MXA68Mb6ZdlkhGEmZYVBcOmkaKs+P+SggTofsK27BlpugAtNWjEy5JY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEOzCCA6SgAwIBAgIQSsnqCI7m94zHpfn6OaSTljANBgkqhkiG9w0BAQUFADBf
MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
HhcNMTEwNjA5MDAwMDAwWhcNMjExMTA3MjM1OTU5WjCByjELMAkGA1UEBhMCVVMx
FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMTk5OSBWZXJpU2lnbiwgSW5jLiAtIEZv
ciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAz
IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzMwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLupxS/HgfGh5vGzdzvfjJa5QS
ME/wNkf10JEK9RfIpWHBFkBN+4phkOV2IMERBn2rLG6m9RFBjvotrSphWaRnJkzQ
6LxSW3AgBFjResmkabyDF2StBYu80FjOjYz16/BCSQudlydnMm7hrpMVHHC8IE0v
GN6SiOhshVcRGul+4yYRVKJFllWDyjCJ6NzYo+0qgD9/eWVXPhUgZggvlZO/qkcv
qEaX8BLi/sIKK1Hmdua3RrfiDabMqMNMWVWJ5uhTXBzqnfBiFgunyV8M8N7Cds6v
92ry+kGmojMUyeV6Y9OeYjfVhWWeDuZTJHQbXh0SU1vHLOeDSTsVropouVeXAgMB
AAGjggEGMIIBAjAPBgNVHRMBAf8EBTADAQH/MD0GA1UdIAQ2MDQwMgYEVR0gADAq
MCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vY3BzMDEGA1Ud
HwQqMCgwJqAkoCKGIGh0dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA4G
A1UdDwEB/wQEAwIBBjBtBggrBgEFBQcBDARhMF+hXaBbMFkwVzBVFglpbWFnZS9n
aWYwITAfMAcGBSsOAwIaBBSP5dMahqyNjmvDz4Bq1EgYLHsZLjAlFiNodHRwOi8v
bG9nby52ZXJpc2lnbi5jb20vdnNsb2dvLmdpZjANBgkqhkiG9w0BAQUFAAOBgQBl
2Sr58sJgybnqQQfKNrcYL2iu/gMk5mdU7nTDLNn1M8Fetw6Tz3iejrImFBFT0cjC
EiG0PXsq2BzUS2TsiU+/lYeH3pVk9HPGF9+9GZCX6GmBEmlmStMkQA5ZdRWwRHQX
op4GYNOwg7jdL+afe2dcFqFH284ueQXZ8fT4PuJKoQ==
-----END CERTIFICATE-----
"""

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)
@app.teardown_appcontext @app.teardown_appcontext
def teardown(exception=None): def teardown(exception=None):
@ -141,3 +143,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

@ -1,9 +1,10 @@
#!/usr/bin/env python
import os import os
import sys import sys
import base64 import base64
from gunicorn.config import make_settings from gunicorn.config import make_settings
from cryptography.fernet import Fernet
from flask import current_app from flask import current_app
from flask.ext.script import Manager, Command, Option, Group, prompt_pass from flask.ext.script import Manager, Command, Option, Group, prompt_pass
from flask.ext.migrate import Migrate, MigrateCommand, stamp from flask.ext.migrate import Migrate, MigrateCommand, stamp
@ -20,7 +21,6 @@ from lemur.certificates import sync
from lemur.elbs.sync import sync_all_elbs from lemur.elbs.sync import sync_all_elbs
from lemur import create_app from lemur import create_app
from lemur.common.crypto import encrypt, decrypt, lock, unlock
# Needed to be imported so that SQLAlchemy create_all can find our models # Needed to be imported so that SQLAlchemy create_all can find our models
from lemur.users.models import User from lemur.users.models import User
@ -133,78 +133,6 @@ def create():
stamp(revision='head') stamp(revision='head')
@manager.command
def lock():
"""
Encrypts all of the files in the `keys` directory with the password
given. This is a useful function to ensure that you do no check in
your key files into source code in clear text.
:return:
"""
password = prompt_pass("Please enter the encryption password")
lock(password)
sys.stdout.write("[+] Lemur keys have been encrypted!\n")
@manager.command
def unlock():
"""
Decrypts all of the files in the `keys` directory with the password
given. This is most commonly used during the startup sequence of Lemur
allowing it to go from source code to something that can communicate
with external services.
:return:
"""
password = prompt_pass("Please enter the encryption password")
unlock(password)
sys.stdout.write("[+] Lemur keys have been unencrypted!\n")
@manager.command
def encrypt_file(source):
"""
Utility to encrypt sensitive files, Lemur will decrypt these
files when admin enters the correct password.
Uses AES-256-CBC encryption
"""
dest = source + ".encrypted"
password = prompt_pass("Please enter the encryption password")
password1 = prompt_pass("Please confirm the encryption password")
if password != password1:
sys.stdout.write("[!] Encryption passwords do not match!\n")
return
with open(source, 'rb') as in_file, open(dest, 'wb') as out_file:
encrypt(in_file, out_file, password)
sys.stdout.write("[+] Writing encryption files... {0}!\n".format(dest))
@manager.command
def decrypt_file(source):
"""
Utility to decrypt, Lemur will decrypt these
files when admin enters the correct password.
Assumes AES-256-CBC encryption
"""
# cleanup extensions a bit
if ".encrypted" in source:
dest = ".".join(source.split(".")[:-1]) + ".decrypted"
else:
dest = source + ".decrypted"
password = prompt_pass("Please enter the encryption password")
with open(source, 'rb') as in_file, open(dest, 'wb') as out_file:
decrypt(in_file, out_file, password)
sys.stdout.write("[+] Writing decrypted files... {0}!\n".format(dest))
@manager.command @manager.command
def check_revoked(): def check_revoked():
""" """
@ -346,45 +274,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
@ -492,7 +381,84 @@ def create_config(config_path=None):
with open(config_path, 'w') as f: with open(config_path, 'w') as f:
f.write(config) f.write(config)
sys.stdout.write("Created a new configuration file {0}\n".format(config_path)) sys.stdout.write("[+] Created a new configuration file {0}\n".format(config_path))
@manager.command
def lock(path=None):
"""
Encrypts a given path. This directory can be used to store secrets needed for normal
Lemur operation. This is especially useful for storing secrets needed for communication
with third parties (e.g. external certificate authorities).
Lemur does not assume anything about the contents of the directory and will attempt to
encrypt all files contained within. Currently this has only been tested against plain
text files.
Path defaults ~/.lemur/keys
:param: path
"""
if not path:
path = os.path.expanduser('~/.lemur/keys')
dest_dir = os.path.join(path, "encrypted")
sys.stdout.write("[!] Generating a new key...\n")
key = Fernet.generate_key()
if not os.path.exists(dest_dir):
sys.stdout.write("[+] Creating encryption directory: {0}\n".format(dest_dir))
os.makedirs(dest_dir)
for root, dirs, files in os.walk(os.path.join(path, 'decrypted')):
for f in files:
source = os.path.join(root, f)
dest = os.path.join(dest_dir, f + ".enc")
with open(source, 'rb') as in_file, open(dest, 'wb') as out_file:
f = Fernet(key)
data = f.encrypt(in_file.read())
out_file.write(data)
sys.stdout.write("[+] Writing file: {0} Source: {1}\n".format(dest, source))
sys.stdout.write("[+] Keys have been encrypted with key {0}\n".format(key))
@manager.command
def unlock(path=None):
"""
Decrypts all of the files in a given directory with provided password.
This is most commonly used during the startup sequence of Lemur
allowing it to go from source code to something that can communicate
with external services.
Path defaults ~/.lemur/keys
:param: path
"""
key = prompt_pass("[!] Please enter the encryption password")
if not path:
path = os.path.expanduser('~/.lemur/keys')
dest_dir = os.path.join(path, "decrypted")
source_dir = os.path.join(path, "encrypted")
if not os.path.exists(dest_dir):
sys.stdout.write("[+] Creating decryption directory: {0}\n".format(dest_dir))
os.makedirs(dest_dir)
for root, dirs, files in os.walk(source_dir):
for f in files:
source = os.path.join(source_dir, f)
dest = os.path.join(dest_dir, ".".join(f.split(".")[:-1]))
with open(source, 'rb') as in_file, open(dest, 'wb') as out_file:
f = Fernet(key)
data = f.decrypt(in_file.read())
out_file.write(data)
sys.stdout.write("[+] Writing file: {0} Source: {1}\n".format(dest, source))
sys.stdout.write("[+] Keys have been unencrypted!\n")
def main(): def main():

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,32 +1,21 @@
""" """
.. 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
def create_authority(self): def create_authority(self):
raise NotImplementedError raise NotImplemented
def get_authorities(self):
raise NotImplementedError
def get_csr_config(self):
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.
@ -317,9 +306,6 @@ class CloudCA(Issuer):
:param data: :param data:
:return: :return:
""" """
if self.dry_run:
endpoint += '?dry_run=1'
data = dumps(dict(data.items() + get_auth_data(data['caName']).items())) 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 # we set a low timeout, if cloudca is down it shouldn't bring down
@ -334,13 +320,6 @@ class CloudCA(Issuer):
:param endpoint: :param endpoint:
:return: :return:
""" """
if self.dry_run:
endpoint += '?dry_run=1'
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

@ -0,0 +1,58 @@
VERISIGN_INTERMEDIATE = """-----BEGIN CERTIFICATE-----
MIIFFTCCA/2gAwIBAgIQKC4nkXkzkuQo8iGnTsk3rjANBgkqhkiG9w0BAQsFADCB
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMTk5OSBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IC0gRzMwHhcNMTMxMDMxMDAwMDAwWhcNMjMxMDMwMjM1OTU5WjB+MQsw
CQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNV
BAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxLzAtBgNVBAMTJlN5bWFudGVjIENs
YXNzIDMgU2VjdXJlIFNlcnZlciBDQSAtIEc0MIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAstgFyhx0LbUXVjnFSlIJluhL2AzxaJ+aQihiw6UwU35VEYJb
A3oNL+F5BMm0lncZgQGUWfm893qZJ4Itt4PdWid/sgN6nFMl6UgfRk/InSn4vnlW
9vf92Tpo2otLgjNBEsPIPMzWlnqEIRoiBAMnF4scaGGTDw5RgDMdtLXO637QYqzu
s3sBdO9pNevK1T2p7peYyo2qRA4lmUoVlqTObQJUHypqJuIGOmNIrLRM0XWTUP8T
L9ba4cYY9Z/JJV3zADreJk20KQnNDz0jbxZKgRb78oMQw7jW2FUyPfG9D72MUpVK
Fpd6UiFjdS8W+cRmvvW1Cdj/JwDNRHxvSz+w9wIDAQABo4IBQDCCATwwHQYDVR0O
BBYEFF9gz2GQVd+EQxSKYCqy9Xr0QxjvMBIGA1UdEwEB/wQIMAYBAf8CAQAwawYD
VR0gBGQwYjBgBgpghkgBhvhFAQc2MFIwJgYIKwYBBQUHAgEWGmh0dHA6Ly93d3cu
c3ltYXV0aC5jb20vY3BzMCgGCCsGAQUFBwICMBwaGmh0dHA6Ly93d3cuc3ltYXV0
aC5jb20vcnBhMC8GA1UdHwQoMCYwJKAioCCGHmh0dHA6Ly9zLnN5bWNiLmNvbS9w
Y2EzLWczLmNybDAOBgNVHQ8BAf8EBAMCAQYwKQYDVR0RBCIwIKQeMBwxGjAYBgNV
BAMTEVN5bWFudGVjUEtJLTEtNTM0MC4GCCsGAQUFBwEBBCIwIDAeBggrBgEFBQcw
AYYSaHR0cDovL3Muc3ltY2QuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBbF1K+1lZ7
9Pc0CUuWysf2IdBpgO/nmhnoJOJ/2S9h3RPrWmXk4WqQy04q6YoW51KN9kMbRwUN
gKOomv4p07wdKNWlStRxPA91xQtzPwBIZXkNq2oeJQzAAt5mrL1LBmuaV4oqgX5n
m7pSYHPEFfe7wVDJCKW6V0o6GxBzHOF7tpQDS65RsIJAOloknO4NWF2uuil6yjOe
soHCL47BJ89A8AShP/U3wsr8rFNtqVNpT+F2ZAwlgak3A/I5czTSwXx4GByoaxbn
5+CdKa/Y5Gk5eZVpuXtcXQGc1PfzSEUTZJXXCm5y2kMiJG8+WnDcwJLgLeVX+OQr
J+71/xuzAYN6
-----END CERTIFICATE-----
"""
VERISIGN_ROOT = """-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
-----END CERTIFICATE-----
"""

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
@ -54,25 +53,11 @@ VERISIGN_ERRORS = {
"0x6013": "only supports DSA keys with (2048, 256) as the bit lengths of the prime parameter pair (p, q), other DSA key sizes will get this error", "0x6013": "only supports DSA keys with (2048, 256) as the bit lengths of the prime parameter pair (p, q), other DSA key sizes will get this error",
"0x600d": "RSA key size < 2A048", "0x600d": "RSA key size < 2A048",
"0x4828": "Verisign certificates can be at most two years in length", "0x4828": "Verisign certificates can be at most two years in length",
"0x3043": "Certificates must have a validity of at least 1 day" "0x3043": "Certificates must have a validity of at least 1 day",
"0x950b": "CSR: Invalid State",
} }
class Verisign(Issuer):
title = 'VeriSign'
slug = 'verisign'
description = 'Enables the creation of certificates by the VICE2.0 verisign API.'
version = verisign.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur'
def __init__(self, *args, **kwargs):
self.session = requests.Session()
self.session.cert = current_app.config.get('VERISIGN_PEM_PATH')
super(Verisign, self).__init__(*args, **kwargs)
@staticmethod
def handle_response(content): def handle_response(content):
""" """
Helper function that helps with parsing responses from the Verisign API. Helper function that helps with parsing responses from the Verisign API.
@ -89,6 +74,21 @@ class Verisign(Issuer):
raise Exception(VERISIGN_ERRORS[status_code]) raise Exception(VERISIGN_ERRORS[status_code])
return d return d
class VerisignPlugin(IssuerPlugin):
title = 'VeriSign'
slug = 'verisign'
description = 'Enables the creation of certificates by the VICE2.0 verisign API.'
version = verisign.VERSION
author = 'Kevin Glisson'
author_url = 'https://github.com/netflix/lemur'
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)
def create_certificate(self, csr, issuer_options): def create_certificate(self, csr, issuer_options):
""" """
Creates a Verisign certificate. Creates a Verisign certificate.
@ -126,42 +126,8 @@ class Verisign(Issuer):
current_app.logger.info("Requesting a new verisign certificate: {0}".format(data)) current_app.logger.info("Requesting a new verisign certificate: {0}".format(data))
response = self.session.post(url, data=data) response = self.session.post(url, data=data)
cert = self.handle_response(response.content)['Response']['Certificate'] cert = 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 +139,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):
""" """
@ -184,11 +150,5 @@ class Verisign(Issuer):
""" """
url = current_app.config.get("VERISIGN_URL") + '/getTokens' url = current_app.config.get("VERISIGN_URL") + '/getTokens'
response = self.session.post(url, headers={'content-type': 'application/x-www-form-urlencoded'}) response = self.session.post(url, headers={'content-type': 'application/x-www-form-urlencoded'})
return self.handle_response(response.content)['Response']['Order'] return handle_response(response.content)['Response']['Order']
def get_authorities(self):
pass
def init():
return Verisign()

0
lemur/pytest.py Normal file
View File

View File

@ -29,9 +29,9 @@
</td> </td>
<td data-title="'Roles'"> <!--filter="{ 'select': 'role' }" filter-data="roleService.getRoleDropDown()">--> <td data-title="'Roles'"> <!--filter="{ 'select': 'role' }" filter-data="roleService.getRoleDropDown()">-->
<div class="btn-group"> <div class="btn-group">
<button ng-repeat="role in authority.roles" class="btn btn-sm btn-danger"> <a href="#/roles/{{ role.id }}/edit" ng-repeat="role in authority.roles" class="btn btn-sm btn-danger">
{{ role.name }} {{ role.name }}
</button> </a>
</div> </div>
</td> </td>
<td data-title="''"> <td data-title="''">

View File

@ -16,7 +16,7 @@
State State
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input name="state" ng-model="certificate.state" placeholder="State" class="form-control" ng-init="certificate.state = 'CA'" required/> <input name="state" ng-model="certificate.state" placeholder="State" class="form-control" ng-init="certificate.state = 'California'" required/>
<p ng-show="dnForm.state.$invalid && !dnForm.state.$pristine" class="help-block">You must enter a state</p> <p ng-show="dnForm.state.$invalid && !dnForm.state.$pristine" class="help-block">You must enter a state</p>
</div> </div>
</div> </div>

View File

@ -20,40 +20,20 @@
<p ng-show="trackingForm.description.$invalid && !trackingForm.description.$pristine" class="help-block">You must give a short description about this authority will be used for, this description should only include alphanumeric characters</p> <p ng-show="trackingForm.description.$invalid && !trackingForm.description.$pristine" class="help-block">You must give a short description about this authority will be used for, this description should only include alphanumeric characters</p>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group"
ng-class="{'has-error': trackingForm.selectedAuthority.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.selectedAuthority.$dirty}">
<label class="control-label col-sm-2"> <label class="control-label col-sm-2">
Certificate Authority Certificate Authority
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="input-group col-sm-12"> <div class="input-group col-sm-12">
<input tooltip="If you are unsure which authority you need; you most likely want to use 'verisign'" type="text" ng-model="certificate.selectedAuthority" placeholder="Authority Name" typeahead-on-select="certificate.attachAuthority($item)" <input name="selectedAuthority" tooltip="If you are unsure which authority you need; you most likely want to use 'verisign'" type="text" ng-model="certificate.selectedAuthority" placeholder="Authority Name" typeahead-on-select="certificate.attachAuthority($item)"
typeahead="authority.name for authority in authorityService.findAuthorityByName($viewValue)" typeahead-loading="loadingAuthorities" typeahead="authority.name for authority in authorityService.findAuthorityByName($viewValue)" typeahead-loading="loadingAuthorities"
class="form-control" typeahead-wait-ms="100" typeahead-template-url="angular/authorities/authority/select.tpl.html"> class="form-control" typeahead-wait-ms="100" typeahead-template-url="angular/authorities/authority/select.tpl.html" required>
</div> </div>
</div> </div>
</div> </div>
<div ng-show="certificate.authority.name == 'verisign'"> <div ng-show="certificate.authority" class="form-group">
<div class="form-group" ng-class="{'has-error': !certificate.extensions.subAltNames.names.length&&!certificate.extensions.subAltNames.names , 'has-success': certificate.extensions.subAltNames.names||certificate.subAltValue}">
<label class="control-label col-sm-2">Domain(s)</label>
<div class="col-sm-10">
<div class="input-group input-group-option">
<input tooltip="If you request multiple domains a SAN certificate will be created" type="text" name="domain" ng-model="certificate.subAltValue" placeholder="Domain..." class="form-control" required/>
<span class="input-group-btn">
<button class="btn btn-default" ng-click="certificate.attachSubAltName()">Add</button>
</span>
</div>
<table ng-show="certificate.extensions.subAltNames.names" class="table">
<tr ng-repeat="domain in certificate.extensions.subAltNames.names track by $index">
<td>{{ domain.value }}</td>
<td>
<button type="button" ng-click="certificate.removeSubAltName($index)" class="btn btn-danger btn-sm pull-right">Remove</button>
</td>
</tr>
</table>
</div>
</div>
</div>
<div ng-show="certificate.authority && certificate.authority.name != 'verisign'" class="form-group">
<label class="control-label col-sm-2"> <label class="control-label col-sm-2">
Certificate Template Certificate Template
</label> </label>
@ -61,19 +41,19 @@
<select class="form-control" ng-change="certificate.useTemplate()" name="certificateTemplate" ng-model="certificate.template" ng-options="template.name for template in templates"></select> <select class="form-control" ng-change="certificate.useTemplate()" name="certificateTemplate" ng-model="certificate.template" ng-options="template.name for template in templates"></select>
</div> </div>
</div> </div>
<div ng-show="certificate.authority && certificate.authority.name != 'verisign'" class="form-group" <div class="form-group"
ng-class="{'has-error': trackingForm.commonName.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.commonName.$dirty}"> ng-class="{'has-error': trackingForm.commonName.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.commonName.$dirty}">
<label class="control-label col-sm-2"> <label class="control-label col-sm-2">
Common Name Common Name
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input name="commonName" ng-model="certificate.commonName" placeholder="Common Name" class="form-control" required/> <input name="commonName" tooltip="If you need a certificate with multiple domains enter your primary domain here and the rest under 'Subject Alternate Names' in the options panel" ng-model="certificate.commonName" placeholder="Common Name" class="form-control" required/>
<p ng-show="trackingForm.commonName.$invalid && !trackingForm.commonName.$pristine" class="help-block">You must enter a common name</p> <p ng-show="trackingForm.commonName.$invalid && !trackingForm.commonName.$pristine" class="help-block">You must enter a common name</p>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="control-label col-sm-2"> <label class="control-label col-sm-2" tooltip="If no date is selected Lemur attempts to issue a 2 year certificate">
Validity Range Validity Range <span class="glyphicon glyphicon-question-sign"></span>
</label> </label>
<div class="col-sm-4"> <div class="col-sm-4">
<div> <div>

View File

@ -77,8 +77,8 @@
<li class="list-group-item"> <li class="list-group-item">
<strong>San</strong> <strong>San</strong>
<span class="pull-right"> <span class="pull-right">
<i class="glyphicon glyphicon-ok" ng-show="certificate.san == 'true'"></i> <i class="glyphicon glyphicon-ok" ng-show="certificate.san"></i>
<i class="glyphicon glyphicon-remove" ng-show="certificate.san == 'false'"></i> <i class="glyphicon glyphicon-remove" ng-show="!certificate.san"></i>
</span> </span>
</li> </li>
<li class="list-group-item"> <li class="list-group-item">

View File

@ -45,7 +45,6 @@ def app():
ctx.pop() ctx.pop()
@pytest.yield_fixture(scope="session") @pytest.yield_fixture(scope="session")
def db(app, request): def db(app, request):
_db.drop_all() _db.drop_all()
@ -72,7 +71,6 @@ def session(db, request):
@pytest.yield_fixture(scope="function") @pytest.yield_fixture(scope="function")
def client(app, session): def client(app, session, client):
with app.test_client() as client:
yield client yield client

View File

@ -22,11 +22,11 @@ def test_account_get(client):
def test_account_post(client): def test_account_post(client):
assert client.post(api.url_for(Accounts, account_id=1), {}).status_code == 405 assert client.post(api.url_for(Accounts, account_id=1), data={}).status_code == 405
def test_account_put(client): def test_account_put(client):
assert client.put(api.url_for(Accounts, account_id=1), {}).status_code == 401 assert client.put(api.url_for(Accounts, account_id=1), data={}).status_code == 401
def test_account_delete(client): def test_account_delete(client):
@ -34,7 +34,7 @@ def test_account_delete(client):
def test_account_patch(client): def test_account_patch(client):
assert client.patch(api.url_for(Accounts, account_id=1), {}).status_code == 405 assert client.patch(api.url_for(Accounts, account_id=1), data={}).status_code == 405
VALID_USER_HEADER_TOKEN = { VALID_USER_HEADER_TOKEN = {
@ -45,7 +45,7 @@ def test_auth_account_get(client):
def test_auth_account_post_(client): def test_auth_account_post_(client):
assert client.post(api.url_for(Accounts, account_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_account_put(client): def test_auth_account_put(client):
@ -57,7 +57,7 @@ def test_auth_account_delete(client):
def test_auth_account_patch(client): def test_auth_account_patch(client):
assert client.patch(api.url_for(Accounts, account_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
VALID_ADMIN_HEADER_TOKEN = { VALID_ADMIN_HEADER_TOKEN = {
@ -68,7 +68,7 @@ def test_admin_account_get(client):
def test_admin_account_post(client): def test_admin_account_post(client):
assert client.post(api.url_for(Accounts, account_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_account_put(client): def test_admin_account_put(client):
@ -80,7 +80,7 @@ def test_admin_account_delete(client):
def test_admin_account_patch(client): def test_admin_account_patch(client):
assert client.patch(api.url_for(Accounts, account_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_accounts_get(client): def test_accounts_get(client):
@ -88,11 +88,11 @@ def test_accounts_get(client):
def test_accounts_post(client): def test_accounts_post(client):
assert client.post(api.url_for(AccountsList), {}).status_code == 401 assert client.post(api.url_for(AccountsList), data={}).status_code == 401
def test_accounts_put(client): def test_accounts_put(client):
assert client.put(api.url_for(AccountsList), {}).status_code == 405 assert client.put(api.url_for(AccountsList), data={}).status_code == 405
def test_accounts_delete(client): def test_accounts_delete(client):
@ -100,7 +100,7 @@ def test_accounts_delete(client):
def test_accounts_patch(client): def test_accounts_patch(client):
assert client.patch(api.url_for(AccountsList), {}).status_code == 405 assert client.patch(api.url_for(AccountsList), data={}).status_code == 405
def test_auth_accounts_get(client): def test_auth_accounts_get(client):
@ -108,7 +108,7 @@ def test_auth_accounts_get(client):
def test_auth_accounts_post(client): def test_auth_accounts_post(client):
assert client.post(api.url_for(AccountsList), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 assert client.post(api.url_for(AccountsList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403
def test_admin_accounts_get(client): def test_admin_accounts_get(client):

View File

@ -16,11 +16,11 @@ def test_authority_get(client):
def test_authority_post(client): def test_authority_post(client):
assert client.post(api.url_for(Authorities, authority_id=1), {}).status_code == 405 assert client.post(api.url_for(Authorities, authority_id=1), data={}).status_code == 405
def test_authority_put(client): def test_authority_put(client):
assert client.put(api.url_for(Authorities, authority_id=1), {}).status_code == 401 assert client.put(api.url_for(Authorities, authority_id=1), data={}).status_code == 401
def test_authority_delete(client): def test_authority_delete(client):
@ -28,7 +28,7 @@ def test_authority_delete(client):
def test_authority_patch(client): def test_authority_patch(client):
assert client.patch(api.url_for(Authorities, authority_id=1), {}).status_code == 405 assert client.patch(api.url_for(Authorities, authority_id=1), data={}).status_code == 405
def test_authorities_get(client): def test_authorities_get(client):
@ -36,11 +36,11 @@ def test_authorities_get(client):
def test_authorities_post(client): def test_authorities_post(client):
assert client.post(api.url_for(AuthoritiesList), {}).status_code == 401 assert client.post(api.url_for(AuthoritiesList), data={}).status_code == 401
def test_authorities_put(client): def test_authorities_put(client):
assert client.put(api.url_for(AuthoritiesList), {}).status_code == 405 assert client.put(api.url_for(AuthoritiesList), data={}).status_code == 405
def test_authorities_delete(client): def test_authorities_delete(client):
@ -48,7 +48,7 @@ def test_authorities_delete(client):
def test_authorities_patch(client): def test_authorities_patch(client):
assert client.patch(api.url_for(AuthoritiesList), {}).status_code == 405 assert client.patch(api.url_for(AuthoritiesList), data={}).status_code == 405
def test_certificate_authorities_get(client): def test_certificate_authorities_get(client):
@ -56,11 +56,11 @@ def test_certificate_authorities_get(client):
def test_certificate_authorities_post(client): def test_certificate_authorities_post(client):
assert client.post(api.url_for(AuthoritiesList), {}).status_code == 401 assert client.post(api.url_for(AuthoritiesList), data={}).status_code == 401
def test_certificate_authorities_put(client): def test_certificate_authorities_put(client):
assert client.put(api.url_for(AuthoritiesList), {}).status_code == 405 assert client.put(api.url_for(AuthoritiesList), data={}).status_code == 405
def test_certificate_authorities_delete(client): def test_certificate_authorities_delete(client):
@ -68,7 +68,7 @@ def test_certificate_authorities_delete(client):
def test_certificate_authorities_patch(client): def test_certificate_authorities_patch(client):
assert client.patch(api.url_for(AuthoritiesList), {}).status_code == 405 assert client.patch(api.url_for(AuthoritiesList), data={}).status_code == 405
VALID_USER_HEADER_TOKEN = { VALID_USER_HEADER_TOKEN = {
@ -80,7 +80,7 @@ def test_auth_authority_get(client):
def test_auth_authority_post_(client): def test_auth_authority_post_(client):
assert client.post(api.url_for(Authorities, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(Authorities, authority_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_authority_put(client): def test_auth_authority_put(client):
@ -92,7 +92,7 @@ def test_auth_authority_delete(client):
def test_auth_authority_patch(client): def test_auth_authority_patch(client):
assert client.patch(api.url_for(Authorities, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(Authorities, authority_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_authorities_get(client): def test_auth_authorities_get(client):
@ -100,7 +100,7 @@ def test_auth_authorities_get(client):
def test_auth_authorities_post(client): def test_auth_authorities_post(client):
assert client.post(api.url_for(AuthoritiesList), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 assert client.post(api.url_for(AuthoritiesList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400
def test_auth_certificates_authorities_get(client): def test_auth_certificates_authorities_get(client):
@ -116,7 +116,7 @@ def test_admin_authority_get(client):
def test_admin_authority_post(client): def test_admin_authority_post(client):
assert client.post(api.url_for(Authorities, authority_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(Authorities, authority_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_authority_put(client): def test_admin_authority_put(client):
@ -136,11 +136,11 @@ def test_admin_authorities_get(client):
def test_admin_authorities_post(client): def test_admin_authorities_post(client):
assert client.post(api.url_for(AuthoritiesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 assert client.post(api.url_for(AuthoritiesList), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400
def test_admin_authorities_put(client): def test_admin_authorities_put(client):
assert client.put(api.url_for(AuthoritiesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.put(api.url_for(AuthoritiesList), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_authorities_delete(client): def test_admin_authorities_delete(client):

View File

@ -1,18 +1,6 @@
import os
import pytest import pytest
from mock import mock_open, patch
from lemur.certificates.views import * from lemur.certificates.views import *
#def test_crud(session):
# role = create('role1')
# assert role.id > 0
#
# role = update(role.id, 'role_new', None, [])
# assert role.name == 'role_new'
# delete(role.id)
# assert get(role.id) == None
def test_valid_authority(session): def test_valid_authority(session):
assert 1 == 2 assert 1 == 2
@ -33,28 +21,26 @@ def test_private_key_str():
private_key_str('dfsdfsdf', 'test') private_key_str('dfsdfsdf', 'test')
def test_create_csr(): def test_create_basic_csr():
from lemur.tests.certs import CSR_CONFIG
from lemur.certificates.service import create_csr from lemur.certificates.service import create_csr
m = mock_open() csr_config = dict(
with patch('lemur.certificates.service.open', m, create=True): commonName=u'example.com',
path = create_csr(CSR_CONFIG) organization=u'Example, Inc.',
assert path == '' organizationalUnit=u'Operations',
country=u'US',
state=u'CA',
location=u'A place',
extensions=dict(subAltNames=[u'test.example.com', u'test2.example.com'])
)
csr, pem = create_csr(csr_config)
private_key = serialization.load_pem_private_key(pem, password=None, backend=default_backend())
csr = x509.load_pem_x509_csr(csr, default_backend())
for name in csr.subject:
assert name.value in csr_config.values()
def test_create_path(): def test_import_certificate():
assert 1 == 2
def test_load_ssl_pack():
assert 1 == 2
def test_delete_ssl_path():
assert 1 == 2
def test_import_certificate(session):
assert 1 == 2 assert 1 == 2
@ -137,20 +123,17 @@ def test_create_name():
True True
) == 'SAN-example.com-ExampleInc-20150507-20150512' ) == 'SAN-example.com-ExampleInc-20150507-20150512'
def test_is_expired():
assert 1 == 2
def test_certificate_get(client): def test_certificate_get(client):
assert client.get(api.url_for(Certificates, certificate_id=1)).status_code == 401 assert client.get(api.url_for(Certificates, certificate_id=1)).status_code == 401
def test_certificate_post(client): def test_certificate_post(client):
assert client.post(api.url_for(Certificates, certificate_id=1), {}).status_code == 405 assert client.post(api.url_for(Certificates, certificate_id=1), data={}).status_code == 405
def test_certificate_put(client): def test_certificate_put(client):
assert client.put(api.url_for(Certificates, certificate_id=1), {}).status_code == 401 assert client.put(api.url_for(Certificates, certificate_id=1), data={}).status_code == 401
def test_certificate_delete(client): def test_certificate_delete(client):
@ -158,7 +141,7 @@ def test_certificate_delete(client):
def test_certificate_patch(client): def test_certificate_patch(client):
assert client.patch(api.url_for(Certificates, certificate_id=1), {}).status_code == 405 assert client.patch(api.url_for(Certificates, certificate_id=1), data={}).status_code == 405
def test_certificates_get(client): def test_certificates_get(client):
@ -166,11 +149,11 @@ def test_certificates_get(client):
def test_certificates_post(client): def test_certificates_post(client):
assert client.post(api.url_for(CertificatesList), {}).status_code == 401 assert client.post(api.url_for(CertificatesList), data={}).status_code == 401
def test_certificates_put(client): def test_certificates_put(client):
assert client.put(api.url_for(CertificatesList), {}).status_code == 405 assert client.put(api.url_for(CertificatesList), data={}).status_code == 405
def test_certificates_delete(client): def test_certificates_delete(client):
@ -178,7 +161,7 @@ def test_certificates_delete(client):
def test_certificates_patch(client): def test_certificates_patch(client):
assert client.patch(api.url_for(CertificatesList), {}).status_code == 405 assert client.patch(api.url_for(CertificatesList), data={}).status_code == 405
def test_certificate_credentials_get(client): def test_certificate_credentials_get(client):
@ -186,11 +169,11 @@ def test_certificate_credentials_get(client):
def test_certificate_credentials_post(client): def test_certificate_credentials_post(client):
assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), {}).status_code == 405 assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), data={}).status_code == 405
def test_certificate_credentials_put(client): def test_certificate_credentials_put(client):
assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), {}).status_code == 405 assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), data={}).status_code == 405
def test_certificate_credentials_delete(client): def test_certificate_credentials_delete(client):
@ -198,7 +181,7 @@ def test_certificate_credentials_delete(client):
def test_certificate_credentials_patch(client): def test_certificate_credentials_patch(client):
assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), {}).status_code == 405 assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), data={}).status_code == 405
def test_certificates_upload_get(client): def test_certificates_upload_get(client):
@ -206,11 +189,11 @@ def test_certificates_upload_get(client):
def test_certificates_upload_post(client): def test_certificates_upload_post(client):
assert client.post(api.url_for(CertificatesUpload), {}).status_code == 401 assert client.post(api.url_for(CertificatesUpload), data={}).status_code == 401
def test_certificates_upload_put(client): def test_certificates_upload_put(client):
assert client.put(api.url_for(CertificatesUpload), {}).status_code == 405 assert client.put(api.url_for(CertificatesUpload), data={}).status_code == 405
def test_certificates_upload_delete(client): def test_certificates_upload_delete(client):
@ -218,7 +201,7 @@ def test_certificates_upload_delete(client):
def test_certificates_upload_patch(client): def test_certificates_upload_patch(client):
assert client.patch(api.url_for(CertificatesUpload), {}).status_code == 405 assert client.patch(api.url_for(CertificatesUpload), data={}).status_code == 405
VALID_USER_HEADER_TOKEN = { VALID_USER_HEADER_TOKEN = {
@ -230,7 +213,7 @@ def test_auth_certificate_get(client):
def test_auth_certificate_post_(client): def test_auth_certificate_post_(client):
assert client.post(api.url_for(Certificates, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_certificate_put(client): def test_auth_certificate_put(client):
@ -242,7 +225,7 @@ def test_auth_certificate_delete(client):
def test_auth_certificate_patch(client): def test_auth_certificate_patch(client):
assert client.patch(api.url_for(Certificates, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_certificates_get(client): def test_auth_certificates_get(client):
@ -250,7 +233,7 @@ def test_auth_certificates_get(client):
def test_auth_certificates_post(client): def test_auth_certificates_post(client):
assert client.post(api.url_for(CertificatesList), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 assert client.post(api.url_for(CertificatesList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400
def test_auth_certificate_credentials_get(client): def test_auth_certificate_credentials_get(client):
@ -258,11 +241,11 @@ def test_auth_certificate_credentials_get(client):
def test_auth_certificate_credentials_post(client): def test_auth_certificate_credentials_post(client):
assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_certificate_credentials_put(client): def test_auth_certificate_credentials_put(client):
assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.put(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_certificate_credentials_delete(client): def test_auth_certificate_credentials_delete(client):
@ -270,7 +253,7 @@ def test_auth_certificate_credentials_delete(client):
def test_auth_certificate_credentials_patch(client): def test_auth_certificate_credentials_patch(client):
assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_certificates_upload_get(client): def test_auth_certificates_upload_get(client):
@ -278,11 +261,11 @@ def test_auth_certificates_upload_get(client):
def test_auth_certificates_upload_post(client): def test_auth_certificates_upload_post(client):
assert client.post(api.url_for(CertificatesUpload), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 400 assert client.post(api.url_for(CertificatesUpload), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400
def test_auth_certificates_upload_put(client): def test_auth_certificates_upload_put(client):
assert client.put(api.url_for(CertificatesUpload), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.put(api.url_for(CertificatesUpload), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_certificates_upload_delete(client): def test_auth_certificates_upload_delete(client):
@ -290,7 +273,7 @@ def test_auth_certificates_upload_delete(client):
def test_auth_certificates_upload_patch(client): def test_auth_certificates_upload_patch(client):
assert client.patch(api.url_for(CertificatesUpload), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(CertificatesUpload), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
VALID_ADMIN_HEADER_TOKEN = { VALID_ADMIN_HEADER_TOKEN = {
@ -302,7 +285,7 @@ def test_admin_certificate_get(client):
def test_admin_certificate_post(client): def test_admin_certificate_post(client):
assert client.post(api.url_for(Certificates, certificate_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_certificate_put(client): def test_admin_certificate_put(client):
@ -314,7 +297,7 @@ def test_admin_certificate_delete(client):
def test_admin_certificate_patch(client): def test_admin_certificate_patch(client):
assert client.patch(api.url_for(Certificates, certificate_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(Certificates, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_certificates_get(client): def test_admin_certificates_get(client):
@ -328,7 +311,7 @@ def test_admin_certificate_credentials_get(client):
def test_admin_certificate_credentials_post(client): def test_admin_certificate_credentials_post(client):
assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_certificate_credentials_put(client): def test_admin_certificate_credentials_put(client):
@ -340,5 +323,5 @@ def test_admin_certificate_credentials_delete(client):
def test_admin_certificate_credentials_patch(client): def test_admin_certificate_credentials_patch(client):
assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405

View File

@ -5,11 +5,11 @@ def test_domain_get(client):
def test_domain_post(client): def test_domain_post(client):
assert client.post(api.url_for(Domains, domain_id=1), {}).status_code == 405 assert client.post(api.url_for(Domains, domain_id=1), data={}).status_code == 405
def test_domain_put(client): def test_domain_put(client):
assert client.put(api.url_for(Domains, domain_id=1), {}).status_code == 405 assert client.put(api.url_for(Domains, domain_id=1), data={}).status_code == 405
def test_domain_delete(client): def test_domain_delete(client):
@ -17,7 +17,7 @@ def test_domain_delete(client):
def test_domain_patch(client): def test_domain_patch(client):
assert client.patch(api.url_for(Domains, domain_id=1), {}).status_code == 405 assert client.patch(api.url_for(Domains, domain_id=1), data={}).status_code == 405
VALID_USER_HEADER_TOKEN = { VALID_USER_HEADER_TOKEN = {
@ -28,7 +28,7 @@ def test_auth_domain_get(client):
def test_auth_domain_post_(client): def test_auth_domain_post_(client):
assert client.post(api.url_for(Domains, domain_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(Domains, domain_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_domain_put(client): def test_auth_domain_put(client):
@ -40,7 +40,7 @@ def test_auth_domain_delete(client):
def test_auth_domain_patch(client): def test_auth_domain_patch(client):
assert client.patch(api.url_for(Domains, domain_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(Domains, domain_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
VALID_ADMIN_HEADER_TOKEN = { VALID_ADMIN_HEADER_TOKEN = {
@ -51,7 +51,7 @@ def test_admin_domain_get(client):
def test_admin_domain_post(client): def test_admin_domain_post(client):
assert client.post(api.url_for(Domains, domain_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(Domains, domain_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_domain_put(client): def test_admin_domain_put(client):
@ -63,7 +63,7 @@ def test_admin_domain_delete(client):
def test_admin_domain_patch(client): def test_admin_domain_patch(client):
assert client.patch(api.url_for(Domains, domain_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(Domains, domain_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_domains_get(client): def test_domains_get(client):
@ -71,11 +71,11 @@ def test_domains_get(client):
def test_domains_post(client): def test_domains_post(client):
assert client.post(api.url_for(DomainsList), {}).status_code == 405 assert client.post(api.url_for(DomainsList), data={}).status_code == 405
def test_domains_put(client): def test_domains_put(client):
assert client.put(api.url_for(DomainsList), {}).status_code == 405 assert client.put(api.url_for(DomainsList), data={}).status_code == 405
def test_domains_delete(client): def test_domains_delete(client):
@ -83,7 +83,7 @@ def test_domains_delete(client):
def test_domains_patch(client): def test_domains_patch(client):
assert client.patch(api.url_for(DomainsList), {}).status_code == 405 assert client.patch(api.url_for(DomainsList), data={}).status_code == 405
def test_auth_domains_get(client): def test_auth_domains_get(client):
@ -101,11 +101,11 @@ def test_certificate_domains_get(client):
def test_certificate_domains_post(client): def test_certificate_domains_post(client):
assert client.post(api.url_for(CertificateDomains, certificate_id=1), {}).status_code == 405 assert client.post(api.url_for(CertificateDomains, certificate_id=1), data={}).status_code == 405
def test_certificate_domains_put(client): def test_certificate_domains_put(client):
assert client.put(api.url_for(CertificateDomains, certificate_id=1), {}).status_code == 405 assert client.put(api.url_for(CertificateDomains, certificate_id=1), data={}).status_code == 405
def test_certificate_domains_delete(client): def test_certificate_domains_delete(client):
@ -113,7 +113,7 @@ def test_certificate_domains_delete(client):
def test_certificate_domains_patch(client): def test_certificate_domains_patch(client):
assert client.patch(api.url_for(CertificateDomains, certificate_id=1), {}).status_code == 405 assert client.patch(api.url_for(CertificateDomains, certificate_id=1), data={}).status_code == 405
def test_auth_certificate_domains_get(client): def test_auth_certificate_domains_get(client):

View File

@ -1 +0,0 @@
KRPZPA&*!_~%dbnuzf153594

View File

@ -1,38 +0,0 @@
# 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
#req_extensions = req_ext
# distinguished_name
[ req_distinguished_name ]
countryName = "US" # C=
stateOrProvinceName = "CALIFORNIA" # ST=
localityName = "Los Gatos" # L=
organizationName = "Netflix, Inc." # O=
organizationalUnitName = "Operations" # OU=
# This is the hostname/subject name on the certificate
commonName = "dfdsflkj.net" # CN=
[ req_ext ]
# Uncomment the following line if you are requesting a SAN cert
#subjectAltName = @alt_names
[alt_names]
# Put your SANs here

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAvNudwW+UeQqkpY71MIdEg501AFlPKuOXG2xU8DZhvZS6dKv+
kDmIWdEqodDgkQiy0jyTgTwxwRqDSw96R6ZgrXefUoJJo66aCsosTBZtVaE85f1L
bj2+3U678c+rekUdkrnGcGCo6b8QtdvBpiDy2clneox8tSvmffAdcR1uCv/790/k
PzQ/djWDX9JcBRyDkcTJwYC0/ek7URvA/+MXmgUL13T+gWKqduaKuIBlFetonDjn
nO11QUBiusIuHV62wzKn8m5Nc+4XoaBR0YWMFn/g6qXDYrwfCsMpka7vSWJFv5Ff
yf+7kY3wU4xIwU2vXlIDcCsdUu6b/pYoQ0YOsQIDAQABAoIBAGbFH6iWnnXrq8MH
8zcQNOFmF+RztRgCt0TOA76f6TowB/LbcXBsTl2J7CgYMUvbLuwm2KHX7r9FPTMI
XiNFT5C16rYMfiQbLGo4sDhLb/3L+wawem6oHQfzA2VH++lSWRByFaEriF+CgIZl
6pALl/uZlLzkXCx+kjPwCSV3vV0wFkDnNs6+wPrz2IhkePsuC8J0QKQLlwsES2It
Gizzhpehdv9lc9MyZC//1QlD9gMDl5ok5Bt1Xm2c12XUEEcLlKQkJxiOrBOfXPmV
PHCdLc7gZO30hc6dyQ1SSnLpywhz/a0ir2GMvkMbS5hculpcZmwEcdZl1HYD8ObP
yOMbPE0CgYEA4LVGJKGtbM8RiBB0MstxNstMYVJ4mXB0lSQ0RazdO3S3ojn+oLpF
b2pvV6m9WnHiCGigWkzhqtGGCo6aqE0MoiR4jTN8GhiZz4ggDDaVgc4Px5reUD+r
tRsTpBHseGQ+ODGgkMI8eJYkdyqkECkYjAOrdy6uorvgxUAZecRIfJMCgYEA1yhM
7NidTNRuA+huS5GcQwQweTM6P1qF7Kfk1JYQMVu4gibLZiLHlWCyHI9lrbI7IaMm
g/4jXXoewv7IvyrrSEFulkPeVWxCe3mjfQ8JANfUj4kuR915LSn4lX2pbUgUS66K
vJSUJtnzLUmb8khLEcOmDbmTFZl8D/bTHFFZlisCgYAeelfWNhuoq3lMRDcOgKuN
bAujE6WJ4kfdxrhUTvr+ynjxxv3zXPB4CS6q7Dnjn5ix3UcKmGzvV1Xf7rGpbDHv
eBTlyfrmKzoJfQQjw++JWKKpRycqKUin2tFSKqAxQB90Tb7ig4XiMTMm+qCgFILg
0sqZ8rn7FpKJDoWmD2ppgwKBgG2Dl9QeVcKbhfv7PNi+HvmFkl6+knFY1D4nHzSN
xWQ6OWoV8QXlwgzokQA0hR6qT6rJbntUyg90b1/1a5zSbbvzgiR+GxcD6bsLqQmo
s354XTtKKgJuWpWAfYUp1ylGvP3gs8FVJyu3WC2+/9+MqJk8KrNlt9YQr7M4gTAy
wBTNAoGAGU7Po4uI3xDKGLLK/ot3D3P8U9ByfeLlrUZtTz1PASsMOr92bkXmUPlE
DYUd5uFfwwlvbMNT1Ooeyrzg3bARd9B6ATyMkOaJeGoQwFAI468iucnm9rNXB+/t
U2rbIi1pXSm8zSNEY85tf6C8DU/5YbcAPf47a2UYhwCpYAJfMk0=
-----END RSA PRIVATE KEY-----

View File

@ -1,17 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICvzCCAacCAQAwejELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNBTElGT1JOSUEx
EjAQBgNVBAcTCUxvcyBHYXRvczEWMBQGA1UEChMNTmV0ZmxpeCwgSW5jLjETMBEG
A1UECxMKT3BlcmF0aW9uczEVMBMGA1UEAxMMZGZkc2Zsa2oubmV0MIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvNudwW+UeQqkpY71MIdEg501AFlPKuOX
G2xU8DZhvZS6dKv+kDmIWdEqodDgkQiy0jyTgTwxwRqDSw96R6ZgrXefUoJJo66a
CsosTBZtVaE85f1Lbj2+3U678c+rekUdkrnGcGCo6b8QtdvBpiDy2clneox8tSvm
ffAdcR1uCv/790/kPzQ/djWDX9JcBRyDkcTJwYC0/ek7URvA/+MXmgUL13T+gWKq
duaKuIBlFetonDjnnO11QUBiusIuHV62wzKn8m5Nc+4XoaBR0YWMFn/g6qXDYrwf
CsMpka7vSWJFv5Ffyf+7kY3wU4xIwU2vXlIDcCsdUu6b/pYoQ0YOsQIDAQABoAAw
DQYJKoZIhvcNAQEFBQADggEBAE8b0+IYGiR64Me/L0/njYvSR5WR4EnjW99Sc8X5
k93zpk4hExrZhrlkDBA/jUHhBZcPNV9w/YkhSu5ubPjRp9gRM2d4B9gGJFAs+bwe
LS9hCOxWIMKgvaBMEDQFcwqAv6kEJzmrIa7LtWS39wNfdko2hANtm7z9qskc8bPr
265+Z48DwSNCF4RPhVp9eDifjHrj0I//GMXYa92uvgj1BlPo/SGMS+XFQF779p2b
622HmUCop3pYeIyYd6rirvl9+KwqvIhm2MqHk62eHOK7Bn/FPev8OUDeV6pIvvSV
UxsEHjjLm0V/lOD65lROc7dTq4jO5PkpoKnFQDgV5v0Bf/k=
-----END CERTIFICATE REQUEST-----

View File

@ -1,21 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDcDCCAlgCCQC8msHu/aa61zANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJV
UzETMBEGA1UECBMKQ0FMSUZPUk5JQTESMBAGA1UEBxMJTG9zIEdhdG9zMRYwFAYD
VQQKEw1OZXRmbGl4LCBJbmMuMRMwEQYDVQQLEwpPcGVyYXRpb25zMRUwEwYDVQQD
EwxkZmRzZmxrai5uZXQwHhcNMTQwNTI1MTczMDMzWhcNMTUwNTI1MTczMDMzWjB6
MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ0FMSUZPUk5JQTESMBAGA1UEBxMJTG9z
IEdhdG9zMRYwFAYDVQQKEw1OZXRmbGl4LCBJbmMuMRMwEQYDVQQLEwpPcGVyYXRp
b25zMRUwEwYDVQQDEwxkZmRzZmxrai5uZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQC8253Bb5R5CqSljvUwh0SDnTUAWU8q45cbbFTwNmG9lLp0q/6Q
OYhZ0Sqh0OCRCLLSPJOBPDHBGoNLD3pHpmCtd59SgkmjrpoKyixMFm1VoTzl/Utu
Pb7dTrvxz6t6RR2SucZwYKjpvxC128GmIPLZyWd6jHy1K+Z98B1xHW4K//v3T+Q/
ND92NYNf0lwFHIORxMnBgLT96TtRG8D/4xeaBQvXdP6BYqp25oq4gGUV62icOOec
7XVBQGK6wi4dXrbDMqfybk1z7hehoFHRhYwWf+DqpcNivB8KwymRru9JYkW/kV/J
/7uRjfBTjEjBTa9eUgNwKx1S7pv+lihDRg6xAgMBAAEwDQYJKoZIhvcNAQEFBQAD
ggEBAJHwa4l2iSiFBb6wVFBJEWEt31qp+njiVCoTg2OJzCT60Xb26hkrsiTldIIh
eB9+y+fwdfwopzWhkNbIOlCfudx/uxtpor8/3BRbjSlNwDUg2L8pfAircJMFLQUM
O6nqPOBWCe8hXwe9FQM/oFOavf/AAw/FED+892xlytjirK9u3B28O20W11+fY7hp
8LQVBrMoVxFeLWmmwETAltJ7HEYutplRzYTM0vLBARl4Vd5kLJlY3j2Dp1ZpRGcg
CrQp26UD/oaAPGtiZQSC4LJ+4JfOuuqbm3CI24QMCh9rxv3ZoOQnFuC+7cZgqrat
V4bxCrVvWhrrDSgy9+A80NVzQ3k=
-----END CERTIFICATE-----

View File

@ -18,11 +18,11 @@ def test_role_get(client):
def test_role_post(client): def test_role_post(client):
assert client.post(api.url_for(Roles, role_id=1), {}).status_code == 405 assert client.post(api.url_for(Roles, role_id=1), data={}).status_code == 405
def test_role_put(client): def test_role_put(client):
assert client.put(api.url_for(Roles, role_id=1), {}).status_code == 401 assert client.put(api.url_for(Roles, role_id=1), data={}).status_code == 401
def test_role_delete(client): def test_role_delete(client):
@ -30,7 +30,7 @@ def test_role_delete(client):
def test_role_patch(client): def test_role_patch(client):
assert client.patch(api.url_for(Roles, role_id=1), {}).status_code == 405 assert client.patch(api.url_for(Roles, role_id=1), data={}).status_code == 405
def test_roles_get(client): def test_roles_get(client):
@ -38,11 +38,11 @@ def test_roles_get(client):
def test_roles_post(client): def test_roles_post(client):
assert client.post(api.url_for(RolesList), {}).status_code == 401 assert client.post(api.url_for(RolesList), data={}).status_code == 401
def test_roles_put(client): def test_roles_put(client):
assert client.put(api.url_for(RolesList), {}).status_code == 405 assert client.put(api.url_for(RolesList), data={}).status_code == 405
def test_roles_delete(client): def test_roles_delete(client):
@ -50,7 +50,7 @@ def test_roles_delete(client):
def test_roles_patch(client): def test_roles_patch(client):
assert client.patch(api.url_for(RolesList), {}).status_code == 405 assert client.patch(api.url_for(RolesList), data={}).status_code == 405
def test_role_credentials_get(client): def test_role_credentials_get(client):
@ -58,11 +58,11 @@ def test_role_credentials_get(client):
def test_role_credentials_post(client): def test_role_credentials_post(client):
assert client.post(api.url_for(RoleViewCredentials, role_id=1), {}).status_code == 405 assert client.post(api.url_for(RoleViewCredentials, role_id=1), data={}).status_code == 405
def test_role_credentials_put(client): def test_role_credentials_put(client):
assert client.put(api.url_for(RoleViewCredentials, role_id=1), {}).status_code == 405 assert client.put(api.url_for(RoleViewCredentials, role_id=1), data={}).status_code == 405
def test_role_credentials_delete(client): def test_role_credentials_delete(client):
@ -70,7 +70,7 @@ def test_role_credentials_delete(client):
def test_role_credentials_patch(client): def test_role_credentials_patch(client):
assert client.patch(api.url_for(RoleViewCredentials, role_id=1), {}).status_code == 405 assert client.patch(api.url_for(RoleViewCredentials, role_id=1), data={}).status_code == 405
def test_user_roles_get(client): def test_user_roles_get(client):
@ -78,11 +78,11 @@ def test_user_roles_get(client):
def test_user_roles_post(client): def test_user_roles_post(client):
assert client.post(api.url_for(UserRolesList, user_id=1), {}).status_code == 405 assert client.post(api.url_for(UserRolesList, user_id=1), data={}).status_code == 405
def test_user_roles_put(client): def test_user_roles_put(client):
assert client.put(api.url_for(UserRolesList, user_id=1), {}).status_code == 405 assert client.put(api.url_for(UserRolesList, user_id=1), data={}).status_code == 405
def test_user_roles_delete(client): def test_user_roles_delete(client):
@ -90,7 +90,7 @@ def test_user_roles_delete(client):
def test_user_roles_patch(client): def test_user_roles_patch(client):
assert client.patch(api.url_for(UserRolesList, user_id=1), {}).status_code == 405 assert client.patch(api.url_for(UserRolesList, user_id=1), data={}).status_code == 405
def test_authority_roles_get(client): def test_authority_roles_get(client):
@ -98,11 +98,11 @@ def test_authority_roles_get(client):
def test_authority_roles_post(client): def test_authority_roles_post(client):
assert client.post(api.url_for(AuthorityRolesList, authority_id=1), {}).status_code == 405 assert client.post(api.url_for(AuthorityRolesList, authority_id=1), data={}).status_code == 405
def test_authority_roles_put(client): def test_authority_roles_put(client):
assert client.put(api.url_for(AuthorityRolesList, authority_id=1), {}).status_code == 405 assert client.put(api.url_for(AuthorityRolesList, authority_id=1), data={}).status_code == 405
def test_authority_roles_delete(client): def test_authority_roles_delete(client):
@ -110,7 +110,7 @@ def test_authority_roles_delete(client):
def test_authority_roles_patch(client): def test_authority_roles_patch(client):
assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), {}).status_code == 405 assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), data={}).status_code == 405
VALID_USER_HEADER_TOKEN = { VALID_USER_HEADER_TOKEN = {
@ -122,7 +122,7 @@ def test_auth_role_get(client):
def test_auth_role_post_(client): def test_auth_role_post_(client):
assert client.post(api.url_for(Roles, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(Roles, role_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_role_put(client): def test_auth_role_put(client):
@ -134,7 +134,7 @@ def test_auth_role_delete(client):
def test_auth_role_patch(client): def test_auth_role_patch(client):
assert client.patch(api.url_for(Roles, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(Roles, role_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_roles_get(client): def test_auth_roles_get(client):
@ -142,7 +142,7 @@ def test_auth_roles_get(client):
def test_auth_roles_post(client): def test_auth_roles_post(client):
assert client.post(api.url_for(RolesList), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 assert client.post(api.url_for(RolesList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403
def test_auth_role_credentials_get(client): def test_auth_role_credentials_get(client):
@ -150,11 +150,11 @@ def test_auth_role_credentials_get(client):
def test_auth_role_credentials_post(client): def test_auth_role_credentials_post(client):
assert client.post(api.url_for(RoleViewCredentials, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(RoleViewCredentials, role_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_role_credentials_put(client): def test_auth_role_credentials_put(client):
assert client.put(api.url_for(RoleViewCredentials, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.put(api.url_for(RoleViewCredentials, role_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_role_credentials_delete(client): def test_auth_role_credentials_delete(client):
@ -162,7 +162,7 @@ def test_auth_role_credentials_delete(client):
def test_auth_role_credentials_patch(client): def test_auth_role_credentials_patch(client):
assert client.patch(api.url_for(RoleViewCredentials, role_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(RoleViewCredentials, role_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_user_roles_get(client): def test_auth_user_roles_get(client):
@ -170,11 +170,11 @@ def test_auth_user_roles_get(client):
def test_auth_user_roles_post(client): def test_auth_user_roles_post(client):
assert client.post(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(UserRolesList, user_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_user_roles_put(client): def test_auth_user_roles_put(client):
assert client.put(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.put(api.url_for(UserRolesList, user_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_user_roles_delete(client): def test_auth_user_roles_delete(client):
@ -182,7 +182,7 @@ def test_auth_user_roles_delete(client):
def test_auth_user_roles_patch(client): def test_auth_user_roles_patch(client):
assert client.patch(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(UserRolesList, user_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_authority_roles_get(client): def test_auth_authority_roles_get(client):
@ -190,11 +190,11 @@ def test_auth_authority_roles_get(client):
def test_auth_authority_roles_post(client): def test_auth_authority_roles_post(client):
assert client.post(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(AuthorityRolesList, authority_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_authority_roles_put(client): def test_auth_authority_roles_put(client):
assert client.put(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.put(api.url_for(AuthorityRolesList, authority_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
def test_auth_authority_roles_delete(client): def test_auth_authority_roles_delete(client):
@ -202,7 +202,7 @@ def test_auth_authority_roles_delete(client):
def test_auth_authority_roles_patch(client): def test_auth_authority_roles_patch(client):
assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
VALID_ADMIN_HEADER_TOKEN = { VALID_ADMIN_HEADER_TOKEN = {
@ -214,7 +214,7 @@ def test_admin_role_get(client):
def test_admin_role_post(client): def test_admin_role_post(client):
assert client.post(api.url_for(Roles, role_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(Roles, role_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_role_put(client): def test_admin_role_put(client):
@ -226,7 +226,7 @@ def test_admin_role_delete(client):
def test_admin_role_patch(client): def test_admin_role_patch(client):
assert client.patch(api.url_for(Roles, role_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(Roles, role_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_roles_get(client): def test_admin_roles_get(client):
@ -240,11 +240,11 @@ def test_admin_role_credentials_get(client):
def test_admin_role_credentials_post(client): def test_admin_role_credentials_post(client):
assert client.post(api.url_for(RolesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 assert client.post(api.url_for(RolesList), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400
def test_admin_role_credentials_put(client): def test_admin_role_credentials_put(client):
assert client.put(api.url_for(RolesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.put(api.url_for(RolesList), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_role_credentials_delete(client): def test_admin_role_credentials_delete(client):
@ -252,7 +252,7 @@ def test_admin_role_credentials_delete(client):
def test_admin_role_credentials_patch(client): def test_admin_role_credentials_patch(client):
assert client.patch(api.url_for(RolesList), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(RolesList), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_user_roles_get(client): def test_admin_user_roles_get(client):
@ -260,11 +260,11 @@ def test_admin_user_roles_get(client):
def test_admin_user_roles_post(client): def test_admin_user_roles_post(client):
assert client.post(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(UserRolesList, user_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_user_roles_put(client): def test_admin_user_roles_put(client):
assert client.put(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.put(api.url_for(UserRolesList, user_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_user_roles_delete(client): def test_admin_user_roles_delete(client):
@ -272,7 +272,7 @@ def test_admin_user_roles_delete(client):
def test_admin_user_roles_patch(client): def test_admin_user_roles_patch(client):
assert client.patch(api.url_for(UserRolesList, user_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(UserRolesList, user_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_authority_roles_get(client): def test_admin_authority_roles_get(client):
@ -280,11 +280,11 @@ def test_admin_authority_roles_get(client):
def test_admin_authority_roles_post(client): def test_admin_authority_roles_post(client):
assert client.post(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.post(api.url_for(AuthorityRolesList, authority_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_authority_roles_put(client): def test_admin_authority_roles_put(client):
assert client.put(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.put(api.url_for(AuthorityRolesList, authority_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_authority_roles_delete(client): def test_admin_authority_roles_delete(client):
@ -292,7 +292,7 @@ def test_admin_authority_roles_delete(client):
def test_admin_authority_roles_patch(client): def test_admin_authority_roles_patch(client):
assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), {}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 assert client.patch(api.url_for(AuthorityRolesList, authority_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
def test_admin_roles_crud(client): def test_admin_roles_crud(client):

View File

@ -47,7 +47,8 @@ tests_require = [
'pyflakes', 'pyflakes',
'moto', 'moto',
'nose', 'nose',
'pytest' 'pytest',
'pytest-flask'
] ]
docs_require = [ docs_require = [
@ -104,6 +105,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',