[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:
parent
6fd47edbe3
commit
d45e7d6b85
@ -8,8 +8,6 @@
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
|
||||
"""
|
||||
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))
|
||||
|
@ -5,7 +5,7 @@
|
||||
:license: Apache, see LICENSE for more details.
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
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)
|
||||
|
@ -5,16 +5,16 @@
|
||||
:license: Apache, see LICENSE for more details.
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
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
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -8,6 +8,8 @@
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
|
||||
"""
|
||||
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.
|
||||
|
@ -5,7 +5,7 @@
|
||||
:license: Apache, see LICENSE for more details.
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
|
330
lemur/manage.py
330
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__":
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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', '')
|
||||
|
@ -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')),
|
||||
},
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
@ -5,7 +5,7 @@
|
||||
:license: Apache, see LICENSE for more details.
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -14,3 +14,6 @@ class TestSourcePlugin(SourcePlugin):
|
||||
|
||||
def get_certificates(self):
|
||||
return
|
||||
|
||||
def update_endpoint(self, endpoint, certificate):
|
||||
return
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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", [
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user