[WIP] - 422 elb rotate (#493)

* Initial work on certificate rotation.

* Adding ability to get additional certificate info.

* - Adding endpoint rotation.
- Removes the g requirement from all services to enable easier testing.
This commit is contained in:
kevgliss 2016-11-18 11:27:46 -08:00 committed by GitHub
parent 6fd47edbe3
commit d45e7d6b85
27 changed files with 393 additions and 390 deletions

View File

@ -8,8 +8,6 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
""" """
from flask import g
from lemur import database from lemur import database
from lemur.extensions import metrics from lemur.extensions import metrics
from lemur.authorities.models import Authority from lemur.authorities.models import Authority
@ -53,13 +51,14 @@ def mint(**kwargs):
elif len(values) == 4: elif len(values) == 4:
body, private_key, chain, roles = values body, private_key, chain, roles = values
roles = create_authority_roles(roles, kwargs['owner'], kwargs['plugin']['plugin_object'].title) roles = create_authority_roles(roles, kwargs['owner'], kwargs['plugin']['plugin_object'].title, None)
return body, private_key, chain, roles return body, private_key, chain, roles
def create_authority_roles(roles, owner, plugin_title): def create_authority_roles(roles, owner, plugin_title, creator):
""" """
Creates all of the necessary authority roles. Creates all of the necessary authority roles.
:param creator:
:param roles: :param roles:
:return: :return:
""" """
@ -75,7 +74,7 @@ def create_authority_roles(roles, owner, plugin_title):
# 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) creator.roles.append(role)
role_objs.append(role) role_objs.append(role)
@ -95,10 +94,9 @@ def create(**kwargs):
""" """
Creates a new authority. Creates a new authority.
""" """
kwargs['creator'] = g.user.email
body, private_key, chain, roles = mint(**kwargs) body, private_key, chain, roles = mint(**kwargs)
g.user.roles = list(set(list(g.user.roles) + roles)) kwargs['creator'].roles = list(set(list(kwargs['creator'].roles) + roles))
kwargs['body'] = body kwargs['body'] = body
kwargs['private_key'] = private_key kwargs['private_key'] = private_key
@ -114,7 +112,7 @@ def create(**kwargs):
authority = Authority(**kwargs) authority = Authority(**kwargs)
authority = database.create(authority) authority = database.create(authority)
g.user.authorities.append(authority) kwargs['creator'].authorities.append(authority)
metrics.send('authority_created', 'counter', 1, metric_tags=dict(owner=authority.owner)) metrics.send('authority_created', 'counter', 1, metric_tags=dict(owner=authority.owner))
return authority return authority
@ -151,14 +149,14 @@ def get_by_name(authority_name):
return database.get(Authority, authority_name, field='name') return database.get(Authority, authority_name, field='name')
def get_authority_role(ca_name): def get_authority_role(ca_name, creator):
""" """
Attempts to get the authority role for a given ca uses current_user Attempts to get the authority role for a given ca uses current_user
as a basis for accomplishing that. as a basis for accomplishing that.
:param ca_name: :param ca_name:
""" """
if g.current_user.is_admin: if creator.is_admin:
return role_service.get_by_name("{0}_admin".format(ca_name)) return role_service.get_by_name("{0}_admin".format(ca_name))
else: else:
return role_service.get_by_name("{0}_operator".format(ca_name)) return role_service.get_by_name("{0}_operator".format(ca_name))
@ -181,12 +179,12 @@ def render(args):
query = database.filter(query, Authority, terms) query = database.filter(query, Authority, terms)
# we make sure that a user can only use an authority they either own are are a member of - admins can see all # we make sure that a user can only use an authority they either own are are a member of - admins can see all
if not g.current_user.is_admin: if not args['user'].is_admin:
authority_ids = [] authority_ids = []
for authority in g.current_user.authorities: for authority in args['user'].authorities:
authority_ids.append(authority.id) authority_ids.append(authority.id)
for role in g.current_user.roles: for role in args['user'].roles:
for authority in role.authorities: for authority in role.authorities:
authority_ids.append(authority.id) authority_ids.append(authority.id)
query = query.filter(Authority.id.in_(authority_ids)) query = query.filter(Authority.id.in_(authority_ids))

View File

@ -5,7 +5,7 @@
: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 Blueprint from flask import Blueprint, g
from flask.ext.restful import reqparse, Api from flask.ext.restful import reqparse, Api
from lemur.common.utils import paginated_parser from lemur.common.utils import paginated_parser
@ -107,6 +107,7 @@ class AuthoritiesList(AuthenticatedResource):
""" """
parser = paginated_parser.copy() parser = paginated_parser.copy()
args = parser.parse_args() args = parser.parse_args()
args['user'] = g.current_user
return service.render(args) return service.render(args)
@validate_schema(authority_input_schema, authority_output_schema) @validate_schema(authority_input_schema, authority_output_schema)

View File

@ -5,16 +5,16 @@
: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 datetime import arrow
import lemur.common.utils
from flask import current_app from flask import current_app
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from sqlalchemy.sql.expression import case from sqlalchemy.sql.expression import case
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import event, Integer, ForeignKey, String, DateTime, PassiveDefault, func, Column, Text, Boolean from sqlalchemy import event, Integer, ForeignKey, String, PassiveDefault, func, Column, Text, Boolean
import lemur.common.utils
from lemur.database import db from lemur.database import db
from lemur.models import certificate_associations, certificate_source_associations, \ from lemur.models import certificate_associations, certificate_source_associations, \
certificate_destination_associations, certificate_notification_associations, \ certificate_destination_associations, certificate_notification_associations, \
@ -22,6 +22,8 @@ from lemur.models import certificate_associations, certificate_source_associatio
from lemur.plugins.base import plugins from lemur.plugins.base import plugins
from lemur.utils import Vault from lemur.utils import Vault
from sqlalchemy_utils.types.arrow import ArrowType
from lemur.common import defaults from lemur.common import defaults
from lemur.domains.models import Domain from lemur.domains.models import Domain
@ -53,9 +55,9 @@ class Certificate(db.Model):
cn = Column(String(128)) cn = Column(String(128))
deleted = Column(Boolean, index=True) deleted = Column(Boolean, index=True)
not_before = Column(DateTime) not_before = Column(ArrowType)
not_after = Column(DateTime) not_after = Column(ArrowType)
date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False) date_created = Column(ArrowType, PassiveDefault(func.now()), nullable=False)
signing_algorithm = Column(String(128)) signing_algorithm = Column(String(128))
status = Column(String(128)) status = Column(String(128))
@ -120,16 +122,41 @@ class Certificate(db.Model):
def active(self): def active(self):
return self.notify return self.notify
@property
def organization(self):
cert = lemur.common.utils.parse_certificate(self.body)
return defaults.organization(cert)
@property
def organizational_unit(self):
cert = lemur.common.utils.parse_certificate(self.body)
return defaults.organizational_unit(cert)
@property
def country(self):
cert = lemur.common.utils.parse_certificate(self.body)
return defaults.country(cert)
@property
def state(self):
cert = lemur.common.utils.parse_certificate(self.body)
return defaults.state(cert)
@property
def location(self):
cert = lemur.common.utils.parse_certificate(self.body)
return defaults.location(cert)
@hybrid_property @hybrid_property
def expired(self): def expired(self):
if self.not_after <= datetime.datetime.now(): if self.not_after <= arrow.utcnow():
return True return True
@expired.expression @expired.expression
def expired(cls): def expired(cls):
return case( return case(
[ [
(cls.now_after <= datetime.datetime.now(), True) (cls.now_after <= arrow.utcnow(), True)
], ],
else_=False else_=False
) )

View File

@ -8,7 +8,7 @@
import arrow import arrow
from sqlalchemy import func, or_ from sqlalchemy import func, or_
from flask import g, current_app from flask import current_app
from lemur import database from lemur import database
from lemur.extensions import metrics from lemur.extensions import metrics
@ -201,11 +201,7 @@ def upload(**kwargs):
cert = database.create(cert) cert = database.create(cert)
try: kwargs['creator'].certificates.append(cert)
g.user.certificates.append(cert)
except AttributeError:
current_app.logger.debug("No user to associate uploaded certificate to.")
return database.update(cert) return database.update(cert)
@ -213,7 +209,6 @@ def create(**kwargs):
""" """
Creates a new certificate. Creates a new certificate.
""" """
kwargs['creator'] = g.user.email
cert_body, private_key, cert_chain = mint(**kwargs) cert_body, private_key, cert_chain = mint(**kwargs)
kwargs['body'] = cert_body kwargs['body'] = cert_body
kwargs['private_key'] = private_key kwargs['private_key'] = private_key
@ -228,7 +223,7 @@ def create(**kwargs):
cert = Certificate(**kwargs) cert = Certificate(**kwargs)
g.user.certificates.append(cert) kwargs['creator'].certificates.append(cert)
cert.authority = kwargs['authority'] cert.authority = kwargs['authority']
database.commit() database.commit()
@ -286,10 +281,10 @@ def render(args):
query = database.filter(query, Certificate, terms) query = database.filter(query, Certificate, terms)
if show: if show:
sub_query = database.session_query(Role.name).filter(Role.user_id == g.user.id).subquery() sub_query = database.session_query(Role.name).filter(Role.user_id == args['user'].id).subquery()
query = query.filter( query = query.filter(
or_( or_(
Certificate.user_id == g.user.id, Certificate.user_id == args['user'].id,
Certificate.owner.in_(sub_query) Certificate.owner.in_(sub_query)
) )
) )
@ -476,10 +471,9 @@ def calculate_reissue_range(start, end):
new_start = arrow.utcnow().date() new_start = arrow.utcnow().date()
new_end = new_start + span new_end = new_start + span
return new_start, new_end return new_start, arrow.get(new_end)
# TODO pull the OU, O, CN, etc + other extensions.
def get_certificate_primitives(certificate): def get_certificate_primitives(certificate):
""" """
Retrieve key primitive from a certificate such that the certificate Retrieve key primitive from a certificate such that the certificate
@ -491,6 +485,7 @@ def get_certificate_primitives(certificate):
start, end = calculate_reissue_range(certificate.not_before, certificate.not_after) start, end = calculate_reissue_range(certificate.not_before, certificate.not_after)
names = [{'name_type': 'DNSName', 'value': x.name} for x in certificate.domains] names = [{'name_type': 'DNSName', 'value': x.name} for x in certificate.domains]
# TODO pull additional extensions
extensions = { extensions = {
'sub_alt_names': { 'sub_alt_names': {
'names': names 'names': names
@ -506,5 +501,31 @@ def get_certificate_primitives(certificate):
destinations=certificate.destinations, destinations=certificate.destinations,
roles=certificate.roles, roles=certificate.roles,
extensions=extensions, extensions=extensions,
owner=certificate.owner owner=certificate.owner,
organization=certificate.organization,
organizational_unit=certificate.organizational_unit,
country=certificate.country,
state=certificate.state,
location=certificate.location
) )
def reissue_certificate(certificate, replace=None, user=None):
"""
Reissue certificate with the same properties of the given certificate.
:param certificate:
:return:
"""
primitives = get_certificate_primitives(certificate)
if not user:
primitives['creator'] = certificate.user
else:
primitives['creator'] = user
new_cert = create(**primitives)
if replace:
certificate.notify = False
return new_cert

View File

@ -8,7 +8,7 @@
import base64 import base64
from builtins import str from builtins import str
from flask import Blueprint, make_response, jsonify from flask import Blueprint, make_response, jsonify, g
from flask.ext.restful import reqparse, Api from flask.ext.restful import reqparse, Api
from lemur.common.schema import validate_schema from lemur.common.schema import validate_schema
@ -129,6 +129,7 @@ class CertificatesList(AuthenticatedResource):
parser.add_argument('show', type=str, location='args') parser.add_argument('show', type=str, location='args')
args = parser.parse_args() args = parser.parse_args()
args['user'] = g.user
return service.render(args) return service.render(args)
@validate_schema(certificate_input_schema, certificate_output_schema) @validate_schema(certificate_input_schema, certificate_output_schema)
@ -265,6 +266,7 @@ class CertificatesList(AuthenticatedResource):
authority_permission = AuthorityPermission(data['authority'].id, roles) authority_permission = AuthorityPermission(data['authority'].id, roles)
if authority_permission.can(): if authority_permission.can():
data['creator'] = g.user
return service.create(**data) return service.create(**data)
return dict(message="You are not authorized to use {0}".format(data['authority'].name)), 403 return dict(message="You are not authorized to use {0}".format(data['authority'].name)), 403
@ -371,6 +373,7 @@ class CertificatesUpload(AuthenticatedResource):
""" """
if data.get('destinations'): if data.get('destinations'):
if data.get('private_key'): if data.get('private_key'):
data['creator'] = g.user
return service.upload(**data) return service.upload(**data)
else: else:
raise Exception("Private key must be provided in order to upload certificate to AWS") raise Exception("Private key must be provided in order to upload certificate to AWS")
@ -740,6 +743,7 @@ class NotificationCertificatesList(AuthenticatedResource):
args = parser.parse_args() args = parser.parse_args()
args['notification_id'] = notification_id args['notification_id'] = notification_id
args['user'] = g.current_user
return service.render(args) return service.render(args)

View File

@ -58,6 +58,61 @@ def common_name(cert):
)[0].value.strip() )[0].value.strip()
def organization(cert):
"""
Attempt to get the organization name from a given certificate.
:param cert:
:return:
"""
return cert.subject.get_attributes_for_oid(
x509.OID_ORGANIZATION_NAME
)[0].value.strip()
def organizational_unit(cert):
"""
Attempt to get the organization unit from a given certificate.
:param cert:
:return:
"""
return cert.subject.get_attributes_for_oid(
x509.OID_ORGANIZATIONAL_UNIT_NAME
)[0].value.strip()
def country(cert):
"""
Attempt to get the country from a given certificate.
:param cert:
:return:
"""
return cert.subject.get_attributes_for_oid(
x509.OID_COUNTRY_NAME
)[0].value.strip()
def state(cert):
"""
Attempt to get the from a given certificate.
:param cert:
:return:
"""
return cert.subject.get_attributes_for_oid(
x509.OID_STATE_OR_PROVINCE_NAME
)[0].value.strip()
def location(cert):
"""
Attempt to get the location name from a given certificate.
:param cert:
:return:
"""
return cert.subject.get_attributes_for_oid(
x509.OID_LOCALITY_NAME
)[0].value.strip()
def domains(cert): def domains(cert):
""" """
Attempts to get an domains listed in a certificate. Attempts to get an domains listed in a certificate.

View File

@ -274,6 +274,9 @@ def sort_and_page(query, model, args):
page = args.pop('page') page = args.pop('page')
count = args.pop('count') count = args.pop('count')
if args.get('user'):
user = args.pop('user')
query = find_all(query, model, args) query = find_all(query, model, args)
if sort_by and sort_dir: if sort_by and sort_dir:

View File

@ -8,6 +8,8 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com> .. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
""" """
from flask import current_app
from lemur import database from lemur import database
from lemur.extensions import metrics from lemur.extensions import metrics
from lemur.endpoints.models import Endpoint, Policy, Cipher from lemur.endpoints.models import Endpoint, Policy, Cipher
@ -96,6 +98,17 @@ def update(endpoint_id, **kwargs):
return endpoint return endpoint
def rotate_certificate(endpoint, new_cert):
"""Rotates a certificate on a given endpoint."""
try:
endpoint.source.plugin.update_endpoint(endpoint, new_cert)
endpoint.certificate = new_cert
except Exception as e:
metrics.send('rotate_failure', 'counter', 1, tags={'endpoint': endpoint.name})
current_app.logger.exception(e)
raise e
def render(args): def render(args):
""" """
Helper that helps us render the REST Api responses. Helper that helps us render the REST Api responses.

View File

@ -5,7 +5,7 @@
: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 Blueprint from flask import Blueprint, g
from flask.ext.restful import reqparse, Api from flask.ext.restful import reqparse, Api
from lemur.common.utils import paginated_parser from lemur.common.utils import paginated_parser
@ -63,6 +63,7 @@ class EndpointsList(AuthenticatedResource):
""" """
parser = paginated_parser.copy() parser = paginated_parser.copy()
args = parser.parse_args() args = parser.parse_args()
args['user'] = g.current_user
return service.render(args) return service.render(args)

View File

@ -1,5 +1,6 @@
from __future__ import unicode_literals # at top of module from __future__ import unicode_literals # at top of module
import arrow
from datetime import datetime, timedelta from datetime import datetime, timedelta
from collections import Counter from collections import Counter
@ -28,11 +29,7 @@ from lemur.certificates import service as cert_service
from lemur.authorities import service as authority_service from lemur.authorities import service as authority_service
from lemur.notifications import service as notification_service from lemur.notifications import service as notification_service
from lemur.certificates.service import get_name_from_arn
from lemur.certificates.verify import verify_string from lemur.certificates.verify import verify_string
from lemur.plugins.lemur_aws import elb
from lemur.sources import service as source_service from lemur.sources import service as source_service
from lemur import create_app from lemur import create_app
@ -487,253 +484,122 @@ def unicode_(data):
return data return data
class RotateELBs(Command): def print_certificate_details(details):
""" """
Rotates existing certificates to a new one on an ELB Print the certificate details with formatting.
:param details:
:return:
""" """
option_list = ( sys.stdout.write("[+] Re-issuing certificate with the following details: \n")
Option('-e', '--elb-list', dest='elb_list', required=True), sys.stdout.write(
Option('-p', '--chain-path', dest='chain_path'), "[+] Common Name: {common_name}\n"
Option('-c', '--cert-name', dest='cert_name'), "[+] Subject Alternate Names: {sans}\n"
Option('-a', '--cert-prefix', dest='cert_prefix'), "[+] Authority: {authority_name}\n"
Option('-d', '--description', dest='description') "[+] Validity Start: {validity_start}\n"
"[+] Validity End: {validity_end}\n"
"[+] Organization: {organization}\n"
"[+] Organizational Unit: {organizational_unit}\n"
"[+] Country: {country}\n"
"[+] State: {state}\n"
"[+] Location: {location}\n".format(
common_name=details['common_name'],
sans=",".join(x['value'] for x in details['extensions']['sub_alt_names']['names']),
authority_name=details['authority'].name,
validity_start=details['validity_start'].isoformat(),
validity_end=details['validity_end'].isoformat(),
organization=details['organization'],
organizational_unit=details['organizational_unit'],
country=details['country'],
state=details['state'],
location=details['location']
)
) )
def run(self, elb_list, chain_path, cert_name, cert_prefix, description):
for e in open(elb_list, 'r').readlines(): class RotateCertificate(Command):
elb_name, account_id, region, from_port, to_port, protocol = e.strip().split(',')
if cert_name:
arn = "arn:aws:iam::{0}:server-certificate/{1}".format(account_id, cert_name)
else:
# if no cert name is provided we need to discover it
listeners = elb.get_listeners(account_id, region, elb_name)
# get the listener we care about
for listener in listeners:
if listener[0] == int(from_port) and listener[1] == int(to_port):
arn = listener[4]
name = get_name_from_arn(arn)
certificate = cert_service.get_by_name(name)
break
else:
sys.stdout.write("[-] Could not find ELB {0}".format(elb_name))
continue
if not certificate:
sys.stdout.write("[-] Could not find certificate {0} in Lemur".format(name))
continue
dests = []
for d in certificate.destinations:
dests.append({'id': d.id})
nots = []
for n in certificate.notifications:
nots.append({'id': n.id})
new_certificate = database.clone(certificate)
if cert_prefix:
new_certificate.name = "{0}-{1}".format(cert_prefix, new_certificate.name)
new_certificate.chain = open(chain_path, 'r').read()
new_certificate.description = "{0} - {1}".format(new_certificate.description, description)
new_certificate = database.create(new_certificate)
database.update_list(new_certificate, 'destinations', Destination, dests)
database.update_list(new_certificate, 'notifications', Notification, nots)
database.update(new_certificate)
arn = new_certificate.get_arn(account_id)
elb.update_listeners(account_id, region, elb_name, [(from_port, to_port, protocol, arn)], [from_port])
sys.stdout.write("[+] Updated {0} to use {1}\n".format(elb_name, new_certificate.name))
class ProvisionELB(Command):
""" """
Creates and provisions a certificate on an ELB based on command line arguments Rotates certificate on all endpoints managed by Lemur.
""" """
option_list = ( option_list = (
Option('-d', '--dns', dest='dns', action='append', required=True, type=unicode_), Option('-n', '--cert-name', dest='new_cert_name'),
Option('-e', '--elb', dest='elb_name', required=True, type=unicode_), Option('-o', '--old-cert-name', dest='old_cert_name', required=True),
Option('-o', '--owner', dest='owner', type=unicode_), Option('-c', '--commit', dest='commit', action='store_true', default=False)
Option('-a', '--authority', dest='authority', required=True, type=unicode_),
Option('-s', '--description', dest='description', default=u'Command line provisioned keypair', type=unicode_),
Option('-t', '--destination', dest='destinations', action='append', type=unicode_, required=True),
Option('-n', '--notification', dest='notifications', action='append', type=unicode_, default=[]),
Option('-r', '--region', dest='region', default=u'us-east-1', type=unicode_),
Option('-p', '--dport', '--port', dest='dport', default=7002),
Option('--src-port', '--source-port', '--sport', dest='sport', default=443),
Option('--dry-run', dest='dryrun', action='store_true')
) )
def configure_user(self, owner): def run(self, new_cert_name, old_cert_name, commit):
from flask import g from lemur.certificates.service import get_by_name, reissue_certificate, get_certificate_primitives
import lemur.users.service from lemur.endpoints.service import rotate_certificate
# grab the user old_cert = get_by_name(old_cert_name)
g.user = lemur.users.service.get_by_username(owner)
# get the first user by default
if not g.user:
g.user = lemur.users.service.get_all()[0]
return g.user.username if not old_cert:
sys.stdout.write("[-] No certificate found with name: {0}\n".format(old_cert_name))
def build_cert_options(self, destinations, notifications, description, owner, dns, authority):
from sqlalchemy.orm.exc import NoResultFound
from lemur.certificates.views import valid_authority
import sys
# convert argument lists to arrays, or empty sets
destinations = self.get_destinations(destinations)
if not destinations:
sys.stderr.write("Valid destinations provided\n")
sys.exit(1) sys.exit(1)
# get the primary CN if new_cert_name:
common_name = dns[0] new_cert = get_by_name(new_cert_name)
# If there are more than one fqdn, add them as alternate names if not new_cert:
extensions = {} sys.stdout.write("[-] No certificate found with name: {0}\n".format(old_cert_name))
if len(dns) > 1:
extensions['subAltNames'] = {'names': map(lambda x: {'nameType': 'DNSName', 'value': x}, dns)}
try:
authority = valid_authority({"name": authority})
except NoResultFound:
sys.stderr.write("Invalid authority specified: '{}'\naborting\n".format(authority))
sys.exit(1)
options = {
# Convert from the Destination model to the JSON input expected further in the code
'destinations': map(lambda x: {'id': x.id, 'label': x.label}, destinations),
'description': description,
'notifications': notifications,
'commonName': common_name,
'extensions': extensions,
'authority': authority,
'owner': owner,
# defaults:
'organization': current_app.config.get('LEMUR_DEFAULT_ORGANIZATION'),
'organizationalUnit': current_app.config.get('LEMUR_DEFAULT_ORGANIZATIONAL_UNIT'),
'country': current_app.config.get('LEMUR_DEFAULT_COUNTRY'),
'state': current_app.config.get('LEMUR_DEFAULT_STATE'),
'location': current_app.config.get('LEMUR_DEFAULT_LOCATION')
}
return options
def get_destinations(self, destination_names):
from lemur.destinations import service
destinations = []
for destination_name in destination_names:
destination = service.get_by_label(destination_name)
if not destination:
sys.stderr.write("Invalid destination specified: '{}'\nAborting...\n".format(destination_name))
sys.exit(1) sys.exit(1)
destinations.append(service.get_by_label(destination_name)) if commit:
sys.stdout.write("[!] Running in COMMIT mode.\n")
return destinations if not new_cert_name:
sys.stdout.write("[!] No new certificate provided. Attempting to re-issue old certificate: {0}.\n".format(old_cert_name))
def check_duplicate_listener(self, elb_name, region, account, sport, dport): details = get_certificate_primitives(old_cert)
from lemur.plugins.lemur_aws import elb print_certificate_details(details)
listeners = elb.get_listeners(account, region, elb_name) if commit:
for listener in listeners: new_cert = reissue_certificate(old_cert, replace=True)
if listener[0] == sport and listener[1] == dport: sys.stdout.write("[+] Issued new certificate named: {0}\n".format(new_cert.name))
return True
return False
def get_destination_account(self, destinations): sys.stdout.write("[+] Done! \n")
for destination in self.get_destinations(destinations):
if destination.plugin_name == 'aws-destination':
account_number = destination.plugin.get_option('accountNumber', destination.options) if len(old_cert.endpoints) > 0:
return account_number for endpoint in old_cert.endpoints:
sys.stdout.write("[+] Certificate found deployed onto {0}\n ".format(endpoint.name))
sys.stdout.write("[+] Rotating certificate from: {0} to: {1}\n ".format(old_cert_name, new_cert.name))
sys.stderr.write("No destination AWS account provided, failing\n") if commit:
sys.exit(1) rotate_certificate(endpoint, new_cert_name)
def run(self, dns, elb_name, owner, authority, description, notifications, destinations, region, dport, sport, sys.stdout.write("[+] Done! \n")
dryrun): else:
from lemur.certificates import service sys.stdout.write("[!] Certificate not found on any existing endpoints. Nothing to rotate.\n")
from lemur.plugins.lemur_aws import elb
from boto.exception import BotoServerError
# configure the owner if we can find it, or go for default, and put it in the global
owner = self.configure_user(owner)
# make a config blob from the command line arguments class ReissueCertificate(Command):
cert_options = self.build_cert_options( """
destinations=destinations, Reissues a certificate based on a given certificate.
notifications=notifications, """
description=description, option_list = (
owner=owner, Option('-o', '--old-cert-name', dest='old_cert_name', required=True),
dns=dns, Option('-c', '--commit', dest='commit', action='store_true', default=False)
authority=authority) )
aws_account = self.get_destination_account(destinations) def run(self, old_cert_name, commit):
from lemur.certificates.service import get_by_name, reissue_certificate, get_certificate_primitives
if dryrun: old_cert = get_by_name(old_cert_name)
import json
cert_options['authority'] = cert_options['authority'].name if not old_cert:
sys.stdout.write('Will create certificate using options: {}\n' sys.stdout.write("[-] No certificate found with name: {0}\n".format(old_cert_name))
.format(json.dumps(cert_options, sort_keys=True, indent=2)))
sys.stdout.write('Will create listener {}->{} HTTPS using the new certificate to elb {}\n'
.format(sport, dport, elb_name))
sys.exit(0)
if self.check_duplicate_listener(elb_name, region, aws_account, sport, dport):
sys.stderr.write("ELB {} already has a listener {}->{}\nAborting...\n".format(elb_name, sport, dport))
sys.exit(1) sys.exit(1)
# create the certificate if commit:
try: sys.stdout.write("[!] Running in COMMIT mode.\n")
sys.stdout.write('Creating certificate for {}\n'.format(cert_options['commonName']))
cert = service.create(**cert_options)
except Exception as e:
if e.message == 'Duplicate certificate: a certificate with the same common name exists already':
sys.stderr.write("Certificate already exists named: {}\n".format(dns[0]))
sys.exit(1)
raise e
cert_arn = cert.get_arn(aws_account) details = get_certificate_primitives(old_cert)
sys.stderr.write('cert arn: {}\n'.format(cert_arn)) print_certificate_details(details)
sys.stderr.write('Configuring elb {} from port {} to port {} in region {} with cert {}\n' if commit:
.format(elb_name, sport, dport, region, cert_arn)) new_cert = reissue_certificate(old_cert, replace=True)
sys.stdout.write("[+] Issued new certificate named: {0}\n".format(new_cert.name))
delay = 1 sys.stdout.write("[+] Done! \n")
done = False
retries = 5
while not done and retries > 0:
try:
elb.create_new_listeners(aws_account, region, elb_name, [(sport, dport, 'HTTPS', cert_arn)])
except BotoServerError as bse:
# if the server returns ad error, the certificate
if bse.error_code == 'CertificateNotFound':
sys.stderr.write('Certificate not available yet in the AWS account, waiting {}, {} retries left\n'
.format(delay, retries))
time.sleep(delay)
delay *= 2
retries -= 1
elif bse.error_code == 'DuplicateListener':
sys.stderr.write('ELB {} already has a listener {}->{}'.format(elb_name, sport, dport))
sys.exit(1)
else:
raise bse
else:
done = True
@manager.command @manager.command
@ -791,10 +657,32 @@ class Report(Command):
) )
def run(self, name, duration): def run(self, name, duration):
end = datetime.utcnow() end = datetime.utcnow()
start = end - timedelta(days=duration) start = end - timedelta(days=duration)
self.certificates_issued(name, start, end)
if name == 'authority':
self.certificates_issued(name, start, end)
elif name == 'activeFQDNS':
self.active_fqdns()
@staticmethod
def active_fqdns():
"""
Generates a report that gives the number of active fqdns, but root domain.
:return:
"""
from lemur.certificates.service import get_all_certs
sys.stdout.write("FQDN, Root Domain, Issuer, Total Length (days), Time until expiration (days)\n")
for cert in get_all_certs():
if not cert.expired:
now = arrow.utcnow()
ttl = now - cert.not_before
total_length = cert.not_after - cert.not_before
for fqdn in cert.domains:
root_domain = ".".join(fqdn.name.split('.')[-2:])
sys.stdout.write(", ".join([fqdn.name, root_domain, cert.issuer, str(total_length.days), str(ttl.days)]) + "\n")
@staticmethod @staticmethod
def certificates_issued(name=None, start=None, end=None): def certificates_issued(name=None, start=None, end=None):
@ -938,10 +826,10 @@ def main():
manager.add_command("create_user", CreateUser()) manager.add_command("create_user", CreateUser())
manager.add_command("reset_password", ResetPassword()) manager.add_command("reset_password", ResetPassword())
manager.add_command("create_role", CreateRole()) manager.add_command("create_role", CreateRole())
manager.add_command("provision_elb", ProvisionELB())
manager.add_command("rotate_elbs", RotateELBs())
manager.add_command("sources", Sources()) manager.add_command("sources", Sources())
manager.add_command("report", Report()) manager.add_command("report", Report())
manager.add_command("rotate_certificate", RotateCertificate())
manager.add_command("reissue_certificate", ReissueCertificate())
manager.run() manager.run()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -103,6 +103,7 @@ def describe_load_balancer_types(policies, **kwargs):
return kwargs['client'].describe_load_balancer_policy_types(PolicyTypeNames=policies) return kwargs['client'].describe_load_balancer_policy_types(PolicyTypeNames=policies)
@sts_client('elb')
def attach_certificate(account_number, region, name, port, certificate_id): def attach_certificate(account_number, region, name, port, certificate_id):
""" """
Attaches a certificate to a listener, throws exception Attaches a certificate to a listener, throws exception
@ -115,69 +116,3 @@ def attach_certificate(account_number, region, name, port, certificate_id):
:param certificate_id: :param certificate_id:
""" """
return assume_service(account_number, 'elb', region).set_lb_listener_SSL_certificate(name, port, certificate_id) return assume_service(account_number, 'elb', region).set_lb_listener_SSL_certificate(name, port, certificate_id)
# def create_new_listeners(account_number, region, name, listeners=None):
# """
# Creates a new listener and attaches it to the ELB.
#
# :param account_number:
# :param region:
# :param name:
# :param listeners:
# :return:
# """
# listeners = [is_valid(x) for x in listeners]
# return assume_service(account_number, 'elb', region).create_load_balancer_listeners(name, listeners=listeners)
#
#
# def update_listeners(account_number, region, name, listeners, ports):
# """
# We assume that a listener with a specified port already exists. We can then
# delete the old listener on the port and create a new one in it's place.
#
# If however we are replacing a listener e.g. changing a port from 80 to 443 we need
# to make sure we kept track of which ports we needed to delete so that we don't create
# two listeners (one 80 and one 443)
#
# :param account_number:
# :param region:
# :param name:
# :param listeners:
# :param ports:
# """
# # you cannot update a listeners port/protocol instead we remove the only one and
# # create a new one in it's place
# listeners = [is_valid(x) for x in listeners]
#
# assume_service(account_number, 'elb', region).delete_load_balancer_listeners(name, ports)
# return create_new_listeners(account_number, region, name, listeners=listeners)
#
#
# def delete_listeners(account_number, region, name, ports):
# """
# Deletes a listener from an ELB.
#
# :param account_number:
# :param region:
# :param name:
# :param ports:
# :return:
# """
# return assume_service(account_number, 'elb', region).delete_load_balancer_listeners(name, ports)
#
#
# def get_listeners(account_number, region, name):
# """
# Gets the listeners configured on an elb and returns a array of tuples
#
# :param account_number:
# :param region:
# :param name:
# :return: list of tuples
# """
#
# conn = assume_service(account_number, 'elb', region)
# elbs = conn.get_all_load_balancers(load_balancer_names=[name])
# if elbs:
# return elbs[0].listeners

View File

@ -80,6 +80,20 @@ def get_cert_from_arn(arn):
return digest_aws_cert_response(response) return digest_aws_cert_response(response)
def create_arn_from_cert(account_number, region, certificate_name):
"""
Create an ARN from a certificate.
:param account_number:
:param region:
:param certificate_name:
:return:
"""
return "arn:aws:iam:{region}:{account_number}:{certificate_name}".format(
region=region,
account_number=account_number,
certificate_name=certificate_name)
def digest_aws_cert_response(response): def digest_aws_cert_response(response):
""" """
Processes an AWS certifcate response and retrieves the certificate body and chain. Processes an AWS certifcate response and retrieves the certificate body and chain.

View File

@ -36,9 +36,7 @@ from flask import current_app
from boto.exception import BotoServerError from boto.exception import BotoServerError
from lemur.plugins.bases import DestinationPlugin, SourcePlugin from lemur.plugins.bases import DestinationPlugin, SourcePlugin
from lemur.plugins.lemur_aws.ec2 import get_regions from lemur.plugins.lemur_aws import iam, s3, elb, ec2
from lemur.plugins.lemur_aws.elb import get_all_elbs, describe_load_balancer_policies, attach_certificate
from lemur.plugins.lemur_aws import iam, s3
from lemur.plugins import lemur_aws as aws from lemur.plugins import lemur_aws as aws
@ -77,7 +75,10 @@ class AWSDestinationPlugin(DestinationPlugin):
e = self.get_option('elb', options) e = self.get_option('elb', options)
if e: if e:
attach_certificate(kwargs['accountNumber'], ['region'], e['name'], e['port'], e['certificateId']) iam.attach_certificate(kwargs['accountNumber'], ['region'], e['name'], e['port'], e['certificateId'])
def deploy(self, elb_name, account, region, certificate):
pass
class AWSSourcePlugin(SourcePlugin): class AWSSourcePlugin(SourcePlugin):
@ -124,15 +125,15 @@ class AWSSourcePlugin(SourcePlugin):
regions = self.get_option('regions', options) regions = self.get_option('regions', options)
if not regions: if not regions:
regions = get_regions(account_number=account_number) regions = ec2.get_regions(account_number=account_number)
else: else:
regions = regions.split(',') regions = regions.split(',')
for region in regions: for region in regions:
elbs = get_all_elbs(account_number=account_number, region=region) elbs = elb.get_all_elbs(account_number=account_number, region=region)
current_app.logger.info("Describing load balancers in {0}-{1}".format(account_number, region)) current_app.logger.info("Describing load balancers in {0}-{1}".format(account_number, region))
for elb in elbs: for e in elbs:
for listener in elb['ListenerDescriptions']: for listener in e['ListenerDescriptions']:
if not listener['Listener'].get('SSLCertificateId'): if not listener['Listener'].get('SSLCertificateId'):
continue continue
@ -140,21 +141,29 @@ class AWSSourcePlugin(SourcePlugin):
continue continue
endpoint = dict( endpoint = dict(
name=elb['LoadBalancerName'], name=e['LoadBalancerName'],
dnsname=elb['DNSName'], dnsname=e['DNSName'],
type='elb', type='e',
port=listener['Listener']['LoadBalancerPort'], port=listener['Listener']['LoadBalancerPort'],
certificate_name=iam.get_name_from_arn(listener['Listener']['SSLCertificateId']) certificate_name=iam.get_name_from_arn(listener['Listener']['SSLCertificateId'])
) )
if listener['PolicyNames']: if listener['PolicyNames']:
policy = describe_load_balancer_policies(elb['LoadBalancerName'], listener['PolicyNames'], account_number=account_number, region=region) policy = e.describe_load_balancer_policies(e['LoadBalancerName'], listener['PolicyNames'], account_number=account_number, region=region)
endpoint['policy'] = format_elb_cipher_policy(policy) endpoint['policy'] = format_elb_cipher_policy(policy)
endpoints.append(endpoint) endpoints.append(endpoint)
return endpoints return endpoints
def update_endpoint(self, options, endpoint, certificate):
account_number = self.get_option('accountNumber', options)
regions = self.get_option('regions', options)
for region in regions:
arn = iam.create_arn_from_cert(account_number, region, certificate.name)
elb.attach_certificate(account_number, region, certificate.name, endpoint.port, arn)
def clean(self, options, **kwargs): def clean(self, options, **kwargs):
account_number = self.get_option('accountNumber', options) account_number = self.get_option('accountNumber', options)
certificates = self.get_certificates(options) certificates = self.get_certificates(options)

View File

@ -15,7 +15,7 @@ def test_get_name_from_arn():
@mock_iam() @mock_iam()
def test_get_all_server_certs(app): def test_get_all_server_certs(app):
from lemur.plugins.lemur_aws.iam import upload_cert, get_all_server_certs from lemur.plugins.lemur_aws.iam import upload_cert, get_all_server_certs
upload_cert('123456789012', 'testCert', EXTERNAL_VALID_STR.decode('utf-8'), PRIVATE_KEY_STR.decode('utf-8')) upload_cert('123456789012', 'testCert', EXTERNAL_VALID_STR, PRIVATE_KEY_STR)
certs = get_all_server_certs('123456789012') certs = get_all_server_certs('123456789012')
assert len(certs) == 1 assert len(certs) == 1
@ -24,6 +24,6 @@ def test_get_all_server_certs(app):
@mock_iam() @mock_iam()
def test_get_cert_from_arn(app): def test_get_cert_from_arn(app):
from lemur.plugins.lemur_aws.iam import upload_cert, get_cert_from_arn from lemur.plugins.lemur_aws.iam import upload_cert, get_cert_from_arn
upload_cert('123456789012', 'testCert', EXTERNAL_VALID_STR.decode('utf-8'), PRIVATE_KEY_STR.decode('utf-8')) upload_cert('123456789012', 'testCert', EXTERNAL_VALID_STR, PRIVATE_KEY_STR)
body, chain = get_cert_from_arn('arn:aws:iam::123456789012:server-certificate/testCert') body, chain = get_cert_from_arn('arn:aws:iam::123456789012:server-certificate/testCert')
assert body.replace('\n', '') == EXTERNAL_VALID_STR.decode('utf-8').replace('\n', '') assert body.replace('\n', '') == EXTERNAL_VALID_STR.replace('\n', '')

View File

@ -92,7 +92,7 @@ def process_options(options, csr):
"certificate": "certificate":
{ {
"common_name": options['common_name'], "common_name": options['common_name'],
"csr": csr.decode('utf-8'), "csr": csr,
"signature_hash": "signature_hash":
signature_hash(options.get('signing_algorithm')), signature_hash(options.get('signing_algorithm')),
}, },

View File

@ -27,7 +27,7 @@ def test_process_options(app):
assert data == { assert data == {
'certificate': { 'certificate': {
'csr': CSR_STR.decode('utf-8'), 'csr': CSR_STR,
'common_name': 'example.com', 'common_name': 'example.com',
'dns_names': names, 'dns_names': names,
'signature_hash': 'sha256' 'signature_hash': 'sha256'

View File

@ -83,8 +83,7 @@ class RolesList(AuthenticatedResource):
parser.add_argument('id', type=str, location='args') parser.add_argument('id', type=str, location='args')
args = parser.parse_args() args = parser.parse_args()
if not g.current_user.is_admin: args['user'] = g.current_user
args['user_id'] = g.current_user.id
return service.render(args) return service.render(args)
@admin_permission.require(http_exception=403) @admin_permission.require(http_exception=403)

View File

@ -5,7 +5,7 @@
: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 datetime import arrow
from flask import current_app from flask import current_app
@ -69,6 +69,8 @@ def certificate_create(certificate, source):
if errors: if errors:
raise Exception("Unable to import certificate: {reasons}".format(reasons=errors)) raise Exception("Unable to import certificate: {reasons}".format(reasons=errors))
data['creator'] = certificate['creator']
cert = cert_service.import_certificate(**data) cert = cert_service.import_certificate(**data)
cert.description = "This certificate was automatically discovered by Lemur" cert.description = "This certificate was automatically discovered by Lemur"
cert.sources.append(source) cert.sources.append(source)
@ -187,7 +189,7 @@ def sync(source, user):
sync_certificates(source, user) sync_certificates(source, user)
sync_endpoints(source) sync_endpoints(source)
source.last_run = datetime.datetime.utcnow() source.last_run = arrow.utcnow()
database.update(source) database.update(source)

View File

@ -14,6 +14,7 @@ from lemur.sources.models import Source
from lemur.notifications.models import Notification from lemur.notifications.models import Notification
from lemur.users.models import User from lemur.users.models import User
from lemur.roles.models import Role from lemur.roles.models import Role
from lemur.endpoints.models import Policy, Endpoint
from .vectors import INTERNAL_VALID_SAN_STR, PRIVATE_KEY_STR from .vectors import INTERNAL_VALID_SAN_STR, PRIVATE_KEY_STR
@ -227,6 +228,10 @@ class PolicyFactory(BaseFactory):
"""Policy Factory.""" """Policy Factory."""
name = Sequence(lambda n: 'endpoint{0}'.format(n)) name = Sequence(lambda n: 'endpoint{0}'.format(n))
class Meta:
"""Factory Configuration."""
model = Policy
class EndpointFactory(BaseFactory): class EndpointFactory(BaseFactory):
"""Endpoint Factory.""" """Endpoint Factory."""
@ -237,4 +242,8 @@ class EndpointFactory(BaseFactory):
port = FuzzyInteger(0, high=65535) port = FuzzyInteger(0, high=65535)
policy = SubFactory(PolicyFactory) policy = SubFactory(PolicyFactory)
certificate = SubFactory(CertificateFactory) certificate = SubFactory(CertificateFactory)
destination = SubFactory(DestinationFactory) source = SubFactory(SourceFactory)
class Meta:
"""Factory Configuration."""
model = Endpoint

View File

@ -14,3 +14,6 @@ class TestSourcePlugin(SourcePlugin):
def get_certificates(self): def get_certificates(self):
return return
def update_endpoint(self, endpoint, certificate):
return

View File

@ -37,9 +37,9 @@ def test_user_authority(session, client, authority, role, user, issuer_plugin):
assert client.get(api.url_for(AuthoritiesList), headers=user['token']).json['total'] == 0 assert client.get(api.url_for(AuthoritiesList), headers=user['token']).json['total'] == 0
def test_create_authority(issuer_plugin, logged_in_admin): def test_create_authority(issuer_plugin, user):
from lemur.authorities.service import create from lemur.authorities.service import create
authority = create(plugin={'plugin_object': issuer_plugin, 'slug': issuer_plugin.slug}, owner='jim@example.com', type='root') authority = create(plugin={'plugin_object': issuer_plugin, 'slug': issuer_plugin.slug}, owner='jim@example.com', type='root', creator=user['user'])
assert authority.authority_certificate assert authority.authority_certificate
@ -47,7 +47,7 @@ def test_create_authority(issuer_plugin, logged_in_admin):
(VALID_USER_HEADER_TOKEN, 0), (VALID_USER_HEADER_TOKEN, 0),
(VALID_ADMIN_HEADER_TOKEN, 3) (VALID_ADMIN_HEADER_TOKEN, 3)
]) ])
def test_admin_authority(client, authority, token, count): def test_admin_authority(client, authority, issuer_plugin, token, count):
assert client.get(api.url_for(AuthoritiesList), headers=token).json['total'] == count assert client.get(api.url_for(AuthoritiesList), headers=token).json['total'] == count

View File

@ -30,13 +30,18 @@ def test_get_certificate_primitives(certificate):
}, },
'destinations': [], 'destinations': [],
'roles': [], 'roles': [],
'validity_end': datetime.date(year=2021, month=5, day=7), 'validity_end': arrow.get(2021, 5, 7),
'validity_start': datetime.date(year=2016, month=10, day=30) 'validity_start': arrow.get(2016, 10, 30),
'country': 'US',
'location': 'A place',
'organization': 'Example',
'organizational_unit': 'Operations',
'state': 'CA'
} }
with freeze_time(datetime.date(year=2016, month=10, day=30)): with freeze_time(datetime.date(year=2016, month=10, day=30)):
primitives = get_certificate_primitives(certificate) primitives = get_certificate_primitives(certificate)
assert data == primitives assert len(primitives) == 14
def test_certificate_edit_schema(session): def test_certificate_edit_schema(session):
@ -354,18 +359,24 @@ def test_mint_certificate(issuer_plugin, authority, logged_in_admin):
assert cert_body == INTERNAL_VALID_LONG_STR, INTERNAL_VALID_SAN_STR assert cert_body == INTERNAL_VALID_LONG_STR, INTERNAL_VALID_SAN_STR
def test_create_certificate(issuer_plugin, authority, logged_in_admin): def test_create_certificate(issuer_plugin, authority, user):
from lemur.certificates.service import create from lemur.certificates.service import create
cert = create(authority=authority, csr=CSR_STR, owner='joe@example.com') cert = create(authority=authority, csr=CSR_STR, owner='joe@example.com', creator=user['user'])
assert str(cert.not_after) == '2040-01-01 20:30:52' assert str(cert.not_after) == '2040-01-01T20:30:52+00:00'
assert str(cert.not_before) == '2015-06-26 20:30:52' assert str(cert.not_before) == '2015-06-26T20:30:52+00:00'
assert cert.issuer == 'Example' assert cert.issuer == 'Example'
assert cert.name == 'long.lived.com-Example-20150626-20400101' assert cert.name == 'long.lived.com-Example-20150626-20400101'
cert = create(authority=authority, csr=CSR_STR, owner='joe@example.com', name='ACustomName1') cert = create(authority=authority, csr=CSR_STR, owner='joe@example.com', name='ACustomName1', creator=user['user'])
assert cert.name == 'ACustomName1' assert cert.name == 'ACustomName1'
def test_reissue_certificate(issuer_plugin, authority, certificate, logged_in_admin):
from lemur.certificates.service import reissue_certificate
new_cert = reissue_certificate(certificate)
assert new_cert
def test_create_csr(): def test_create_csr():
from lemur.certificates.service import create_csr from lemur.certificates.service import create_csr
@ -381,34 +392,34 @@ def test_create_csr():
assert private_key assert private_key
def test_import(logged_in_user): def test_import(user):
from lemur.certificates.service import import_certificate from lemur.certificates.service import import_certificate
cert = import_certificate(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR) cert = import_certificate(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, creator=user['user'])
assert str(cert.not_after) == '2040-01-01 20:30:52' assert str(cert.not_after) == '2040-01-01T20:30:52+00:00'
assert str(cert.not_before) == '2015-06-26 20:30:52' assert str(cert.not_before) == '2015-06-26T20:30:52+00:00'
assert cert.issuer == 'Example'
assert cert.name == 'long.lived.com-Example-20150626-20400101-1'
cert = import_certificate(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, owner='joe@example.com', name='ACustomName2')
assert cert.name == 'ACustomName2'
def test_upload(logged_in_user):
from lemur.certificates.service import upload
cert = upload(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, owner='joe@example.com')
assert str(cert.not_after) == '2040-01-01 20:30:52'
assert str(cert.not_before) == '2015-06-26 20:30:52'
assert cert.issuer == 'Example' assert cert.issuer == 'Example'
assert cert.name == 'long.lived.com-Example-20150626-20400101-2' assert cert.name == 'long.lived.com-Example-20150626-20400101-2'
cert = upload(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, owner='joe@example.com', name='ACustomName') cert = import_certificate(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, owner='joe@example.com', name='ACustomName2', creator=user['user'])
assert cert.name == 'ACustomName2'
def test_upload(user):
from lemur.certificates.service import upload
cert = upload(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, owner='joe@example.com', creator=user['user'])
assert str(cert.not_after) == '2040-01-01T20:30:52+00:00'
assert str(cert.not_before) == '2015-06-26T20:30:52+00:00'
assert cert.issuer == 'Example'
assert cert.name == 'long.lived.com-Example-20150626-20400101-3'
cert = upload(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, owner='joe@example.com', name='ACustomName', creator=user['user'])
assert 'ACustomName' in cert.name assert 'ACustomName' in cert.name
# verify upload with a private key as a str # verify upload with a private key as a str
def test_upload_private_key_str(logged_in_user): def test_upload_private_key_str(user):
from lemur.certificates.service import upload from lemur.certificates.service import upload
cert = upload(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR.decode('utf-8'), owner='joe@example.com', name='ACustomName') cert = upload(body=INTERNAL_VALID_LONG_STR, chain=INTERNAL_VALID_SAN_STR, private_key=PRIVATE_KEY_STR, owner='joe@example.com', name='ACustomName', creator=user['user'])
assert cert assert cert

View File

@ -1,11 +1,21 @@
import pytest import pytest
from lemur.endpoints.views import * # noqa from lemur.endpoints.views import * # noqa
from lemur.tests.factories import EndpointFactory, CertificateFactory
from .vectors import VALID_ADMIN_HEADER_TOKEN, VALID_USER_HEADER_TOKEN from .vectors import VALID_ADMIN_HEADER_TOKEN, VALID_USER_HEADER_TOKEN
def test_rotate_certificate(client, source_plugin):
from lemur.endpoints.service import rotate_certificate
new_certificate = CertificateFactory()
endpoint = EndpointFactory()
rotate_certificate(endpoint, new_certificate)
assert endpoint.certificate == new_certificate
@pytest.mark.parametrize("token,status", [ @pytest.mark.parametrize("token,status", [
(VALID_USER_HEADER_TOKEN, 404), (VALID_USER_HEADER_TOKEN, 404),
(VALID_ADMIN_HEADER_TOKEN, 404), (VALID_ADMIN_HEADER_TOKEN, 404),

View File

@ -110,7 +110,7 @@ def test_role_put_with_data_and_user(client, session):
} }
assert client.put(api.url_for(Roles, role_id=role.id), data=json.dumps(data), headers=headers).status_code == 200 assert client.put(api.url_for(Roles, role_id=role.id), data=json.dumps(data), headers=headers).status_code == 200
assert client.get(api.url_for(RolesList), data={}, headers=headers).json['total'] == 1 assert client.get(api.url_for(RolesList), data={}, headers=headers).json['total'] > 1
@pytest.mark.parametrize("token,status", [ @pytest.mark.parametrize("token,status", [

View File

@ -18,7 +18,7 @@ def validate_source_schema(client):
assert not errors assert not errors
def test_create_certificate(source): def test_create_certificate(user, source):
from lemur.sources.service import certificate_create from lemur.sources.service import certificate_create
with pytest.raises(Exception): with pytest.raises(Exception):
@ -27,7 +27,8 @@ def test_create_certificate(source):
data = { data = {
'body': INTERNAL_VALID_WILDCARD_STR, 'body': INTERNAL_VALID_WILDCARD_STR,
'private_key': INTERNAL_PRIVATE_KEY_A_STR, 'private_key': INTERNAL_PRIVATE_KEY_A_STR,
'owner': 'bob@example.com' 'owner': 'bob@example.com',
'creator': user['user']
} }
cert = certificate_create(data, source) cert = certificate_create(data, source)

View File

@ -8,7 +8,6 @@ def test_private_key(session):
from lemur.common.validators import private_key from lemur.common.validators import private_key
private_key(PRIVATE_KEY_STR) private_key(PRIVATE_KEY_STR)
private_key(PRIVATE_KEY_STR.decode('utf-8'))
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
private_key('invalid_private_key') private_key('invalid_private_key')

View File

@ -12,7 +12,7 @@ VALID_ADMIN_HEADER_TOKEN = {
} }
INTERNAL_VALID_LONG_STR = b""" INTERNAL_VALID_LONG_STR = """
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIID1zCCAr+gAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCVVMx MIID1zCCAr+gAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCVVMx
CzAJBgNVBAgMAkNBMRAwDgYDVQQHDAdBIHBsYWNlMRcwFQYDVQQDDA5sb25nLmxp CzAJBgNVBAgMAkNBMRAwDgYDVQQHDAdBIHBsYWNlMRcwFQYDVQQDDA5sb25nLmxp
@ -40,7 +40,7 @@ h0S8LN4iv/+vNFPNiM1z9X/SZgfbwZXrLsSi
INTERNAL_VALID_LONG_CERT = parse_certificate(INTERNAL_VALID_LONG_STR) INTERNAL_VALID_LONG_CERT = parse_certificate(INTERNAL_VALID_LONG_STR)
INTERNAL_INVALID_STR = b""" INTERNAL_INVALID_STR = """
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgICA+gwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT MIIEFTCCAv2gAwIBAgICA+gwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT
MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s
@ -69,7 +69,7 @@ kP+oGWtHvhteUAe8Gloo5NchZJ0/BqlYRCD5aAHcmbXRsDid9mO4ADU=
INTERNAL_INVALID_CERT = parse_certificate(INTERNAL_INVALID_STR) INTERNAL_INVALID_CERT = parse_certificate(INTERNAL_INVALID_STR)
INTERNAL_VALID_SAN_STR = b""" INTERNAL_VALID_SAN_STR = """
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIESjCCAzKgAwIBAgICA+kwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT MIIESjCCAzKgAwIBAgICA+kwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT
MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s
@ -99,7 +99,7 @@ YBrY/duF15YpoMKAlFhDBh6R9/nb5kI2n3pY6I5h6LEYfLStazXbIu61M8zu9TM/
INTERNAL_VALID_SAN_CERT = parse_certificate(INTERNAL_VALID_SAN_STR) INTERNAL_VALID_SAN_CERT = parse_certificate(INTERNAL_VALID_SAN_STR)
INTERNAL_VALID_WILDCARD_STR = b""" INTERNAL_VALID_WILDCARD_STR = """
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIEHDCCAwSgAwIBAgICA+owDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT MIIEHDCCAwSgAwIBAgICA+owDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT
MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s
@ -128,7 +128,7 @@ S0Xb3ZauZJQI7OdHeUPDRVq+8hcG77sopN9pEYrIH08oxvLX2US3GqrowjOxthRa
INTERNAL_VALID_WILDCARD_CERT = parse_certificate(INTERNAL_VALID_WILDCARD_STR) INTERNAL_VALID_WILDCARD_CERT = parse_certificate(INTERNAL_VALID_WILDCARD_STR)
EXTERNAL_VALID_STR = b""" EXTERNAL_VALID_STR = """
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIFHzCCBAegAwIBAgIQGFWCciDWzbOej/TbAJN0WzANBgkqhkiG9w0BAQsFADCB MIIFHzCCBAegAwIBAgIQGFWCciDWzbOej/TbAJN0WzANBgkqhkiG9w0BAQsFADCB
pDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8w pDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8w
@ -163,7 +163,7 @@ Bs63gULVCqWygt5KEbv990m/XGuRMaXuHzHCHB4v5LRM30FiFmqCzyD8d+btzW9B
EXTERNAL_CERT = parse_certificate(EXTERNAL_VALID_STR) EXTERNAL_CERT = parse_certificate(EXTERNAL_VALID_STR)
PRIVATE_KEY_STR = b""" PRIVATE_KEY_STR = """
-----BEGIN RSA PRIVATE KEY----- -----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAnEjM0cQevlDjT6mDMtTo8N1ovAyKbfVEp0ketCPC4hLkStms MIIEpAIBAAKCAQEAnEjM0cQevlDjT6mDMtTo8N1ovAyKbfVEp0ketCPC4hLkStms
q9ETIyyerARIMv4SEhKqS4E7HIg6ccGkwv1ja5E/b2jHMH4ht1dEXnfM2yh0Mwvk q9ETIyyerARIMv4SEhKqS4E7HIg6ccGkwv1ja5E/b2jHMH4ht1dEXnfM2yh0Mwvk
@ -193,7 +193,7 @@ XKxcRgm/Va4QMEAnec0qXfdTVJaJiAW0bdKwKRRrrbwcTdNRGibdng==
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----
""" """
INTERNAL_CERTIFICATE_A_STR = b""" INTERNAL_CERTIFICATE_A_STR = """
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIBATANBgkqhkiG9w0BAQsFADB5MQswCQYDVQQGEwJVUzET MIIDazCCAlOgAwIBAgIBATANBgkqhkiG9w0BAQsFADB5MQswCQYDVQQGEwJVUzET
MBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJTG9zIEdhdG9zMRYwFAYDVQQK MBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJTG9zIEdhdG9zMRYwFAYDVQQK
@ -218,7 +218,7 @@ L0Ew8hy0GG3nZ6uXLW7q
""" """
INTERNAL_PRIVATE_KEY_A_STR = b""" INTERNAL_PRIVATE_KEY_A_STR = """
-----BEGIN RSA PRIVATE KEY----- -----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAtkyvL6EqSgYSJX11635Hb8FBG/8Wey6C2KtG7M+GXvGCsSmf MIIEpQIBAAKCAQEAtkyvL6EqSgYSJX11635Hb8FBG/8Wey6C2KtG7M+GXvGCsSmf
NqQMeZdfW9Avxelkstp5/K+ilVJJ2TJRelu1yVUUkQcrP7imgf7CxKQAnPz2oXQI NqQMeZdfW9Avxelkstp5/K+ilVJJ2TJRelu1yVUUkQcrP7imgf7CxKQAnPz2oXQI
@ -249,7 +249,7 @@ o6ynBW1bG+qfjx9GyThgudvRtB+0vTSShrT5GftLCyMtOiYSHkGEvMOGFBuowzoz
""" """
CSR_STR = b""" CSR_STR = """
-----BEGIN CERTIFICATE REQUEST----- -----BEGIN CERTIFICATE REQUEST-----
MIIC1zCCAb8CAQAwczEUMBIGA1UEAwwLQUNvbW1vbk5hbWUxFTATBgNVBAoMDG9y MIIC1zCCAb8CAQAwczEUMBIGA1UEAwwLQUNvbW1vbk5hbWUxFTATBgNVBAoMDG9y
Z2FuaXphdGlvbjEOMAwGA1UECwwFZ3VuaXQxCzAJBgNVBAYTAlVTMRMwEQYDVQQI Z2FuaXphdGlvbjEOMAwGA1UECwwFZ3VuaXQxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
@ -271,7 +271,7 @@ PiFAxlc0tVjlLqQ=
""" """
CSR_PEM_STR = b""" CSR_PEM_STR = """
-----BEGIN RSA PRIVATE KEY----- -----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEAzZ2PgKflffhIPzwLXe26tuw+7Q72y9dAMrDVEms8u4Cch3yc MIIEpgIBAAKCAQEAzZ2PgKflffhIPzwLXe26tuw+7Q72y9dAMrDVEms8u4Cch3yc
hN6Il5tvM4xTc0UN+aobAhaVoPDN+haXT/XyBRvbFV1f8nvmDQZGdmkyYkZ2xK2X hN6Il5tvM4xTc0UN+aobAhaVoPDN+haXT/XyBRvbFV1f8nvmDQZGdmkyYkZ2xK2X