This commit is contained in:
@ -13,16 +13,17 @@ from cryptography.hazmat.backends import default_backend
|
||||
from flask import current_app
|
||||
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import Integer, ForeignKey, String, DateTime, PassiveDefault, func, Column, Text, Boolean
|
||||
from sqlalchemy import event, Integer, ForeignKey, String, DateTime, PassiveDefault, func, Column, Text, Boolean
|
||||
|
||||
from sqlalchemy_utils import EncryptedType
|
||||
|
||||
from lemur.database import db
|
||||
from lemur.plugins.base import plugins
|
||||
|
||||
from lemur.domains.models import Domain
|
||||
|
||||
from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE
|
||||
from lemur.models import certificate_associations, certificate_destination_associations
|
||||
from lemur.models import certificate_associations, certificate_destination_associations, certificate_notification_associations
|
||||
|
||||
|
||||
def create_name(issuer, not_before, not_after, subject, san):
|
||||
@ -147,7 +148,7 @@ def cert_get_issuer(cert):
|
||||
"""
|
||||
delchars = ''.join(c for c in map(chr, range(256)) if not c.isalnum())
|
||||
try:
|
||||
issuer = str(cert.subject.get_attributes_for_oid(x509.OID_ORGANIZATION_NAME)[0].value)
|
||||
issuer = str(cert.issuer.get_attributes_for_oid(x509.OID_ORGANIZATION_NAME)[0].value)
|
||||
return issuer.translate(None, delchars)
|
||||
except Exception as e:
|
||||
current_app.logger.error("Unable to get issuer! {0}".format(e))
|
||||
@ -203,8 +204,6 @@ class Certificate(db.Model):
|
||||
owner = Column(String(128))
|
||||
body = Column(Text())
|
||||
private_key = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY')))
|
||||
challenge = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY'))) # TODO deprecate
|
||||
csr_config = Column(Text()) # TODO deprecate
|
||||
status = Column(String(128))
|
||||
deleted = Column(Boolean, index=True)
|
||||
name = Column(String(128))
|
||||
@ -221,7 +220,8 @@ class Certificate(db.Model):
|
||||
date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)
|
||||
user_id = Column(Integer, ForeignKey('users.id'))
|
||||
authority_id = Column(Integer, ForeignKey('authorities.id'))
|
||||
accounts = relationship("Destination", secondary=certificate_destination_associations, backref='certificate')
|
||||
notifications = relationship("Notification", secondary=certificate_notification_associations, backref='certificate')
|
||||
destinations = relationship("Destination", secondary=certificate_destination_associations, backref='certificate')
|
||||
domains = relationship("Domain", secondary=certificate_associations, backref="certificate")
|
||||
elb_listeners = relationship("Listener", lazy='dynamic', backref='certificate')
|
||||
|
||||
@ -272,3 +272,10 @@ class Certificate(db.Model):
|
||||
|
||||
def as_dict(self):
|
||||
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
|
||||
|
||||
|
||||
@event.listens_for(Certificate.destinations, 'append')
|
||||
def update_destinations(target, value, initiator):
|
||||
destination_plugin = plugins.get(value.plugin_name)
|
||||
|
||||
destination_plugin.upload(target.body, target.private_key, target.chain, value.options)
|
||||
|
@ -15,6 +15,7 @@ from lemur.plugins.base import plugins
|
||||
from lemur.certificates.models import Certificate
|
||||
|
||||
from lemur.destinations.models import Destination
|
||||
from lemur.notifications.models import Notification
|
||||
from lemur.authorities.models import Authority
|
||||
|
||||
from lemur.roles.models import Role
|
||||
@ -75,7 +76,7 @@ def find_duplicates(cert_body):
|
||||
return Certificate.query.filter_by(body=cert_body).all()
|
||||
|
||||
|
||||
def update(cert_id, owner, active):
|
||||
def update(cert_id, owner, description, active, destinations, notifications):
|
||||
"""
|
||||
Updates a certificate.
|
||||
|
||||
@ -87,6 +88,11 @@ def update(cert_id, owner, active):
|
||||
cert = get(cert_id)
|
||||
cert.owner = owner
|
||||
cert.active = active
|
||||
cert.description = description
|
||||
|
||||
database.update_list(cert, 'notifications', Notification, notifications)
|
||||
database.update_list(cert, 'destinations', Destination, destinations)
|
||||
|
||||
return database.update(cert)
|
||||
|
||||
|
||||
@ -106,7 +112,8 @@ def mint(issuer_options):
|
||||
issuer_options['creator'] = g.user.email
|
||||
cert_body, cert_chain = issuer.create_certificate(csr, issuer_options)
|
||||
|
||||
cert = save_cert(cert_body, private_key, cert_chain, issuer_options.get('destinations'))
|
||||
cert = Certificate(cert_body, private_key, cert_chain)
|
||||
|
||||
cert.user = g.user
|
||||
cert.authority = authority
|
||||
database.update(cert)
|
||||
@ -139,43 +146,25 @@ def import_certificate(**kwargs):
|
||||
if kwargs.get('user'):
|
||||
cert.user = kwargs.get('user')
|
||||
|
||||
if kwargs.get('destination'):
|
||||
cert.destinations.append(kwargs.get('destination'))
|
||||
database.update_list(cert, 'notifications', Notification, kwargs.get('notifications'))
|
||||
|
||||
cert = database.create(cert)
|
||||
return cert
|
||||
|
||||
|
||||
def save_cert(cert_body, private_key, cert_chain, destinations):
|
||||
"""
|
||||
Determines if the certificate needs to be uploaded to AWS or other services.
|
||||
|
||||
:param cert_body:
|
||||
:param private_key:
|
||||
:param cert_chain:
|
||||
:param destinations:
|
||||
"""
|
||||
cert = Certificate(cert_body, private_key, cert_chain)
|
||||
|
||||
# we should save them to any destination that is requested
|
||||
for destination in destinations:
|
||||
destination_plugin = plugins.get(destination['plugin']['slug'])
|
||||
destination_plugin.upload(cert, private_key, cert_chain, destination['plugin']['pluginOptions'])
|
||||
|
||||
return cert
|
||||
|
||||
|
||||
def upload(**kwargs):
|
||||
"""
|
||||
Allows for pre-made certificates to be imported into Lemur.
|
||||
"""
|
||||
cert = save_cert(
|
||||
cert = Certificate(
|
||||
kwargs.get('public_cert'),
|
||||
kwargs.get('private_key'),
|
||||
kwargs.get('intermediate_cert'),
|
||||
kwargs.get('destinations')
|
||||
)
|
||||
|
||||
database.update_list(cert, 'destinations', Destination, kwargs.get('destinations'))
|
||||
database.update_list(cert, 'notifications', Notification, kwargs.get('notifications'))
|
||||
|
||||
cert.owner = kwargs['owner']
|
||||
cert = database.create(cert)
|
||||
g.user.certificates.append(cert)
|
||||
@ -189,10 +178,18 @@ def create(**kwargs):
|
||||
cert, private_key, cert_chain = mint(kwargs)
|
||||
|
||||
cert.owner = kwargs['owner']
|
||||
|
||||
database.update_list(cert, 'destinations', Destination, kwargs.get('destinations'))
|
||||
|
||||
database.create(cert)
|
||||
cert.description = kwargs['description']
|
||||
g.user.certificates.append(cert)
|
||||
database.update(g.user)
|
||||
|
||||
# do this after the certificate has already been created because if it fails to upload to the third party
|
||||
# we do not want to lose the certificate information.
|
||||
database.update_list(cert, 'notifications', Notification, kwargs.get('notifications'))
|
||||
database.update(cert)
|
||||
return cert
|
||||
|
||||
|
||||
@ -207,6 +204,7 @@ def render(args):
|
||||
|
||||
time_range = args.pop('time_range')
|
||||
destination_id = args.pop('destination_id')
|
||||
notification_id = args.pop('notification_id', None)
|
||||
show = args.pop('show')
|
||||
# owner = args.pop('owner')
|
||||
# creator = args.pop('creator') # TODO we should enabling filtering by owner
|
||||
@ -248,6 +246,9 @@ def render(args):
|
||||
if destination_id:
|
||||
query = query.filter(Certificate.destinations.any(Destination.id == destination_id))
|
||||
|
||||
if notification_id:
|
||||
query = query.filter(Certificate.notifications.any(Notification.id == notification_id))
|
||||
|
||||
if time_range:
|
||||
to = arrow.now().replace(weeks=+time_range).format('YYYY-MM-DD')
|
||||
now = arrow.now().format('YYYY-MM-DD')
|
||||
@ -284,11 +285,17 @@ def create_csr(csr_config):
|
||||
x509.BasicConstraints(ca=False, path_length=None), critical=True,
|
||||
)
|
||||
|
||||
# for k, v in csr_config.get('extensions', {}).items():
|
||||
# if k == 'subAltNames':
|
||||
# builder = builder.add_extension(
|
||||
# x509.SubjectAlternativeName([x509.DNSName(n) for n in v]), critical=True,
|
||||
# )
|
||||
for k, v in csr_config.get('extensions', {}).items():
|
||||
if k == 'subAltNames':
|
||||
# map types to their x509 objects
|
||||
general_names = []
|
||||
for name in v['names']:
|
||||
if name['nameType'] == 'DNSName':
|
||||
general_names.append(x509.DNSName(name['value']))
|
||||
|
||||
builder = builder.add_extension(
|
||||
x509.SubjectAlternativeName(general_names), critical=True
|
||||
)
|
||||
|
||||
# TODO support more CSR options, none of the authorities support these atm
|
||||
# builder.add_extension(
|
||||
|
@ -271,7 +271,7 @@ class CertificatesList(AuthenticatedResource):
|
||||
"""
|
||||
self.reqparse.add_argument('extensions', type=dict, location='json')
|
||||
self.reqparse.add_argument('destinations', type=list, default=[], location='json')
|
||||
self.reqparse.add_argument('elbs', type=list, location='json')
|
||||
self.reqparse.add_argument('notifications', type=list, default=[], location='json')
|
||||
self.reqparse.add_argument('owner', type=str, location='json')
|
||||
self.reqparse.add_argument('validityStart', type=str, location='json') # TODO validate
|
||||
self.reqparse.add_argument('validityEnd', type=str, location='json') # TODO validate
|
||||
@ -329,7 +329,8 @@ class CertificatesUpload(AuthenticatedResource):
|
||||
"publicCert": "---Begin Public...",
|
||||
"intermediateCert": "---Begin Public...",
|
||||
"privateKey": "---Begin Private..."
|
||||
"destinations": []
|
||||
"destinations": [],
|
||||
"notifications": []
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@ -371,6 +372,7 @@ class CertificatesUpload(AuthenticatedResource):
|
||||
self.reqparse.add_argument('owner', type=str, required=True, location='json')
|
||||
self.reqparse.add_argument('publicCert', type=pem_str, required=True, dest='public_cert', location='json')
|
||||
self.reqparse.add_argument('destinations', type=list, default=[], dest='destinations', location='json')
|
||||
self.reqparse.add_argument('notifications', type=list, default=[], dest='notifications', location='json')
|
||||
self.reqparse.add_argument('intermediateCert', type=pem_str, dest='intermediate_cert', location='json')
|
||||
self.reqparse.add_argument('privateKey', type=private_key_str, dest='private_key', location='json')
|
||||
|
||||
@ -523,6 +525,8 @@ class Certificates(AuthenticatedResource):
|
||||
{
|
||||
"owner": "jimbob@example.com",
|
||||
"active": false
|
||||
"notifications": [],
|
||||
"destinations": []
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@ -549,7 +553,7 @@ class Certificates(AuthenticatedResource):
|
||||
"notBefore": "2015-06-05T17:09:39",
|
||||
"notAfter": "2015-06-10T17:09:39",
|
||||
"cn": "example.com",
|
||||
"status": "unknown"
|
||||
"status": "unknown",
|
||||
}
|
||||
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
@ -558,6 +562,9 @@ class Certificates(AuthenticatedResource):
|
||||
"""
|
||||
self.reqparse.add_argument('active', type=bool, location='json')
|
||||
self.reqparse.add_argument('owner', type=str, location='json')
|
||||
self.reqparse.add_argument('description', type=str, location='json')
|
||||
self.reqparse.add_argument('destinations', type=list, default=[], location='json')
|
||||
self.reqparse.add_argument('notifications', type=list, default=[], location='json')
|
||||
args = self.reqparse.parse_args()
|
||||
|
||||
cert = service.get(certificate_id)
|
||||
@ -565,13 +572,96 @@ class Certificates(AuthenticatedResource):
|
||||
permission = UpdateCertificatePermission(certificate_id, hasattr(role, 'id'))
|
||||
|
||||
if permission.can():
|
||||
return service.update(certificate_id, args['owner'], args['active'])
|
||||
return service.update(
|
||||
certificate_id,
|
||||
args['owner'],
|
||||
args['description'],
|
||||
args['active'],
|
||||
args['destinations'],
|
||||
args['notifications']
|
||||
)
|
||||
|
||||
return dict(message='You are not authorized to update this certificate'), 403
|
||||
|
||||
|
||||
class NotificationCertificatesList(AuthenticatedResource):
|
||||
""" Defines the 'certificates' endpoint """
|
||||
def __init__(self):
|
||||
self.reqparse = reqparse.RequestParser()
|
||||
super(NotificationCertificatesList, self).__init__()
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def get(self, notification_id):
|
||||
"""
|
||||
.. http:get:: /notifications/1/certificates
|
||||
|
||||
The current list of certificates for a given notification
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /notifications/1/certificates HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "cert1",
|
||||
"description": "this is cert1",
|
||||
"bits": 2048,
|
||||
"deleted": false,
|
||||
"issuer": "ExampeInc.",
|
||||
"serial": "123450",
|
||||
"chain": "-----Begin ...",
|
||||
"body": "-----Begin ...",
|
||||
"san": true,
|
||||
"owner": 'bob@example.com",
|
||||
"active": true,
|
||||
"notBefore": "2015-06-05T17:09:39",
|
||||
"notAfter": "2015-06-10T17:09:39",
|
||||
"cn": "example.com",
|
||||
"status": "unknown"
|
||||
}
|
||||
]
|
||||
"total": 1
|
||||
}
|
||||
|
||||
:query sortBy: field to sort on
|
||||
:query sortDir: acs or desc
|
||||
:query page: int. default is 1
|
||||
:query filter: key value pair. format is k=v;
|
||||
:query limit: limit number. default is 10
|
||||
:reqheader Authorization: OAuth token to authenticate
|
||||
:statuscode 200: no error
|
||||
:statuscode 403: unauthenticated
|
||||
"""
|
||||
parser = paginated_parser.copy()
|
||||
parser.add_argument('timeRange', type=int, dest='time_range', location='args')
|
||||
parser.add_argument('owner', type=bool, location='args')
|
||||
parser.add_argument('id', type=str, location='args')
|
||||
parser.add_argument('active', type=bool, location='args')
|
||||
parser.add_argument('destinationId', type=int, dest="destination_id", location='args')
|
||||
parser.add_argument('creator', type=str, location='args')
|
||||
parser.add_argument('show', type=str, location='args')
|
||||
|
||||
args = parser.parse_args()
|
||||
args['notification_id'] = notification_id
|
||||
return service.render(args)
|
||||
|
||||
api.add_resource(CertificatesList, '/certificates', endpoint='certificates')
|
||||
api.add_resource(Certificates, '/certificates/<int:certificate_id>', endpoint='certificate')
|
||||
api.add_resource(CertificatesStats, '/certificates/stats', endpoint='certificateStats')
|
||||
api.add_resource(CertificatesUpload, '/certificates/upload', endpoint='certificateUpload')
|
||||
api.add_resource(CertificatePrivateKey, '/certificates/<int:certificate_id>/key', endpoint='privateKeyCertificates')
|
||||
api.add_resource(NotificationCertificatesList, '/notifications/<int:notification_id>/certificates', endpoint='notificationCertificates')
|
||||
|
Reference in New Issue
Block a user