From d45e7d6b85e37d8d916b82b96c1037aef491e557 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Fri, 18 Nov 2016 11:27:46 -0800 Subject: [PATCH] [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. --- lemur/authorities/service.py | 24 +- lemur/authorities/views.py | 3 +- lemur/certificates/models.py | 43 ++- lemur/certificates/service.py | 47 ++- lemur/certificates/views.py | 6 +- lemur/common/defaults.py | 55 +++ lemur/database.py | 3 + lemur/endpoints/service.py | 13 + lemur/endpoints/views.py | 3 +- lemur/manage.py | 330 ++++++------------ lemur/plugins/lemur_aws/elb.py | 67 +--- lemur/plugins/lemur_aws/iam.py | 14 + lemur/plugins/lemur_aws/plugin.py | 33 +- lemur/plugins/lemur_aws/tests/test_iam.py | 6 +- lemur/plugins/lemur_digicert/plugin.py | 2 +- .../lemur_digicert/tests/test_digicert.py | 2 +- lemur/roles/views.py | 3 +- lemur/sources/service.py | 6 +- lemur/tests/factories.py | 11 +- lemur/tests/plugins/source_plugin.py | 3 + lemur/tests/test_authorities.py | 6 +- lemur/tests/test_certificates.py | 65 ++-- lemur/tests/test_endpoints.py | 10 + lemur/tests/test_roles.py | 2 +- lemur/tests/test_sources.py | 5 +- lemur/tests/test_validators.py | 1 - lemur/tests/vectors.py | 20 +- 27 files changed, 393 insertions(+), 390 deletions(-) diff --git a/lemur/authorities/service.py b/lemur/authorities/service.py index 3182b13f..1ca0e80f 100644 --- a/lemur/authorities/service.py +++ b/lemur/authorities/service.py @@ -8,8 +8,6 @@ .. moduleauthor:: Kevin Glisson """ -from flask import g - from lemur import database from lemur.extensions import metrics from lemur.authorities.models import Authority @@ -53,13 +51,14 @@ def mint(**kwargs): elif len(values) == 4: 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 -def create_authority_roles(roles, owner, plugin_title): +def create_authority_roles(roles, owner, plugin_title, creator): """ Creates all of the necessary authority roles. + :param creator: :param roles: :return: """ @@ -75,7 +74,7 @@ def create_authority_roles(roles, owner, plugin_title): # the user creating the authority should be able to administer it if role.username == 'admin': - g.current_user.roles.append(role) + creator.roles.append(role) role_objs.append(role) @@ -95,10 +94,9 @@ def create(**kwargs): """ Creates a new authority. """ - kwargs['creator'] = g.user.email 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['private_key'] = private_key @@ -114,7 +112,7 @@ def create(**kwargs): authority = Authority(**kwargs) 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)) return authority @@ -151,14 +149,14 @@ def get_by_name(authority_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 as a basis for accomplishing that. :param ca_name: """ - if g.current_user.is_admin: + if creator.is_admin: return role_service.get_by_name("{0}_admin".format(ca_name)) else: return role_service.get_by_name("{0}_operator".format(ca_name)) @@ -181,12 +179,12 @@ def render(args): 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 - if not g.current_user.is_admin: + if not args['user'].is_admin: authority_ids = [] - for authority in g.current_user.authorities: + for authority in args['user'].authorities: authority_ids.append(authority.id) - for role in g.current_user.roles: + for role in args['user'].roles: for authority in role.authorities: authority_ids.append(authority.id) query = query.filter(Authority.id.in_(authority_ids)) diff --git a/lemur/authorities/views.py b/lemur/authorities/views.py index 0045409f..1407c37b 100644 --- a/lemur/authorities/views.py +++ b/lemur/authorities/views.py @@ -5,7 +5,7 @@ :license: Apache, see LICENSE for more details. .. moduleauthor:: Kevin Glisson """ -from flask import Blueprint +from flask import Blueprint, g from flask.ext.restful import reqparse, Api from lemur.common.utils import paginated_parser @@ -107,6 +107,7 @@ class AuthoritiesList(AuthenticatedResource): """ parser = paginated_parser.copy() args = parser.parse_args() + args['user'] = g.current_user return service.render(args) @validate_schema(authority_input_schema, authority_output_schema) diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py index 4729afa9..71e23b31 100644 --- a/lemur/certificates/models.py +++ b/lemur/certificates/models.py @@ -5,16 +5,16 @@ :license: Apache, see LICENSE for more details. .. moduleauthor:: Kevin Glisson """ -import datetime +import arrow -import lemur.common.utils from flask import current_app from sqlalchemy.orm import relationship from sqlalchemy.sql.expression import case 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.models import certificate_associations, certificate_source_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.utils import Vault +from sqlalchemy_utils.types.arrow import ArrowType + from lemur.common import defaults from lemur.domains.models import Domain @@ -53,9 +55,9 @@ class Certificate(db.Model): cn = Column(String(128)) deleted = Column(Boolean, index=True) - not_before = Column(DateTime) - not_after = Column(DateTime) - date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False) + not_before = Column(ArrowType) + not_after = Column(ArrowType) + date_created = Column(ArrowType, PassiveDefault(func.now()), nullable=False) signing_algorithm = Column(String(128)) status = Column(String(128)) @@ -120,16 +122,41 @@ class Certificate(db.Model): def active(self): 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 def expired(self): - if self.not_after <= datetime.datetime.now(): + if self.not_after <= arrow.utcnow(): return True @expired.expression def expired(cls): return case( [ - (cls.now_after <= datetime.datetime.now(), True) + (cls.now_after <= arrow.utcnow(), True) ], else_=False ) diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 3b54913e..6643969c 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -8,7 +8,7 @@ import arrow from sqlalchemy import func, or_ -from flask import g, current_app +from flask import current_app from lemur import database from lemur.extensions import metrics @@ -201,11 +201,7 @@ def upload(**kwargs): cert = database.create(cert) - try: - g.user.certificates.append(cert) - except AttributeError: - current_app.logger.debug("No user to associate uploaded certificate to.") - + kwargs['creator'].certificates.append(cert) return database.update(cert) @@ -213,7 +209,6 @@ def create(**kwargs): """ Creates a new certificate. """ - kwargs['creator'] = g.user.email cert_body, private_key, cert_chain = mint(**kwargs) kwargs['body'] = cert_body kwargs['private_key'] = private_key @@ -228,7 +223,7 @@ def create(**kwargs): cert = Certificate(**kwargs) - g.user.certificates.append(cert) + kwargs['creator'].certificates.append(cert) cert.authority = kwargs['authority'] database.commit() @@ -286,10 +281,10 @@ def render(args): query = database.filter(query, Certificate, terms) 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( or_( - Certificate.user_id == g.user.id, + Certificate.user_id == args['user'].id, Certificate.owner.in_(sub_query) ) ) @@ -476,10 +471,9 @@ def calculate_reissue_range(start, end): new_start = arrow.utcnow().date() 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): """ 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) names = [{'name_type': 'DNSName', 'value': x.name} for x in certificate.domains] + # TODO pull additional extensions extensions = { 'sub_alt_names': { 'names': names @@ -506,5 +501,31 @@ def get_certificate_primitives(certificate): destinations=certificate.destinations, roles=certificate.roles, 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 diff --git a/lemur/certificates/views.py b/lemur/certificates/views.py index eee3e132..8a1a2ee2 100644 --- a/lemur/certificates/views.py +++ b/lemur/certificates/views.py @@ -8,7 +8,7 @@ import base64 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 lemur.common.schema import validate_schema @@ -129,6 +129,7 @@ class CertificatesList(AuthenticatedResource): parser.add_argument('show', type=str, location='args') args = parser.parse_args() + args['user'] = g.user return service.render(args) @validate_schema(certificate_input_schema, certificate_output_schema) @@ -265,6 +266,7 @@ class CertificatesList(AuthenticatedResource): authority_permission = AuthorityPermission(data['authority'].id, roles) if authority_permission.can(): + data['creator'] = g.user return service.create(**data) 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('private_key'): + data['creator'] = g.user return service.upload(**data) else: 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['notification_id'] = notification_id + args['user'] = g.current_user return service.render(args) diff --git a/lemur/common/defaults.py b/lemur/common/defaults.py index 6e6c11dc..6f1aff67 100644 --- a/lemur/common/defaults.py +++ b/lemur/common/defaults.py @@ -58,6 +58,61 @@ def common_name(cert): )[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): """ Attempts to get an domains listed in a certificate. diff --git a/lemur/database.py b/lemur/database.py index 4929aa9e..5f4f6004 100644 --- a/lemur/database.py +++ b/lemur/database.py @@ -274,6 +274,9 @@ def sort_and_page(query, model, args): page = args.pop('page') count = args.pop('count') + if args.get('user'): + user = args.pop('user') + query = find_all(query, model, args) if sort_by and sort_dir: diff --git a/lemur/endpoints/service.py b/lemur/endpoints/service.py index 09f94bc4..d6363619 100644 --- a/lemur/endpoints/service.py +++ b/lemur/endpoints/service.py @@ -8,6 +8,8 @@ .. moduleauthor:: Kevin Glisson """ +from flask import current_app + from lemur import database from lemur.extensions import metrics from lemur.endpoints.models import Endpoint, Policy, Cipher @@ -96,6 +98,17 @@ def update(endpoint_id, **kwargs): 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): """ Helper that helps us render the REST Api responses. diff --git a/lemur/endpoints/views.py b/lemur/endpoints/views.py index d974d8d1..c78cffa4 100644 --- a/lemur/endpoints/views.py +++ b/lemur/endpoints/views.py @@ -5,7 +5,7 @@ :license: Apache, see LICENSE for more details. .. moduleauthor:: Kevin Glisson """ -from flask import Blueprint +from flask import Blueprint, g from flask.ext.restful import reqparse, Api from lemur.common.utils import paginated_parser @@ -63,6 +63,7 @@ class EndpointsList(AuthenticatedResource): """ parser = paginated_parser.copy() args = parser.parse_args() + args['user'] = g.current_user return service.render(args) diff --git a/lemur/manage.py b/lemur/manage.py index 9f55ba5f..4b95a183 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals # at top of module +import arrow from datetime import datetime, timedelta 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.notifications import service as notification_service -from lemur.certificates.service import get_name_from_arn from lemur.certificates.verify import verify_string - -from lemur.plugins.lemur_aws import elb - from lemur.sources import service as source_service from lemur import create_app @@ -487,253 +484,122 @@ def unicode_(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 = ( - Option('-e', '--elb-list', dest='elb_list', required=True), - Option('-p', '--chain-path', dest='chain_path'), - Option('-c', '--cert-name', dest='cert_name'), - Option('-a', '--cert-prefix', dest='cert_prefix'), - Option('-d', '--description', dest='description') + sys.stdout.write("[+] Re-issuing certificate with the following details: \n") + sys.stdout.write( + "[+] Common Name: {common_name}\n" + "[+] Subject Alternate Names: {sans}\n" + "[+] Authority: {authority_name}\n" + "[+] 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(): - 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): +class RotateCertificate(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('-d', '--dns', dest='dns', action='append', required=True, type=unicode_), - Option('-e', '--elb', dest='elb_name', required=True, type=unicode_), - Option('-o', '--owner', dest='owner', type=unicode_), - 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') + Option('-n', '--cert-name', dest='new_cert_name'), + Option('-o', '--old-cert-name', dest='old_cert_name', required=True), + Option('-c', '--commit', dest='commit', action='store_true', default=False) ) - def configure_user(self, owner): - from flask import g - import lemur.users.service + def run(self, new_cert_name, old_cert_name, commit): + from lemur.certificates.service import get_by_name, reissue_certificate, get_certificate_primitives + from lemur.endpoints.service import rotate_certificate - # grab the user - 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] + old_cert = get_by_name(old_cert_name) - return g.user.username - - 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") + if not old_cert: + sys.stdout.write("[-] No certificate found with name: {0}\n".format(old_cert_name)) sys.exit(1) - # get the primary CN - common_name = dns[0] + if new_cert_name: + new_cert = get_by_name(new_cert_name) - # If there are more than one fqdn, add them as alternate names - extensions = {} - 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)) + if not new_cert: + sys.stdout.write("[-] No certificate found with name: {0}\n".format(old_cert_name)) 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): - from lemur.plugins.lemur_aws import elb + details = get_certificate_primitives(old_cert) + print_certificate_details(details) - listeners = elb.get_listeners(account, region, elb_name) - for listener in listeners: - if listener[0] == sport and listener[1] == dport: - return True - return False + if commit: + new_cert = reissue_certificate(old_cert, replace=True) + sys.stdout.write("[+] Issued new certificate named: {0}\n".format(new_cert.name)) - def get_destination_account(self, destinations): - for destination in self.get_destinations(destinations): - if destination.plugin_name == 'aws-destination': + sys.stdout.write("[+] Done! \n") - account_number = destination.plugin.get_option('accountNumber', destination.options) - return account_number + if len(old_cert.endpoints) > 0: + 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") - sys.exit(1) + if commit: + rotate_certificate(endpoint, new_cert_name) - def run(self, dns, elb_name, owner, authority, description, notifications, destinations, region, dport, sport, - dryrun): - from lemur.certificates import service - from lemur.plugins.lemur_aws import elb - from boto.exception import BotoServerError + sys.stdout.write("[+] Done! \n") + else: + sys.stdout.write("[!] Certificate not found on any existing endpoints. Nothing to rotate.\n") - # 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 - cert_options = self.build_cert_options( - destinations=destinations, - notifications=notifications, - description=description, - owner=owner, - dns=dns, - authority=authority) +class ReissueCertificate(Command): + """ + Reissues a certificate based on a given certificate. + """ + option_list = ( + Option('-o', '--old-cert-name', dest='old_cert_name', required=True), + Option('-c', '--commit', dest='commit', action='store_true', default=False) + ) - 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: - import json + old_cert = get_by_name(old_cert_name) - cert_options['authority'] = cert_options['authority'].name - sys.stdout.write('Will create certificate using options: {}\n' - .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)) + if not old_cert: + sys.stdout.write("[-] No certificate found with name: {0}\n".format(old_cert_name)) sys.exit(1) - # create the certificate - try: - 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 + if commit: + sys.stdout.write("[!] Running in COMMIT mode.\n") - cert_arn = cert.get_arn(aws_account) - sys.stderr.write('cert arn: {}\n'.format(cert_arn)) + details = get_certificate_primitives(old_cert) + print_certificate_details(details) - sys.stderr.write('Configuring elb {} from port {} to port {} in region {} with cert {}\n' - .format(elb_name, sport, dport, region, cert_arn)) + if commit: + new_cert = reissue_certificate(old_cert, replace=True) + sys.stdout.write("[+] Issued new certificate named: {0}\n".format(new_cert.name)) - delay = 1 - 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 + sys.stdout.write("[+] Done! \n") @manager.command @@ -791,10 +657,32 @@ class Report(Command): ) def run(self, name, duration): - end = datetime.utcnow() 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 def certificates_issued(name=None, start=None, end=None): @@ -938,10 +826,10 @@ def main(): manager.add_command("create_user", CreateUser()) manager.add_command("reset_password", ResetPassword()) 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("report", Report()) + manager.add_command("rotate_certificate", RotateCertificate()) + manager.add_command("reissue_certificate", ReissueCertificate()) manager.run() if __name__ == "__main__": diff --git a/lemur/plugins/lemur_aws/elb.py b/lemur/plugins/lemur_aws/elb.py index 5ccdc5a3..385bf0d4 100644 --- a/lemur/plugins/lemur_aws/elb.py +++ b/lemur/plugins/lemur_aws/elb.py @@ -103,6 +103,7 @@ def describe_load_balancer_types(policies, **kwargs): return kwargs['client'].describe_load_balancer_policy_types(PolicyTypeNames=policies) +@sts_client('elb') def attach_certificate(account_number, region, name, port, certificate_id): """ 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: """ 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 diff --git a/lemur/plugins/lemur_aws/iam.py b/lemur/plugins/lemur_aws/iam.py index 03124599..b332484f 100644 --- a/lemur/plugins/lemur_aws/iam.py +++ b/lemur/plugins/lemur_aws/iam.py @@ -80,6 +80,20 @@ def get_cert_from_arn(arn): 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): """ Processes an AWS certifcate response and retrieves the certificate body and chain. diff --git a/lemur/plugins/lemur_aws/plugin.py b/lemur/plugins/lemur_aws/plugin.py index dfee63e6..da100fb3 100644 --- a/lemur/plugins/lemur_aws/plugin.py +++ b/lemur/plugins/lemur_aws/plugin.py @@ -36,9 +36,7 @@ from flask import current_app from boto.exception import BotoServerError from lemur.plugins.bases import DestinationPlugin, SourcePlugin -from lemur.plugins.lemur_aws.ec2 import get_regions -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.lemur_aws import iam, s3, elb, ec2 from lemur.plugins import lemur_aws as aws @@ -77,7 +75,10 @@ class AWSDestinationPlugin(DestinationPlugin): e = self.get_option('elb', options) 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): @@ -124,15 +125,15 @@ class AWSSourcePlugin(SourcePlugin): regions = self.get_option('regions', options) if not regions: - regions = get_regions(account_number=account_number) + regions = ec2.get_regions(account_number=account_number) else: regions = regions.split(',') 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)) - for elb in elbs: - for listener in elb['ListenerDescriptions']: + for e in elbs: + for listener in e['ListenerDescriptions']: if not listener['Listener'].get('SSLCertificateId'): continue @@ -140,21 +141,29 @@ class AWSSourcePlugin(SourcePlugin): continue endpoint = dict( - name=elb['LoadBalancerName'], - dnsname=elb['DNSName'], - type='elb', + name=e['LoadBalancerName'], + dnsname=e['DNSName'], + type='e', port=listener['Listener']['LoadBalancerPort'], certificate_name=iam.get_name_from_arn(listener['Listener']['SSLCertificateId']) ) 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) endpoints.append(endpoint) 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): account_number = self.get_option('accountNumber', options) certificates = self.get_certificates(options) diff --git a/lemur/plugins/lemur_aws/tests/test_iam.py b/lemur/plugins/lemur_aws/tests/test_iam.py index 03342aef..eee0e236 100644 --- a/lemur/plugins/lemur_aws/tests/test_iam.py +++ b/lemur/plugins/lemur_aws/tests/test_iam.py @@ -15,7 +15,7 @@ def test_get_name_from_arn(): @mock_iam() def test_get_all_server_certs(app): 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') assert len(certs) == 1 @@ -24,6 +24,6 @@ def test_get_all_server_certs(app): @mock_iam() def test_get_cert_from_arn(app): 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') - assert body.replace('\n', '') == EXTERNAL_VALID_STR.decode('utf-8').replace('\n', '') + assert body.replace('\n', '') == EXTERNAL_VALID_STR.replace('\n', '') diff --git a/lemur/plugins/lemur_digicert/plugin.py b/lemur/plugins/lemur_digicert/plugin.py index 5f4e3f64..b839daac 100644 --- a/lemur/plugins/lemur_digicert/plugin.py +++ b/lemur/plugins/lemur_digicert/plugin.py @@ -92,7 +92,7 @@ def process_options(options, csr): "certificate": { "common_name": options['common_name'], - "csr": csr.decode('utf-8'), + "csr": csr, "signature_hash": signature_hash(options.get('signing_algorithm')), }, diff --git a/lemur/plugins/lemur_digicert/tests/test_digicert.py b/lemur/plugins/lemur_digicert/tests/test_digicert.py index 816f3a25..4027446c 100644 --- a/lemur/plugins/lemur_digicert/tests/test_digicert.py +++ b/lemur/plugins/lemur_digicert/tests/test_digicert.py @@ -27,7 +27,7 @@ def test_process_options(app): assert data == { 'certificate': { - 'csr': CSR_STR.decode('utf-8'), + 'csr': CSR_STR, 'common_name': 'example.com', 'dns_names': names, 'signature_hash': 'sha256' diff --git a/lemur/roles/views.py b/lemur/roles/views.py index 3d85b4d6..6cbe5fcd 100644 --- a/lemur/roles/views.py +++ b/lemur/roles/views.py @@ -83,8 +83,7 @@ class RolesList(AuthenticatedResource): parser.add_argument('id', type=str, location='args') args = parser.parse_args() - if not g.current_user.is_admin: - args['user_id'] = g.current_user.id + args['user'] = g.current_user return service.render(args) @admin_permission.require(http_exception=403) diff --git a/lemur/sources/service.py b/lemur/sources/service.py index 663261a4..42762489 100644 --- a/lemur/sources/service.py +++ b/lemur/sources/service.py @@ -5,7 +5,7 @@ :license: Apache, see LICENSE for more details. .. moduleauthor:: Kevin Glisson """ -import datetime +import arrow from flask import current_app @@ -69,6 +69,8 @@ def certificate_create(certificate, source): if errors: raise Exception("Unable to import certificate: {reasons}".format(reasons=errors)) + data['creator'] = certificate['creator'] + cert = cert_service.import_certificate(**data) cert.description = "This certificate was automatically discovered by Lemur" cert.sources.append(source) @@ -187,7 +189,7 @@ def sync(source, user): sync_certificates(source, user) sync_endpoints(source) - source.last_run = datetime.datetime.utcnow() + source.last_run = arrow.utcnow() database.update(source) diff --git a/lemur/tests/factories.py b/lemur/tests/factories.py index eeedab88..a9023368 100644 --- a/lemur/tests/factories.py +++ b/lemur/tests/factories.py @@ -14,6 +14,7 @@ from lemur.sources.models import Source from lemur.notifications.models import Notification from lemur.users.models import User from lemur.roles.models import Role +from lemur.endpoints.models import Policy, Endpoint from .vectors import INTERNAL_VALID_SAN_STR, PRIVATE_KEY_STR @@ -227,6 +228,10 @@ class PolicyFactory(BaseFactory): """Policy Factory.""" name = Sequence(lambda n: 'endpoint{0}'.format(n)) + class Meta: + """Factory Configuration.""" + model = Policy + class EndpointFactory(BaseFactory): """Endpoint Factory.""" @@ -237,4 +242,8 @@ class EndpointFactory(BaseFactory): port = FuzzyInteger(0, high=65535) policy = SubFactory(PolicyFactory) certificate = SubFactory(CertificateFactory) - destination = SubFactory(DestinationFactory) + source = SubFactory(SourceFactory) + + class Meta: + """Factory Configuration.""" + model = Endpoint diff --git a/lemur/tests/plugins/source_plugin.py b/lemur/tests/plugins/source_plugin.py index c08e7390..10402576 100644 --- a/lemur/tests/plugins/source_plugin.py +++ b/lemur/tests/plugins/source_plugin.py @@ -14,3 +14,6 @@ class TestSourcePlugin(SourcePlugin): def get_certificates(self): return + + def update_endpoint(self, endpoint, certificate): + return diff --git a/lemur/tests/test_authorities.py b/lemur/tests/test_authorities.py index b2e1e840..aef7e4bd 100644 --- a/lemur/tests/test_authorities.py +++ b/lemur/tests/test_authorities.py @@ -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 -def test_create_authority(issuer_plugin, logged_in_admin): +def test_create_authority(issuer_plugin, user): 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 @@ -47,7 +47,7 @@ def test_create_authority(issuer_plugin, logged_in_admin): (VALID_USER_HEADER_TOKEN, 0), (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 diff --git a/lemur/tests/test_certificates.py b/lemur/tests/test_certificates.py index 48827209..5fb8d048 100644 --- a/lemur/tests/test_certificates.py +++ b/lemur/tests/test_certificates.py @@ -30,13 +30,18 @@ def test_get_certificate_primitives(certificate): }, 'destinations': [], 'roles': [], - 'validity_end': datetime.date(year=2021, month=5, day=7), - 'validity_start': datetime.date(year=2016, month=10, day=30) + 'validity_end': arrow.get(2021, 5, 7), + '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)): primitives = get_certificate_primitives(certificate) - assert data == primitives + assert len(primitives) == 14 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 -def test_create_certificate(issuer_plugin, authority, logged_in_admin): +def test_create_certificate(issuer_plugin, authority, user): from lemur.certificates.service import create - cert = create(authority=authority, csr=CSR_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' + cert = create(authority=authority, csr=CSR_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' - 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' +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(): from lemur.certificates.service import create_csr @@ -381,34 +392,34 @@ def test_create_csr(): assert private_key -def test_import(logged_in_user): +def test_import(user): 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) - 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.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' + 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-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-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 # 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 - 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 diff --git a/lemur/tests/test_endpoints.py b/lemur/tests/test_endpoints.py index a2353854..a20e866e 100644 --- a/lemur/tests/test_endpoints.py +++ b/lemur/tests/test_endpoints.py @@ -1,11 +1,21 @@ import pytest from lemur.endpoints.views import * # noqa +from lemur.tests.factories import EndpointFactory, CertificateFactory 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", [ (VALID_USER_HEADER_TOKEN, 404), (VALID_ADMIN_HEADER_TOKEN, 404), diff --git a/lemur/tests/test_roles.py b/lemur/tests/test_roles.py index 7d6a53ea..d6f8c93c 100644 --- a/lemur/tests/test_roles.py +++ b/lemur/tests/test_roles.py @@ -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.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", [ diff --git a/lemur/tests/test_sources.py b/lemur/tests/test_sources.py index 7b428505..2b6e6fea 100644 --- a/lemur/tests/test_sources.py +++ b/lemur/tests/test_sources.py @@ -18,7 +18,7 @@ def validate_source_schema(client): assert not errors -def test_create_certificate(source): +def test_create_certificate(user, source): from lemur.sources.service import certificate_create with pytest.raises(Exception): @@ -27,7 +27,8 @@ def test_create_certificate(source): data = { 'body': INTERNAL_VALID_WILDCARD_STR, 'private_key': INTERNAL_PRIVATE_KEY_A_STR, - 'owner': 'bob@example.com' + 'owner': 'bob@example.com', + 'creator': user['user'] } cert = certificate_create(data, source) diff --git a/lemur/tests/test_validators.py b/lemur/tests/test_validators.py index 37527b1c..b9489ef2 100644 --- a/lemur/tests/test_validators.py +++ b/lemur/tests/test_validators.py @@ -8,7 +8,6 @@ def test_private_key(session): from lemur.common.validators import private_key private_key(PRIVATE_KEY_STR) - private_key(PRIVATE_KEY_STR.decode('utf-8')) with pytest.raises(ValidationError): private_key('invalid_private_key') diff --git a/lemur/tests/vectors.py b/lemur/tests/vectors.py index d029bec2..e9b2be91 100644 --- a/lemur/tests/vectors.py +++ b/lemur/tests/vectors.py @@ -12,7 +12,7 @@ VALID_ADMIN_HEADER_TOKEN = { } -INTERNAL_VALID_LONG_STR = b""" +INTERNAL_VALID_LONG_STR = """ -----BEGIN CERTIFICATE----- MIID1zCCAr+gAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCVVMx CzAJBgNVBAgMAkNBMRAwDgYDVQQHDAdBIHBsYWNlMRcwFQYDVQQDDA5sb25nLmxp @@ -40,7 +40,7 @@ h0S8LN4iv/+vNFPNiM1z9X/SZgfbwZXrLsSi INTERNAL_VALID_LONG_CERT = parse_certificate(INTERNAL_VALID_LONG_STR) -INTERNAL_INVALID_STR = b""" +INTERNAL_INVALID_STR = """ -----BEGIN CERTIFICATE----- MIIEFTCCAv2gAwIBAgICA+gwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s @@ -69,7 +69,7 @@ kP+oGWtHvhteUAe8Gloo5NchZJ0/BqlYRCD5aAHcmbXRsDid9mO4ADU= INTERNAL_INVALID_CERT = parse_certificate(INTERNAL_INVALID_STR) -INTERNAL_VALID_SAN_STR = b""" +INTERNAL_VALID_SAN_STR = """ -----BEGIN CERTIFICATE----- MIIESjCCAzKgAwIBAgICA+kwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s @@ -99,7 +99,7 @@ YBrY/duF15YpoMKAlFhDBh6R9/nb5kI2n3pY6I5h6LEYfLStazXbIu61M8zu9TM/ INTERNAL_VALID_SAN_CERT = parse_certificate(INTERNAL_VALID_SAN_STR) -INTERNAL_VALID_WILDCARD_STR = b""" +INTERNAL_VALID_WILDCARD_STR = """ -----BEGIN CERTIFICATE----- MIIEHDCCAwSgAwIBAgICA+owDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT MQswCQYDVQQIDAJDQTEQMA4GA1UEBwwHQSBwbGFjZTEXMBUGA1UEAwwObG9uZy5s @@ -128,7 +128,7 @@ S0Xb3ZauZJQI7OdHeUPDRVq+8hcG77sopN9pEYrIH08oxvLX2US3GqrowjOxthRa INTERNAL_VALID_WILDCARD_CERT = parse_certificate(INTERNAL_VALID_WILDCARD_STR) -EXTERNAL_VALID_STR = b""" +EXTERNAL_VALID_STR = """ -----BEGIN CERTIFICATE----- MIIFHzCCBAegAwIBAgIQGFWCciDWzbOej/TbAJN0WzANBgkqhkiG9w0BAQsFADCB pDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8w @@ -163,7 +163,7 @@ Bs63gULVCqWygt5KEbv990m/XGuRMaXuHzHCHB4v5LRM30FiFmqCzyD8d+btzW9B EXTERNAL_CERT = parse_certificate(EXTERNAL_VALID_STR) -PRIVATE_KEY_STR = b""" +PRIVATE_KEY_STR = """ -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAnEjM0cQevlDjT6mDMtTo8N1ovAyKbfVEp0ketCPC4hLkStms q9ETIyyerARIMv4SEhKqS4E7HIg6ccGkwv1ja5E/b2jHMH4ht1dEXnfM2yh0Mwvk @@ -193,7 +193,7 @@ XKxcRgm/Va4QMEAnec0qXfdTVJaJiAW0bdKwKRRrrbwcTdNRGibdng== -----END RSA PRIVATE KEY----- """ -INTERNAL_CERTIFICATE_A_STR = b""" +INTERNAL_CERTIFICATE_A_STR = """ -----BEGIN CERTIFICATE----- MIIDazCCAlOgAwIBAgIBATANBgkqhkiG9w0BAQsFADB5MQswCQYDVQQGEwJVUzET MBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJTG9zIEdhdG9zMRYwFAYDVQQK @@ -218,7 +218,7 @@ L0Ew8hy0GG3nZ6uXLW7q """ -INTERNAL_PRIVATE_KEY_A_STR = b""" +INTERNAL_PRIVATE_KEY_A_STR = """ -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAtkyvL6EqSgYSJX11635Hb8FBG/8Wey6C2KtG7M+GXvGCsSmf NqQMeZdfW9Avxelkstp5/K+ilVJJ2TJRelu1yVUUkQcrP7imgf7CxKQAnPz2oXQI @@ -249,7 +249,7 @@ o6ynBW1bG+qfjx9GyThgudvRtB+0vTSShrT5GftLCyMtOiYSHkGEvMOGFBuowzoz """ -CSR_STR = b""" +CSR_STR = """ -----BEGIN CERTIFICATE REQUEST----- MIIC1zCCAb8CAQAwczEUMBIGA1UEAwwLQUNvbW1vbk5hbWUxFTATBgNVBAoMDG9y Z2FuaXphdGlvbjEOMAwGA1UECwwFZ3VuaXQxCzAJBgNVBAYTAlVTMRMwEQYDVQQI @@ -271,7 +271,7 @@ PiFAxlc0tVjlLqQ= """ -CSR_PEM_STR = b""" +CSR_PEM_STR = """ -----BEGIN RSA PRIVATE KEY----- MIIEpgIBAAKCAQEAzZ2PgKflffhIPzwLXe26tuw+7Q72y9dAMrDVEms8u4Cch3yc hN6Il5tvM4xTc0UN+aobAhaVoPDN+haXT/XyBRvbFV1f8nvmDQZGdmkyYkZ2xK2X