[WIP] - 422 elb rotate (#493)

* Initial work on certificate rotation.

* Adding ability to get additional certificate info.

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

View File

@ -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
)

View File

@ -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

View File

@ -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)