Skip to content

Commit

Permalink
[WIP] - 422 elb rotate (#493)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
kevgliss committed Nov 18, 2016
1 parent 6fd47ed commit d45e7d6
Show file tree
Hide file tree
Showing 27 changed files with 388 additions and 385 deletions.
24 changes: 11 additions & 13 deletions lemur/authorities/service.py
Expand Up @@ -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
Expand Down Expand Up @@ -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:
"""
Expand All @@ -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)

Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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))
Expand All @@ -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))
Expand Down
3 changes: 2 additions & 1 deletion lemur/authorities/views.py
Expand Up @@ -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
Expand Down Expand Up @@ -107,6 +107,7 @@ def get(self):
"""
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)
Expand Down
43 changes: 35 additions & 8 deletions lemur/certificates/models.py
Expand Up @@ -5,23 +5,25 @@
: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, \
certificate_replacement_associations, roles_certificates
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

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -120,16 +122,41 @@ def __init__(self, **kwargs):
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
)
Expand Down
47 changes: 34 additions & 13 deletions lemur/certificates/service.py
Expand Up @@ -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
Expand Down Expand Up @@ -201,19 +201,14 @@ 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)


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
Expand All @@ -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()

Expand Down Expand Up @@ -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)
)
)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
6 changes: 5 additions & 1 deletion lemur/certificates/views.py
Expand Up @@ -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
Expand Down Expand Up @@ -129,6 +129,7 @@ def get(self):
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)
Expand Down Expand Up @@ -265,6 +266,7 @@ def post(self, data=None):
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
Expand Down Expand Up @@ -371,6 +373,7 @@ def post(self, data=None):
"""
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")
Expand Down Expand Up @@ -740,6 +743,7 @@ def get(self, notification_id):

args = parser.parse_args()
args['notification_id'] = notification_id
args['user'] = g.current_user
return service.render(args)


Expand Down
55 changes: 55 additions & 0 deletions lemur/common/defaults.py
Expand Up @@ -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.
Expand Down

0 comments on commit d45e7d6

Please sign in to comment.