diff --git a/lemur/authorities/service.py b/lemur/authorities/service.py index f5fe1262..a6066ab8 100644 --- a/lemur/authorities/service.py +++ b/lemur/authorities/service.py @@ -157,8 +157,9 @@ def get_authority_role(ca_name): else: for role in g.current_user.roles: if role.authority: - if role.authority.name == ca_name: - return role + for authority in role.authorities: + if authority.name == ca_name: + return role def render(args): diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py index 585227d4..ae9191cb 100644 --- a/lemur/certificates/models.py +++ b/lemur/certificates/models.py @@ -7,6 +7,8 @@ """ import datetime +from flask import current_app + from sqlalchemy import event, Integer, ForeignKey, String, DateTime, PassiveDefault, func, Column, Text, Boolean from sqlalchemy.orm import relationship @@ -22,11 +24,9 @@ from lemur.domains.models import Domain def get_or_increase_name(name): - count = Certificate.query.filter(Certificate.name == name).count() + count = Certificate.query.filter(Certificate.name.ilike('{0}%'.format(name))).count() - if count == 1: - return name + '-1' - elif count > 1: + if count >= 1: return name + '-' + str(count) return name @@ -77,6 +77,11 @@ class Certificate(db.Model): self.body = kwargs['body'] self.private_key = kwargs.get('private_key') self.chain = kwargs.get('chain') + self.destinations = kwargs.get('destinations', []) + self.notifications = kwargs.get('notifications', []) + self.description = kwargs.get('description') + self.roles = kwargs.get('roles', []) + self.replaces = kwargs.get('replacements', []) self.signing_algorithm = defaults.signing_algorithm(cert) self.bits = defaults.bitstrength(cert) self.issuer = defaults.issuer(cert) @@ -129,7 +134,10 @@ def update_destinations(target, value, initiator): :return: """ destination_plugin = plugins.get(value.plugin_name) - destination_plugin.upload(target.name, target.body, target.private_key, target.chain, value.options) + try: + destination_plugin.upload(target.name, target.body, target.private_key, target.chain, value.options) + except Exception as e: + current_app.logger.exception(e) @event.listens_for(Certificate.replaces, 'append') diff --git a/lemur/certificates/schemas.py b/lemur/certificates/schemas.py index 3bdef74d..d34c2fb1 100644 --- a/lemur/certificates/schemas.py +++ b/lemur/certificates/schemas.py @@ -7,7 +7,7 @@ """ from flask import current_app -from marshmallow import fields, validates_schema +from marshmallow import fields, validates_schema, post_load from marshmallow.exceptions import ValidationError from lemur.schemas import AssociatedAuthoritySchema, AssociatedDestinationSchema, AssociatedCertificateSchema, \ @@ -21,12 +21,26 @@ from lemur.users.schemas import UserNestedOutputSchema from lemur.common.schema import LemurInputSchema, LemurOutputSchema from lemur.common import validators +from lemur.notifications import service as notification_service -class CertificateInputSchema(LemurInputSchema): - name = fields.String() +class CertificateSchema(LemurInputSchema): owner = fields.Email(required=True) description = fields.String() + + @post_load + def default_notifications(self, data): + if not data['notifications']: + notification_name = "DEFAULT_{0}".format(data['owner'].split('@')[0].upper()) + data['notifications'] += notification_service.create_default_expiration_notifications(notification_name, [data['owner']]) + + notification_name = 'DEFAULT_SECURITY' + data['notifications'] += notification_service.create_default_expiration_notifications(notification_name, current_app.config.get('LEMUR_SECURITY_TEAM_EMAIL')) + return data + + +class CertificateInputSchema(CertificateSchema): + name = fields.String() common_name = fields.String(required=True, validate=validators.sensitive_domain) authority = fields.Nested(AssociatedAuthoritySchema, required=True) @@ -54,9 +68,7 @@ class CertificateInputSchema(LemurInputSchema): validators.dates(data) -class CertificateEditInputSchema(LemurInputSchema): - owner = fields.Email(required=True) - description = fields.String() +class CertificateEditInputSchema(CertificateSchema): active = fields.Boolean() destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True) notifications = fields.Nested(AssociatedNotificationSchema, missing=[], many=True) @@ -107,14 +119,12 @@ class CertificateOutputSchema(LemurOutputSchema): authority = fields.Nested(AuthorityNestedOutputSchema) -class CertificateUploadInputSchema(LemurInputSchema): +class CertificateUploadInputSchema(CertificateSchema): name = fields.String() - owner = fields.Email(required=True) - description = fields.String() active = fields.Boolean(missing=True) private_key = fields.String(validate=validators.private_key) - public_cert = fields.String(required=True, validate=validators.public_certificate) + body = fields.String(required=True, validate=validators.public_certificate) chain = fields.String(validate=validators.public_certificate) # TODO this could be multiple certificates destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True) diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 18904937..177cab84 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -103,59 +103,36 @@ def update(cert_id, owner, description, active, destinations, notifications, rep :param replaces: :return: """ - from lemur.notifications import service as notification_service cert = get(cert_id) cert.active = active cert.description = description - - # we might have to create new notifications if the owner changes - new_notifications = [] - # get existing names to remove - notification_name = "DEFAULT_{0}".format(cert.owner.split('@')[0].upper()) - for n in notifications: - if notification_name not in n.label: - new_notifications.append(n) - - notification_name = "DEFAULT_{0}".format(owner.split('@')[0].upper()) - new_notifications += notification_service.create_default_expiration_notifications(notification_name, owner) - - cert.notifications = new_notifications - - database.update_list(cert, 'destinations', Destination, destinations) - database.update_list(cert, 'replaces', Certificate, replaces) - + cert.destinations = destinations + cert.replaces = replaces cert.owner = owner return database.update(cert) -def mint(issuer_options): +def mint(**kwargs): """ Minting is slightly different for each authority. Support for multiple authorities is handled by individual plugins. :param issuer_options: """ - authority = issuer_options['authority'] + authority = kwargs['authority'] issuer = plugins.get(authority.plugin_name) # allow the CSR to be specified by the user - if not issuer_options.get('csr'): - csr, private_key = create_csr(issuer_options) + if not kwargs.get('csr'): + csr, private_key = create_csr(**kwargs) else: - csr = str(issuer_options.get('csr')) + csr = str(kwargs.get('csr')) private_key = None - issuer_options['creator'] = g.user.email - cert_body, cert_chain = issuer.create_certificate(csr, issuer_options) - - cert = Certificate(cert_body, private_key, cert_chain) - - cert.user = g.user - cert.authority = authority - database.update(cert) - return cert, private_key, cert_chain, + cert_body, cert_chain = issuer.create_certificate(csr, kwargs) + return cert_body, private_key, cert_chain, def import_certificate(**kwargs): @@ -172,69 +149,29 @@ def import_certificate(**kwargs): :param kwargs: """ from lemur.users import service as user_service - from lemur.notifications import service as notification_service - cert = Certificate(kwargs['public_certificate'], chain=kwargs['intermediate_certificate']) - # TODO future source plugins might have a better understanding of who the 'owner' is we should support this - cert.owner = kwargs.get('owner', current_app.config.get('LEMUR_SECURITY_TEAM_EMAIL')[0]) - cert.creator = kwargs.get('creator', user_service.get_by_email('lemur@nobody')) + if not kwargs.get('owner'): + kwargs['owner'] = current_app.config.get('LEMUR_SECURITY_TEAM_EMAIL')[0] - # NOTE existing certs may not follow our naming standard we will - # overwrite the generated name with the actual cert name - if kwargs.get('name'): - cert.name = kwargs.get('name') + if not kwargs.get('creator'): + kwargs['creator'] = user_service.get_by_email('lemur@nobody') - if kwargs.get('user'): - cert.user = kwargs.get('user') - - notification_name = 'DEFAULT_SECURITY' - notifications = notification_service.create_default_expiration_notifications(notification_name, current_app.config.get('LEMUR_SECURITY_TEAM_EMAIL')) - - if kwargs.get('replacements'): - database.update_list(cert, 'replaces', Certificate, kwargs['replacements']) - - cert.notifications = notifications - - cert = database.create(cert) - return cert + return upload(**kwargs) def upload(**kwargs): """ Allows for pre-made certificates to be imported into Lemur. """ - from lemur.notifications import service as notification_service - cert = Certificate( - kwargs.get('public_cert'), - kwargs.get('private_key'), - kwargs.get('intermediate_cert'), - ) + cert = Certificate(**kwargs) # we override the generated name if one is provided if kwargs.get('name'): cert.name = kwargs['name'] - cert.description = kwargs.get('description') - - cert.owner = kwargs['owner'] cert = database.create(cert) - g.user.certificates.append(cert) - database.update_list(cert, 'destinations', Destination, kwargs['destinations']) - database.update_list(cert, 'notifications', Notification, kwargs['notifications']) - database.update_list(cert, 'replaces', Certificate, kwargs['replacements']) - - # create default notifications for this certificate if none are provided - notifications = [] - if not kwargs.get('notifications'): - notification_name = "DEFAULT_{0}".format(cert.owner.split('@')[0].upper()) - notifications += notification_service.create_default_expiration_notifications(notification_name, [cert.owner]) - - notification_name = 'DEFAULT_SECURITY' - notifications += notification_service.create_default_expiration_notifications(notification_name, current_app.config.get('LEMUR_SECURITY_TEAM_EMAIL')) - cert.notifications = notifications - database.update(cert) return cert @@ -243,38 +180,21 @@ def create(**kwargs): """ Creates a new certificate. """ - from lemur.notifications import service as notification_service - cert, private_key, cert_chain = mint(kwargs) + kwargs['creator'] = g.user.email + cert_body, private_key, cert_chain = mint(**kwargs) + kwargs['body'] = cert_body + kwargs['private_key'] = private_key + kwargs['chain'] = cert_chain - cert.owner = kwargs['owner'] + cert = Certificate(**kwargs) # we override the generated name if one is provided if kwargs.get('name'): cert.name = kwargs['name'] - database.create(cert) - cert.description = kwargs.get('description') g.user.certificates.append(cert) - database.update(g.user) + database.commit() - # 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, 'destinations', Destination, kwargs['destinations']) - database.update_list(cert, 'replaces', Certificate, kwargs['replacements']) - database.update_list(cert, 'notifications', Notification, kwargs['notifications']) - - # create default notifications for this certificate if none are provided - notifications = cert.notifications - if not kwargs.get('notifications'): - notification_name = "DEFAULT_{0}".format(cert.owner.split('@')[0].upper()) - notifications += notification_service.create_default_expiration_notifications(notification_name, [cert.owner]) - - notification_name = 'DEFAULT_SECURITY' - notifications += notification_service.create_default_expiration_notifications(notification_name, - current_app.config.get('LEMUR_SECURITY_TEAM_EMAIL')) - cert.notifications = notifications - - database.update(cert) metrics.send('certificate_issued', 'counter', 1, metric_tags=dict(owner=cert.owner, issuer=cert.issuer)) return cert @@ -351,7 +271,7 @@ def render(args): return database.sort_and_page(query, Certificate, args) -def create_csr(csr_config): +def create_csr(**csr_config): """ Given a list of domains create the appropriate csr for those domains @@ -440,7 +360,7 @@ def create_csr(csr_config): ) # serialize our private key and CSR - pem = private_key.private_bytes( + private_key = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, # would like to use PKCS8 but AWS ELBs don't like it encryption_algorithm=serialization.NoEncryption() @@ -450,7 +370,7 @@ def create_csr(csr_config): encoding=serialization.Encoding.PEM ) - return csr, pem + return csr, private_key def stats(**kwargs): diff --git a/lemur/static/app/angular/certificates/certificate/upload.tpl.html b/lemur/static/app/angular/certificates/certificate/upload.tpl.html index e8497d06..ecd33305 100644 --- a/lemur/static/app/angular/certificates/certificate/upload.tpl.html +++ b/lemur/static/app/angular/certificates/certificate/upload.tpl.html @@ -45,7 +45,7 @@
Enter @@ -73,7 +73,7 @@