Merge pull request #27 from kevgliss/editCertificate
Initial support for notification plugins closes #8, closes #9, closes…
This commit is contained in:
commit
11a6294162
|
@ -1,6 +1,9 @@
|
||||||
Lemur
|
Lemur
|
||||||
*****
|
*****
|
||||||
|
|
||||||
|
[![Build Status](https://magnum.travis-ci.com/Netflix/lemur.svg?token=i5tcyx3z4N7pEVTxTeuP&branch=master)](https://magnum.travis-ci.com/Netflix/lemur)
|
||||||
|
|
||||||
|
|
||||||
Lemur manages SSL certificate creation. It provides a central portal for developers to issuer their own SSL certificates with 'sane' defaults.
|
Lemur manages SSL certificate creation. It provides a central portal for developers to issuer their own SSL certificates with 'sane' defaults.
|
||||||
|
|
||||||
It works on CPython 2.7. It is known
|
It works on CPython 2.7. It is known
|
||||||
|
|
|
@ -21,6 +21,7 @@ from lemur.listeners.views import mod as listeners_bp
|
||||||
from lemur.certificates.views import mod as certificates_bp
|
from lemur.certificates.views import mod as certificates_bp
|
||||||
from lemur.status.views import mod as status_bp
|
from lemur.status.views import mod as status_bp
|
||||||
from lemur.plugins.views import mod as plugins_bp
|
from lemur.plugins.views import mod as plugins_bp
|
||||||
|
from lemur.notifications.views import mod as notifications_bp
|
||||||
|
|
||||||
LEMUR_BLUEPRINTS = (
|
LEMUR_BLUEPRINTS = (
|
||||||
users_bp,
|
users_bp,
|
||||||
|
@ -34,6 +35,7 @@ LEMUR_BLUEPRINTS = (
|
||||||
certificates_bp,
|
certificates_bp,
|
||||||
status_bp,
|
status_bp,
|
||||||
plugins_bp,
|
plugins_bp,
|
||||||
|
notifications_bp,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ from lemur.authorities.models import Authority
|
||||||
from lemur.roles import service as role_service
|
from lemur.roles import service as role_service
|
||||||
|
|
||||||
from lemur.roles.models import Role
|
from lemur.roles.models import Role
|
||||||
import lemur.certificates.service as cert_service
|
from lemur.certificates.models import Certificate
|
||||||
|
|
||||||
from lemur.plugins.base import plugins
|
from lemur.plugins.base import plugins
|
||||||
|
|
||||||
|
@ -42,10 +42,6 @@ def create(kwargs):
|
||||||
"""
|
"""
|
||||||
Create a new authority.
|
Create a new authority.
|
||||||
|
|
||||||
:param name: name of the authority
|
|
||||||
:param roles: roles that are allowed to use this authority
|
|
||||||
:param options: available options for authority
|
|
||||||
:param description:
|
|
||||||
:rtype : Authority
|
:rtype : Authority
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
@ -55,7 +51,9 @@ def create(kwargs):
|
||||||
kwargs['creator'] = g.current_user.email
|
kwargs['creator'] = g.current_user.email
|
||||||
cert_body, intermediate, issuer_roles = issuer.create_authority(kwargs)
|
cert_body, intermediate, issuer_roles = issuer.create_authority(kwargs)
|
||||||
|
|
||||||
cert = cert_service.save_cert(cert_body, None, intermediate, [])
|
cert = Certificate(cert_body, chain=intermediate)
|
||||||
|
cert.owner = kwargs['ownerEmail']
|
||||||
|
cert.description = "This is the ROOT certificate for the {0} certificate authority".format(kwargs.get('caName'))
|
||||||
cert.user = g.current_user
|
cert.user = g.current_user
|
||||||
|
|
||||||
# we create and attach any roles that the issuer gives us
|
# we create and attach any roles that the issuer gives us
|
||||||
|
|
|
@ -13,16 +13,17 @@ from cryptography.hazmat.backends import default_backend
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
from sqlalchemy.orm import relationship
|
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 sqlalchemy_utils import EncryptedType
|
||||||
|
|
||||||
from lemur.database import db
|
from lemur.database import db
|
||||||
|
from lemur.plugins.base import plugins
|
||||||
|
|
||||||
from lemur.domains.models import Domain
|
from lemur.domains.models import Domain
|
||||||
|
|
||||||
from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE
|
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):
|
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())
|
delchars = ''.join(c for c in map(chr, range(256)) if not c.isalnum())
|
||||||
try:
|
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)
|
return issuer.translate(None, delchars)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error("Unable to get issuer! {0}".format(e))
|
current_app.logger.error("Unable to get issuer! {0}".format(e))
|
||||||
|
@ -203,8 +204,6 @@ class Certificate(db.Model):
|
||||||
owner = Column(String(128))
|
owner = Column(String(128))
|
||||||
body = Column(Text())
|
body = Column(Text())
|
||||||
private_key = Column(EncryptedType(String, os.environ.get('LEMUR_ENCRYPTION_KEY')))
|
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))
|
status = Column(String(128))
|
||||||
deleted = Column(Boolean, index=True)
|
deleted = Column(Boolean, index=True)
|
||||||
name = Column(String(128))
|
name = Column(String(128))
|
||||||
|
@ -221,7 +220,8 @@ class Certificate(db.Model):
|
||||||
date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)
|
date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)
|
||||||
user_id = Column(Integer, ForeignKey('users.id'))
|
user_id = Column(Integer, ForeignKey('users.id'))
|
||||||
authority_id = Column(Integer, ForeignKey('authorities.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")
|
domains = relationship("Domain", secondary=certificate_associations, backref="certificate")
|
||||||
elb_listeners = relationship("Listener", lazy='dynamic', backref='certificate')
|
elb_listeners = relationship("Listener", lazy='dynamic', backref='certificate')
|
||||||
|
|
||||||
|
@ -272,3 +272,10 @@ class Certificate(db.Model):
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
|
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.certificates.models import Certificate
|
||||||
|
|
||||||
from lemur.destinations.models import Destination
|
from lemur.destinations.models import Destination
|
||||||
|
from lemur.notifications.models import Notification
|
||||||
from lemur.authorities.models import Authority
|
from lemur.authorities.models import Authority
|
||||||
|
|
||||||
from lemur.roles.models import Role
|
from lemur.roles.models import Role
|
||||||
|
@ -75,7 +76,7 @@ def find_duplicates(cert_body):
|
||||||
return Certificate.query.filter_by(body=cert_body).all()
|
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.
|
Updates a certificate.
|
||||||
|
|
||||||
|
@ -87,6 +88,11 @@ def update(cert_id, owner, active):
|
||||||
cert = get(cert_id)
|
cert = get(cert_id)
|
||||||
cert.owner = owner
|
cert.owner = owner
|
||||||
cert.active = active
|
cert.active = active
|
||||||
|
cert.description = description
|
||||||
|
|
||||||
|
database.update_list(cert, 'notifications', Notification, notifications)
|
||||||
|
database.update_list(cert, 'destinations', Destination, destinations)
|
||||||
|
|
||||||
return database.update(cert)
|
return database.update(cert)
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,7 +112,8 @@ def mint(issuer_options):
|
||||||
issuer_options['creator'] = g.user.email
|
issuer_options['creator'] = g.user.email
|
||||||
cert_body, cert_chain = issuer.create_certificate(csr, issuer_options)
|
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.user = g.user
|
||||||
cert.authority = authority
|
cert.authority = authority
|
||||||
database.update(cert)
|
database.update(cert)
|
||||||
|
@ -139,43 +146,25 @@ def import_certificate(**kwargs):
|
||||||
if kwargs.get('user'):
|
if kwargs.get('user'):
|
||||||
cert.user = kwargs.get('user')
|
cert.user = kwargs.get('user')
|
||||||
|
|
||||||
if kwargs.get('destination'):
|
database.update_list(cert, 'notifications', Notification, kwargs.get('notifications'))
|
||||||
cert.destinations.append(kwargs.get('destination'))
|
|
||||||
|
|
||||||
cert = database.create(cert)
|
cert = database.create(cert)
|
||||||
return 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):
|
def upload(**kwargs):
|
||||||
"""
|
"""
|
||||||
Allows for pre-made certificates to be imported into Lemur.
|
Allows for pre-made certificates to be imported into Lemur.
|
||||||
"""
|
"""
|
||||||
cert = save_cert(
|
cert = Certificate(
|
||||||
kwargs.get('public_cert'),
|
kwargs.get('public_cert'),
|
||||||
kwargs.get('private_key'),
|
kwargs.get('private_key'),
|
||||||
kwargs.get('intermediate_cert'),
|
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.owner = kwargs['owner']
|
||||||
cert = database.create(cert)
|
cert = database.create(cert)
|
||||||
g.user.certificates.append(cert)
|
g.user.certificates.append(cert)
|
||||||
|
@ -189,10 +178,18 @@ def create(**kwargs):
|
||||||
cert, private_key, cert_chain = mint(kwargs)
|
cert, private_key, cert_chain = mint(kwargs)
|
||||||
|
|
||||||
cert.owner = kwargs['owner']
|
cert.owner = kwargs['owner']
|
||||||
|
|
||||||
|
database.update_list(cert, 'destinations', Destination, kwargs.get('destinations'))
|
||||||
|
|
||||||
database.create(cert)
|
database.create(cert)
|
||||||
cert.description = kwargs['description']
|
cert.description = kwargs['description']
|
||||||
g.user.certificates.append(cert)
|
g.user.certificates.append(cert)
|
||||||
database.update(g.user)
|
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
|
return cert
|
||||||
|
|
||||||
|
|
||||||
|
@ -207,6 +204,7 @@ def render(args):
|
||||||
|
|
||||||
time_range = args.pop('time_range')
|
time_range = args.pop('time_range')
|
||||||
destination_id = args.pop('destination_id')
|
destination_id = args.pop('destination_id')
|
||||||
|
notification_id = args.pop('notification_id', None)
|
||||||
show = args.pop('show')
|
show = args.pop('show')
|
||||||
# owner = args.pop('owner')
|
# owner = args.pop('owner')
|
||||||
# creator = args.pop('creator') # TODO we should enabling filtering by owner
|
# creator = args.pop('creator') # TODO we should enabling filtering by owner
|
||||||
|
@ -248,6 +246,9 @@ def render(args):
|
||||||
if destination_id:
|
if destination_id:
|
||||||
query = query.filter(Certificate.destinations.any(Destination.id == 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:
|
if time_range:
|
||||||
to = arrow.now().replace(weeks=+time_range).format('YYYY-MM-DD')
|
to = arrow.now().replace(weeks=+time_range).format('YYYY-MM-DD')
|
||||||
now = arrow.now().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,
|
x509.BasicConstraints(ca=False, path_length=None), critical=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# for k, v in csr_config.get('extensions', {}).items():
|
for k, v in csr_config.get('extensions', {}).items():
|
||||||
# if k == 'subAltNames':
|
if k == 'subAltNames':
|
||||||
# builder = builder.add_extension(
|
# map types to their x509 objects
|
||||||
# x509.SubjectAlternativeName([x509.DNSName(n) for n in v]), critical=True,
|
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
|
# TODO support more CSR options, none of the authorities support these atm
|
||||||
# builder.add_extension(
|
# builder.add_extension(
|
||||||
|
|
|
@ -271,7 +271,7 @@ class CertificatesList(AuthenticatedResource):
|
||||||
"""
|
"""
|
||||||
self.reqparse.add_argument('extensions', type=dict, location='json')
|
self.reqparse.add_argument('extensions', type=dict, location='json')
|
||||||
self.reqparse.add_argument('destinations', type=list, default=[], 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('owner', type=str, location='json')
|
||||||
self.reqparse.add_argument('validityStart', type=str, location='json') # TODO validate
|
self.reqparse.add_argument('validityStart', type=str, location='json') # TODO validate
|
||||||
self.reqparse.add_argument('validityEnd', 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...",
|
"publicCert": "---Begin Public...",
|
||||||
"intermediateCert": "---Begin Public...",
|
"intermediateCert": "---Begin Public...",
|
||||||
"privateKey": "---Begin Private..."
|
"privateKey": "---Begin Private..."
|
||||||
"destinations": []
|
"destinations": [],
|
||||||
|
"notifications": []
|
||||||
}
|
}
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
|
@ -371,6 +372,7 @@ class CertificatesUpload(AuthenticatedResource):
|
||||||
self.reqparse.add_argument('owner', type=str, required=True, location='json')
|
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('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('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('intermediateCert', type=pem_str, dest='intermediate_cert', location='json')
|
||||||
self.reqparse.add_argument('privateKey', type=private_key_str, dest='private_key', 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",
|
"owner": "jimbob@example.com",
|
||||||
"active": false
|
"active": false
|
||||||
|
"notifications": [],
|
||||||
|
"destinations": []
|
||||||
}
|
}
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
|
@ -549,7 +553,7 @@ class Certificates(AuthenticatedResource):
|
||||||
"notBefore": "2015-06-05T17:09:39",
|
"notBefore": "2015-06-05T17:09:39",
|
||||||
"notAfter": "2015-06-10T17:09:39",
|
"notAfter": "2015-06-10T17:09:39",
|
||||||
"cn": "example.com",
|
"cn": "example.com",
|
||||||
"status": "unknown"
|
"status": "unknown",
|
||||||
}
|
}
|
||||||
|
|
||||||
:reqheader Authorization: OAuth token to authenticate
|
: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('active', type=bool, location='json')
|
||||||
self.reqparse.add_argument('owner', type=str, 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()
|
args = self.reqparse.parse_args()
|
||||||
|
|
||||||
cert = service.get(certificate_id)
|
cert = service.get(certificate_id)
|
||||||
|
@ -565,13 +572,96 @@ class Certificates(AuthenticatedResource):
|
||||||
permission = UpdateCertificatePermission(certificate_id, hasattr(role, 'id'))
|
permission = UpdateCertificatePermission(certificate_id, hasattr(role, 'id'))
|
||||||
|
|
||||||
if permission.can():
|
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
|
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(CertificatesList, '/certificates', endpoint='certificates')
|
||||||
api.add_resource(Certificates, '/certificates/<int:certificate_id>', endpoint='certificate')
|
api.add_resource(Certificates, '/certificates/<int:certificate_id>', endpoint='certificate')
|
||||||
api.add_resource(CertificatesStats, '/certificates/stats', endpoint='certificateStats')
|
api.add_resource(CertificatesStats, '/certificates/stats', endpoint='certificateStats')
|
||||||
api.add_resource(CertificatesUpload, '/certificates/upload', endpoint='certificateUpload')
|
api.add_resource(CertificatesUpload, '/certificates/upload', endpoint='certificateUpload')
|
||||||
api.add_resource(CertificatePrivateKey, '/certificates/<int:certificate_id>/key', endpoint='privateKeyCertificates')
|
api.add_resource(CertificatePrivateKey, '/certificates/<int:certificate_id>/key', endpoint='privateKeyCertificates')
|
||||||
|
api.add_resource(NotificationCertificatesList, '/notifications/<int:notification_id>/certificates', endpoint='notificationCertificates')
|
||||||
|
|
|
@ -14,7 +14,6 @@ from lemur.auth.service import AuthenticatedResource
|
||||||
from lemur.auth.permissions import admin_permission
|
from lemur.auth.permissions import admin_permission
|
||||||
from lemur.common.utils import paginated_parser, marshal_items
|
from lemur.common.utils import paginated_parser, marshal_items
|
||||||
|
|
||||||
from lemur.plugins.views import FIELDS as PLUGIN_FIELDS
|
|
||||||
|
|
||||||
mod = Blueprint('destinations', __name__)
|
mod = Blueprint('destinations', __name__)
|
||||||
api = Api(mod)
|
api = Api(mod)
|
||||||
|
@ -22,7 +21,8 @@ api = Api(mod)
|
||||||
|
|
||||||
FIELDS = {
|
FIELDS = {
|
||||||
'description': fields.String,
|
'description': fields.String,
|
||||||
'plugin': fields.Nested(PLUGIN_FIELDS, attribute='plugin'),
|
'destinationOptions': fields.Raw(attribute='options'),
|
||||||
|
'pluginName': fields.String(attribute='plugin_name'),
|
||||||
'label': fields.String,
|
'label': fields.String,
|
||||||
'id': fields.Integer,
|
'id': fields.Integer,
|
||||||
}
|
}
|
||||||
|
@ -60,19 +60,23 @@ class DestinationsList(AuthenticatedResource):
|
||||||
{
|
{
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": 2,
|
"destinationOptions": [
|
||||||
"accountNumber": 222222222,
|
|
||||||
"label": "account2",
|
|
||||||
"comments": "this is a thing"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": 1,
|
"name": "accountNumber",
|
||||||
"accountNumber": 11111111111,
|
"required": true,
|
||||||
"label": "account1",
|
"value": 111111111112,
|
||||||
"comments": "this is a thing"
|
"helpMessage": "Must be a valid AWS account number!",
|
||||||
},
|
"validation": "/^[0-9]{12,12}$/",
|
||||||
]
|
"type": "int"
|
||||||
"total": 2
|
}
|
||||||
|
],
|
||||||
|
"pluginName": "aws-destination",
|
||||||
|
"id": 3,
|
||||||
|
"description": "test",
|
||||||
|
"label": "test"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
:query sortBy: field to sort on
|
:query sortBy: field to sort on
|
||||||
|
@ -104,9 +108,20 @@ class DestinationsList(AuthenticatedResource):
|
||||||
Accept: application/json, text/javascript
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
{
|
{
|
||||||
"accountNumber": 11111111111,
|
"destinationOptions": [
|
||||||
"label": "account1,
|
{
|
||||||
"comments": "this is a thing"
|
"name": "accountNumber",
|
||||||
|
"required": true,
|
||||||
|
"value": 111111111112,
|
||||||
|
"helpMessage": "Must be a valid AWS account number!",
|
||||||
|
"validation": "/^[0-9]{12,12}$/",
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pluginName": "aws-destination",
|
||||||
|
"id": 3,
|
||||||
|
"description": "test",
|
||||||
|
"label": "test"
|
||||||
}
|
}
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
|
@ -118,15 +133,24 @@ class DestinationsList(AuthenticatedResource):
|
||||||
Content-Type: text/javascript
|
Content-Type: text/javascript
|
||||||
|
|
||||||
{
|
{
|
||||||
"id": 1,
|
"destinationOptions": [
|
||||||
"accountNumber": 11111111111,
|
{
|
||||||
"label": "account1",
|
"name": "accountNumber",
|
||||||
"comments": "this is a thing"
|
"required": true,
|
||||||
|
"value": 111111111112,
|
||||||
|
"helpMessage": "Must be a valid AWS account number!",
|
||||||
|
"validation": "/^[0-9]{12,12}$/",
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pluginName": "aws-destination",
|
||||||
|
"id": 3,
|
||||||
|
"description": "test",
|
||||||
|
"label": "test"
|
||||||
}
|
}
|
||||||
|
|
||||||
:arg accountNumber: aws account number
|
|
||||||
:arg label: human readable account label
|
:arg label: human readable account label
|
||||||
:arg comments: some description about the account
|
:arg description: some description about the account
|
||||||
:reqheader Authorization: OAuth token to authenticate
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
"""
|
"""
|
||||||
|
@ -167,10 +191,20 @@ class Destinations(AuthenticatedResource):
|
||||||
Content-Type: text/javascript
|
Content-Type: text/javascript
|
||||||
|
|
||||||
{
|
{
|
||||||
"id": 1,
|
"destinationOptions": [
|
||||||
"accountNumber": 11111111111,
|
{
|
||||||
"label": "account1",
|
"name": "accountNumber",
|
||||||
"comments": "this is a thing"
|
"required": true,
|
||||||
|
"value": 111111111112,
|
||||||
|
"helpMessage": "Must be a valid AWS account number!",
|
||||||
|
"validation": "/^[0-9]{12,12}$/",
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pluginName": "aws-destination",
|
||||||
|
"id": 3,
|
||||||
|
"description": "test",
|
||||||
|
"label": "test"
|
||||||
}
|
}
|
||||||
|
|
||||||
:reqheader Authorization: OAuth token to authenticate
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
|
@ -194,6 +228,22 @@ class Destinations(AuthenticatedResource):
|
||||||
Host: example.com
|
Host: example.com
|
||||||
Accept: application/json, text/javascript
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"destinationOptions": [
|
||||||
|
{
|
||||||
|
"name": "accountNumber",
|
||||||
|
"required": true,
|
||||||
|
"value": 111111111112,
|
||||||
|
"helpMessage": "Must be a valid AWS account number!",
|
||||||
|
"validation": "/^[0-9]{12,12}$/",
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pluginName": "aws-destination",
|
||||||
|
"id": 3,
|
||||||
|
"description": "test",
|
||||||
|
"label": "test"
|
||||||
|
}
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
|
|
||||||
|
@ -204,24 +254,34 @@ class Destinations(AuthenticatedResource):
|
||||||
Content-Type: text/javascript
|
Content-Type: text/javascript
|
||||||
|
|
||||||
{
|
{
|
||||||
"id": 1,
|
"destinationOptions": [
|
||||||
"accountNumber": 11111111111,
|
{
|
||||||
"label": "labelChanged",
|
"name": "accountNumber",
|
||||||
"comments": "this is a thing"
|
"required": true,
|
||||||
|
"value": 111111111112,
|
||||||
|
"helpMessage": "Must be a valid AWS account number!",
|
||||||
|
"validation": "/^[0-9]{12,12}$/",
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pluginName": "aws-destination",
|
||||||
|
"id": 3,
|
||||||
|
"description": "test",
|
||||||
|
"label": "test"
|
||||||
}
|
}
|
||||||
|
|
||||||
:arg accountNumber: aws account number
|
:arg accountNumber: aws account number
|
||||||
:arg label: human readable account label
|
:arg label: human readable account label
|
||||||
:arg comments: some description about the account
|
:arg description: some description about the account
|
||||||
:reqheader Authorization: OAuth token to authenticate
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
"""
|
"""
|
||||||
self.reqparse.add_argument('label', type=str, location='json', required=True)
|
self.reqparse.add_argument('label', type=str, location='json', required=True)
|
||||||
self.reqparse.add_argument('pluginOptions', type=dict, location='json', required=True)
|
self.reqparse.add_argument('plugin', type=dict, location='json', required=True)
|
||||||
self.reqparse.add_argument('description', type=str, location='json')
|
self.reqparse.add_argument('description', type=str, location='json')
|
||||||
|
|
||||||
args = self.reqparse.parse_args()
|
args = self.reqparse.parse_args()
|
||||||
return service.update(destination_id, args['label'], args['options'], args['description'])
|
return service.update(destination_id, args['label'], args['plugin']['pluginOptions'], args['description'])
|
||||||
|
|
||||||
@admin_permission.require(http_exception=403)
|
@admin_permission.require(http_exception=403)
|
||||||
def delete(self, destination_id):
|
def delete(self, destination_id):
|
||||||
|
@ -257,6 +317,28 @@ class CertificateDestinations(AuthenticatedResource):
|
||||||
Vary: Accept
|
Vary: Accept
|
||||||
Content-Type: text/javascript
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"destinationOptions": [
|
||||||
|
{
|
||||||
|
"name": "accountNumber",
|
||||||
|
"required": true,
|
||||||
|
"value": 111111111112,
|
||||||
|
"helpMessage": "Must be a valid AWS account number!",
|
||||||
|
"validation": "/^[0-9]{12,12}$/",
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pluginName": "aws-destination",
|
||||||
|
"id": 3,
|
||||||
|
"description": "test",
|
||||||
|
"label": "test"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1
|
||||||
|
}
|
||||||
|
|
||||||
:query sortBy: field to sort on
|
:query sortBy: field to sort on
|
||||||
:query sortDir: acs or desc
|
:query sortDir: acs or desc
|
||||||
:query page: int. default is 1
|
:query page: int. default is 1
|
||||||
|
|
|
@ -32,6 +32,8 @@ from lemur.destinations.models import Destination # noqa
|
||||||
from lemur.domains.models import Domain # noqa
|
from lemur.domains.models import Domain # noqa
|
||||||
from lemur.elbs.models import ELB # noqa
|
from lemur.elbs.models import ELB # noqa
|
||||||
from lemur.listeners.models import Listener # noqa
|
from lemur.listeners.models import Listener # noqa
|
||||||
|
from lemur.notifications.models import Notification # noqa
|
||||||
|
|
||||||
|
|
||||||
manager = Manager(create_app)
|
manager = Manager(create_app)
|
||||||
manager.add_option('-c', '--config', dest='config')
|
manager.add_option('-c', '--config', dest='config')
|
||||||
|
|
|
@ -25,6 +25,12 @@ certificate_destination_associations = db.Table('certificate_destination_associa
|
||||||
ForeignKey('certificates.id', ondelete='cascade'))
|
ForeignKey('certificates.id', ondelete='cascade'))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
certificate_notification_associations = db.Table('certificate_notification_associations',
|
||||||
|
Column('notification_id', Integer,
|
||||||
|
ForeignKey('notifications.id', ondelete='cascade')),
|
||||||
|
Column('certificate_id', Integer,
|
||||||
|
ForeignKey('certificates.id', ondelete='cascade'))
|
||||||
|
)
|
||||||
roles_users = db.Table('roles_users',
|
roles_users = db.Table('roles_users',
|
||||||
Column('user_id', Integer, ForeignKey('users.id')),
|
Column('user_id', Integer, ForeignKey('users.id')),
|
||||||
Column('role_id', Integer, ForeignKey('roles.id'))
|
Column('role_id', Integer, ForeignKey('roles.id'))
|
||||||
|
|
|
@ -1,218 +0,0 @@
|
||||||
"""
|
|
||||||
.. module: lemur.notifications
|
|
||||||
:platform: Unix
|
|
||||||
|
|
||||||
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
|
||||||
:license: Apache, see LICENSE for more details.
|
|
||||||
|
|
||||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
|
||||||
|
|
||||||
"""
|
|
||||||
import ssl
|
|
||||||
import socket
|
|
||||||
|
|
||||||
import arrow
|
|
||||||
import boto.ses
|
|
||||||
|
|
||||||
from flask import current_app
|
|
||||||
from flask_mail import Message
|
|
||||||
|
|
||||||
from lemur import database
|
|
||||||
from lemur.certificates.models import Certificate
|
|
||||||
from lemur.domains.models import Domain
|
|
||||||
|
|
||||||
from lemur.templates.config import env
|
|
||||||
from lemur.extensions import smtp_mail
|
|
||||||
|
|
||||||
|
|
||||||
NOTIFICATION_INTERVALS = [30, 15, 5, 2]
|
|
||||||
|
|
||||||
|
|
||||||
def _get_domain_certificate(name):
|
|
||||||
"""
|
|
||||||
Fetch the SSL certificate currently hosted at a given domain (if any) and
|
|
||||||
compare it against our all of our know certificates to determine if a new
|
|
||||||
SSL certificate has already been deployed
|
|
||||||
|
|
||||||
:param name:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
query = database.session_query(Certificate)
|
|
||||||
try:
|
|
||||||
pub_key = ssl.get_server_certificate((name, 443))
|
|
||||||
return query.filter(Certificate.body == pub_key.strip()).first()
|
|
||||||
|
|
||||||
except socket.gaierror as e:
|
|
||||||
current_app.logger.info(str(e))
|
|
||||||
|
|
||||||
|
|
||||||
def _find_superseded(domains):
|
|
||||||
"""
|
|
||||||
Here we try to fetch any domain in the certificate to see if we can resolve it
|
|
||||||
and to try and see if it is currently serving the certificate we are
|
|
||||||
alerting on
|
|
||||||
|
|
||||||
:param domains:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
query = database.session_query(Certificate)
|
|
||||||
ss_list = []
|
|
||||||
for domain in domains:
|
|
||||||
dc = _get_domain_certificate(domain.name)
|
|
||||||
if dc:
|
|
||||||
ss_list.append(dc)
|
|
||||||
current_app.logger.info("Trying to resolve {0}".format(domain.name))
|
|
||||||
|
|
||||||
query = query.filter(Certificate.domains.any(Domain.name.in_([x.name for x in domains])))
|
|
||||||
query = query.filter(Certificate.active == True) # noqa
|
|
||||||
query = query.filter(Certificate.not_after >= arrow.utcnow().format('YYYY-MM-DD'))
|
|
||||||
ss_list.extend(query.all())
|
|
||||||
|
|
||||||
return ss_list
|
|
||||||
|
|
||||||
|
|
||||||
def send_expiration_notifications():
|
|
||||||
"""
|
|
||||||
This function will check for upcoming certificate expiration,
|
|
||||||
and send out notification emails at given intervals.
|
|
||||||
"""
|
|
||||||
notifications = 0
|
|
||||||
certs = _get_expiring_certs()
|
|
||||||
|
|
||||||
alerts = []
|
|
||||||
for cert in certs:
|
|
||||||
if _is_eligible_for_notifications(cert):
|
|
||||||
data = _get_message_data(cert)
|
|
||||||
recipients = _get_message_recipients(cert)
|
|
||||||
alerts.append((data, recipients))
|
|
||||||
|
|
||||||
roll_ups = _create_roll_ups(alerts)
|
|
||||||
|
|
||||||
for messages, recipients in roll_ups:
|
|
||||||
notifications += 1
|
|
||||||
send("Certificate Expiration", dict(messages=messages), 'event', recipients)
|
|
||||||
|
|
||||||
print notifications
|
|
||||||
current_app.logger.info("Lemur has sent {0} certification notifications".format(notifications))
|
|
||||||
|
|
||||||
|
|
||||||
def _get_message_recipients(cert):
|
|
||||||
"""
|
|
||||||
Determine who the recipients of the certificate expiration should be
|
|
||||||
|
|
||||||
:param cert:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
recipients = []
|
|
||||||
if current_app.config.get('SECURITY_TEAM_EMAIL'):
|
|
||||||
recipients.extend(current_app.config.get('SECURITY_TEAM_EMAIL'))
|
|
||||||
|
|
||||||
recipients.append(cert.owner)
|
|
||||||
|
|
||||||
if cert.user:
|
|
||||||
recipients.append(cert.user.email)
|
|
||||||
return list(set(recipients))
|
|
||||||
|
|
||||||
|
|
||||||
def _get_message_data(cert):
|
|
||||||
"""
|
|
||||||
Parse our the certification information needed for our notification
|
|
||||||
|
|
||||||
:param cert:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
cert_dict = cert.as_dict()
|
|
||||||
cert_dict['domains'] = [x .name for x in cert.domains]
|
|
||||||
cert_dict['superseded'] = list(set([x.name for x in _find_superseded(cert.domains) if cert.name != x]))
|
|
||||||
return cert_dict
|
|
||||||
|
|
||||||
|
|
||||||
def _get_expiring_certs(outlook=30):
|
|
||||||
"""
|
|
||||||
Find all the certificates expiring within a given outlook
|
|
||||||
|
|
||||||
:param outlook: int days to look forward
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
now = arrow.utcnow()
|
|
||||||
|
|
||||||
query = database.session_query(Certificate)
|
|
||||||
attr = Certificate.not_after
|
|
||||||
|
|
||||||
# get all certs expiring in the next 30 days
|
|
||||||
to = now.replace(days=+outlook).format('YYYY-MM-DD')
|
|
||||||
|
|
||||||
certs = []
|
|
||||||
for cert in query.filter(attr <= to).filter(attr >= now.format('YYYY-MM-DD')).all():
|
|
||||||
if _is_eligible_for_notifications(cert):
|
|
||||||
certs.append(cert)
|
|
||||||
return certs
|
|
||||||
|
|
||||||
|
|
||||||
def _is_eligible_for_notifications(cert, intervals=None):
|
|
||||||
"""
|
|
||||||
Determine if notifications for a given certificate should
|
|
||||||
currently be sent
|
|
||||||
|
|
||||||
:param cert:
|
|
||||||
:param intervals: list of days to alert on
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
now = arrow.utcnow()
|
|
||||||
if cert.active:
|
|
||||||
days = (cert.not_after - now.naive).days
|
|
||||||
|
|
||||||
if not intervals:
|
|
||||||
intervals = NOTIFICATION_INTERVALS
|
|
||||||
|
|
||||||
if days in intervals:
|
|
||||||
return cert
|
|
||||||
|
|
||||||
|
|
||||||
def _create_roll_ups(messages):
|
|
||||||
"""
|
|
||||||
Take all of the messages that should be sent and provide
|
|
||||||
a roll up to the same set if the recipients are the same
|
|
||||||
|
|
||||||
:param messages:
|
|
||||||
"""
|
|
||||||
roll_ups = []
|
|
||||||
for message_data, recipients in messages:
|
|
||||||
for m, r in roll_ups:
|
|
||||||
if r == recipients:
|
|
||||||
m.append(message_data)
|
|
||||||
current_app.logger.info(
|
|
||||||
"Sending email expiration alert about {0} to {1}".format(
|
|
||||||
message_data['name'], ",".join(recipients)))
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
roll_ups.append(([message_data], recipients))
|
|
||||||
return roll_ups
|
|
||||||
|
|
||||||
|
|
||||||
def send(subject, data, email_type, recipients):
|
|
||||||
"""
|
|
||||||
Configures all Lemur email messaging
|
|
||||||
|
|
||||||
:param subject:
|
|
||||||
:param data:
|
|
||||||
:param email_type:
|
|
||||||
:param recipients:
|
|
||||||
"""
|
|
||||||
# jinja template depending on type
|
|
||||||
template = env.get_template('{}.html'.format(email_type))
|
|
||||||
body = template.render(**data)
|
|
||||||
|
|
||||||
s_type = current_app.config.get("LEMUR_EMAIL_SENDER").lower()
|
|
||||||
if s_type == 'ses':
|
|
||||||
conn = boto.connect_ses()
|
|
||||||
conn.send_email(current_app.config.get("LEMUR_EMAIL"), subject, body, recipients, format='html')
|
|
||||||
|
|
||||||
elif s_type == 'smtp':
|
|
||||||
msg = Message(subject, recipients=recipients)
|
|
||||||
msg.body = "" # kinda a weird api for sending html emails
|
|
||||||
msg.html = body
|
|
||||||
smtp_mail.send(msg)
|
|
||||||
|
|
||||||
else:
|
|
||||||
current_app.logger.error("No mail carrier specified, notification emails were not able to be sent!")
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
"""
|
||||||
|
.. module: lemur.notifications.models
|
||||||
|
:platform: Unix
|
||||||
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
|
:license: Apache, see LICENSE for more details.
|
||||||
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
|
"""
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
from sqlalchemy import Integer, String, Column, Boolean, Text
|
||||||
|
from sqlalchemy_utils import JSONType
|
||||||
|
|
||||||
|
from lemur.database import db
|
||||||
|
from lemur.plugins.base import plugins
|
||||||
|
from lemur.models import certificate_notification_associations
|
||||||
|
|
||||||
|
|
||||||
|
class Notification(db.Model):
|
||||||
|
__tablename__ = 'notifications'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
label = Column(String(128))
|
||||||
|
description = Column(Text())
|
||||||
|
options = Column(JSONType)
|
||||||
|
active = Column(Boolean, default=True)
|
||||||
|
plugin_name = Column(String(32))
|
||||||
|
certificates = relationship("Certificate", secondary=certificate_notification_associations, passive_deletes=True, backref="notification", cascade='all,delete')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def plugin(self):
|
||||||
|
return plugins.get(self.plugin_name)
|
|
@ -0,0 +1,254 @@
|
||||||
|
"""
|
||||||
|
.. module: lemur.notifications
|
||||||
|
:platform: Unix
|
||||||
|
|
||||||
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
|
:license: Apache, see LICENSE for more details.
|
||||||
|
|
||||||
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
|
|
||||||
|
"""
|
||||||
|
import ssl
|
||||||
|
import socket
|
||||||
|
|
||||||
|
import arrow
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
from lemur import database
|
||||||
|
from lemur.domains.models import Domain
|
||||||
|
from lemur.notifications.models import Notification
|
||||||
|
from lemur.certificates.models import Certificate
|
||||||
|
|
||||||
|
from lemur.certificates import service as cert_service
|
||||||
|
|
||||||
|
from lemur.plugins.base import plugins
|
||||||
|
|
||||||
|
|
||||||
|
def _get_message_data(cert):
|
||||||
|
"""
|
||||||
|
Parse our the certification information needed for our notification
|
||||||
|
|
||||||
|
:param cert:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
cert_dict = cert.as_dict()
|
||||||
|
cert_dict['creator'] = cert.user.email
|
||||||
|
cert_dict['domains'] = [x .name for x in cert.domains]
|
||||||
|
cert_dict['superseded'] = list(set([x.name for x in find_superseded(cert.domains) if cert.name != x]))
|
||||||
|
return cert_dict
|
||||||
|
|
||||||
|
|
||||||
|
def _deduplicate(messages):
|
||||||
|
"""
|
||||||
|
Take all of the messages that should be sent and provide
|
||||||
|
a roll up to the same set if the recipients are the same
|
||||||
|
"""
|
||||||
|
roll_ups = []
|
||||||
|
for targets, data in messages:
|
||||||
|
for m, r in roll_ups:
|
||||||
|
if r == targets:
|
||||||
|
m.append(data)
|
||||||
|
current_app.logger.info(
|
||||||
|
"Sending expiration alert about {0} to {1}".format(
|
||||||
|
data['name'], ",".join(targets)))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
roll_ups.append(([data], targets, data.plugin_options))
|
||||||
|
return roll_ups
|
||||||
|
|
||||||
|
|
||||||
|
def send_expiration_notifications():
|
||||||
|
"""
|
||||||
|
This function will check for upcoming certificate expiration,
|
||||||
|
and send out notification emails at given intervals.
|
||||||
|
"""
|
||||||
|
notifications = 0
|
||||||
|
|
||||||
|
for plugin_name, notifications in database.get_all(Notification, 'active', field='status').group_by(Notification.plugin_name):
|
||||||
|
notifications += 1
|
||||||
|
|
||||||
|
messages = _deduplicate(notifications)
|
||||||
|
plugin = plugins.get(plugin_name)
|
||||||
|
|
||||||
|
for data, targets, options in messages:
|
||||||
|
plugin.send('expiration', data, targets, options)
|
||||||
|
|
||||||
|
current_app.logger.info("Lemur has sent {0} certification notifications".format(notifications))
|
||||||
|
|
||||||
|
|
||||||
|
def get_domain_certificate(name):
|
||||||
|
"""
|
||||||
|
Fetch the SSL certificate currently hosted at a given domain (if any) and
|
||||||
|
compare it against our all of our know certificates to determine if a new
|
||||||
|
SSL certificate has already been deployed
|
||||||
|
|
||||||
|
:param name:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
pub_key = ssl.get_server_certificate((name, 443))
|
||||||
|
return cert_service.find_duplicates(pub_key.strip())
|
||||||
|
except socket.gaierror as e:
|
||||||
|
current_app.logger.info(str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def find_superseded(domains):
|
||||||
|
"""
|
||||||
|
Here we try to fetch any domain in the certificate to see if we can resolve it
|
||||||
|
and to try and see if it is currently serving the certificate we are
|
||||||
|
alerting on.
|
||||||
|
|
||||||
|
:param domains:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
query = database.session_query(Certificate)
|
||||||
|
ss_list = []
|
||||||
|
for domain in domains:
|
||||||
|
dc = get_domain_certificate(domain.name)
|
||||||
|
if dc:
|
||||||
|
ss_list.append(dc)
|
||||||
|
current_app.logger.info("Trying to resolve {0}".format(domain.name))
|
||||||
|
|
||||||
|
query = query.filter(Certificate.domains.any(Domain.name.in_([x.name for x in domains])))
|
||||||
|
query = query.filter(Certificate.active == True) # noqa
|
||||||
|
query = query.filter(Certificate.not_after >= arrow.utcnow().format('YYYY-MM-DD'))
|
||||||
|
ss_list.extend(query.all())
|
||||||
|
|
||||||
|
return ss_list
|
||||||
|
|
||||||
|
|
||||||
|
def _is_eligible_for_notifications(cert):
|
||||||
|
"""
|
||||||
|
Determine if notifications for a given certificate should
|
||||||
|
currently be sent
|
||||||
|
|
||||||
|
:param cert:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
now = arrow.utcnow()
|
||||||
|
days = (cert.not_after - now.naive).days
|
||||||
|
|
||||||
|
for notification in cert.notifications:
|
||||||
|
interval = notification.options['interval']
|
||||||
|
unit = notification.options['unit']
|
||||||
|
if unit == 'weeks':
|
||||||
|
interval *= 7
|
||||||
|
|
||||||
|
elif unit == 'months':
|
||||||
|
interval *= 30
|
||||||
|
|
||||||
|
elif unit == 'days': # it's nice to be explicit about the base unit
|
||||||
|
pass
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception("Invalid base unit for expiration interval: {0}".format(unit))
|
||||||
|
|
||||||
|
if days == interval:
|
||||||
|
return cert
|
||||||
|
|
||||||
|
|
||||||
|
def create(label, plugin_name, options, description, certificates):
|
||||||
|
"""
|
||||||
|
Creates a new destination, that can then be used as a destination for certificates.
|
||||||
|
|
||||||
|
:param label: Notification common name
|
||||||
|
:param plugin_name:
|
||||||
|
:param options:
|
||||||
|
:param description:
|
||||||
|
:rtype : Notification
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
notification = Notification(label=label, options=options, plugin_name=plugin_name, description=description)
|
||||||
|
notification = database.update_list(notification, 'certificates', Certificate, certificates)
|
||||||
|
return database.create(notification)
|
||||||
|
|
||||||
|
|
||||||
|
def update(notification_id, label, options, description, certificates):
|
||||||
|
"""
|
||||||
|
Updates an existing destination.
|
||||||
|
|
||||||
|
:param label: Notification common name
|
||||||
|
:param options:
|
||||||
|
:param description:
|
||||||
|
:rtype : Notification
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
notification = get(notification_id)
|
||||||
|
|
||||||
|
notification.label = label
|
||||||
|
notification.options = options
|
||||||
|
notification.description = description
|
||||||
|
notification = database.update_list(notification, 'certificates', Certificate, certificates)
|
||||||
|
|
||||||
|
return database.update(notification)
|
||||||
|
|
||||||
|
|
||||||
|
def delete(notification_id):
|
||||||
|
"""
|
||||||
|
Deletes an notification.
|
||||||
|
|
||||||
|
:param notification_id: Lemur assigned ID
|
||||||
|
"""
|
||||||
|
database.delete(get(notification_id))
|
||||||
|
|
||||||
|
|
||||||
|
def get(notification_id):
|
||||||
|
"""
|
||||||
|
Retrieves an notification by it's lemur assigned ID.
|
||||||
|
|
||||||
|
:param notification_id: Lemur assigned ID
|
||||||
|
:rtype : Notification
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return database.get(Notification, notification_id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_by_label(label):
|
||||||
|
"""
|
||||||
|
Retrieves a notification by it's label
|
||||||
|
|
||||||
|
:param label:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return database.get(Notification, label, field='label')
|
||||||
|
|
||||||
|
|
||||||
|
def get_all():
|
||||||
|
"""
|
||||||
|
Retrieves all notification currently known by Lemur.
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
query = database.session_query(Notification)
|
||||||
|
return database.find_all(query, Notification, {}).all()
|
||||||
|
|
||||||
|
|
||||||
|
def render(args):
|
||||||
|
sort_by = args.pop('sort_by')
|
||||||
|
sort_dir = args.pop('sort_dir')
|
||||||
|
page = args.pop('page')
|
||||||
|
count = args.pop('count')
|
||||||
|
filt = args.pop('filter')
|
||||||
|
certificate_id = args.pop('certificate_id', None)
|
||||||
|
|
||||||
|
if certificate_id:
|
||||||
|
query = database.session_query(Notification).join(Certificate, Notification.certificate)
|
||||||
|
query = query.filter(Certificate.id == certificate_id)
|
||||||
|
else:
|
||||||
|
query = database.session_query(Notification)
|
||||||
|
|
||||||
|
if filt:
|
||||||
|
terms = filt.split(';')
|
||||||
|
if terms[0] == 'active' and terms[1] == 'false':
|
||||||
|
query = query.filter(Notification.active == False) # noqa
|
||||||
|
elif terms[0] == 'active' and terms[1] == 'true':
|
||||||
|
query = query.filter(Notification.active == True) # noqa
|
||||||
|
else:
|
||||||
|
query = database.filter(query, Notification, terms)
|
||||||
|
|
||||||
|
query = database.find_all(query, Notification, args)
|
||||||
|
|
||||||
|
if sort_by and sort_dir:
|
||||||
|
query = database.sort(query, Notification, sort_by, sort_dir)
|
||||||
|
|
||||||
|
return database.paginate(query, page, count)
|
|
@ -0,0 +1,455 @@
|
||||||
|
"""
|
||||||
|
.. module: lemur.notifications.views
|
||||||
|
:platform: Unix
|
||||||
|
:synopsis: This module contains all of the accounts view code.
|
||||||
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
|
:license: Apache, see LICENSE for more details.
|
||||||
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
|
"""
|
||||||
|
from flask import Blueprint
|
||||||
|
from flask.ext.restful import Api, reqparse, fields
|
||||||
|
from lemur.notifications import service
|
||||||
|
|
||||||
|
from lemur.auth.service import AuthenticatedResource
|
||||||
|
from lemur.common.utils import paginated_parser, marshal_items
|
||||||
|
|
||||||
|
|
||||||
|
mod = Blueprint('notifications', __name__)
|
||||||
|
api = Api(mod)
|
||||||
|
|
||||||
|
|
||||||
|
FIELDS = {
|
||||||
|
'description': fields.String,
|
||||||
|
'notificationOptions': fields.Raw(attribute='options'),
|
||||||
|
'pluginName': fields.String(attribute='plugin_name'),
|
||||||
|
'label': fields.String,
|
||||||
|
'active': fields.Boolean,
|
||||||
|
'id': fields.Integer,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationsList(AuthenticatedResource):
|
||||||
|
""" Defines the 'notifications' endpoint """
|
||||||
|
def __init__(self):
|
||||||
|
self.reqparse = reqparse.RequestParser()
|
||||||
|
super(NotificationsList, self).__init__()
|
||||||
|
|
||||||
|
@marshal_items(FIELDS)
|
||||||
|
def get(self):
|
||||||
|
"""
|
||||||
|
.. http:get:: /notifications
|
||||||
|
|
||||||
|
The current account list
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /notifications 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": [
|
||||||
|
{
|
||||||
|
"description": "An example",
|
||||||
|
"notificationOptions": [
|
||||||
|
{
|
||||||
|
"name": "interval",
|
||||||
|
"required": true,
|
||||||
|
"value": 5,
|
||||||
|
"helpMessage": "Number of days to be alert before expiration.",
|
||||||
|
"validation": "^\\d+$",
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"available": [
|
||||||
|
"days",
|
||||||
|
"weeks",
|
||||||
|
"months"
|
||||||
|
],
|
||||||
|
"name": "unit",
|
||||||
|
"required": true,
|
||||||
|
"value": "weeks",
|
||||||
|
"helpMessage": "Interval unit",
|
||||||
|
"validation": "",
|
||||||
|
"type": "select"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "recipients",
|
||||||
|
"required": true,
|
||||||
|
"value": "kglisson@netflix.com,example@netflix.com",
|
||||||
|
"helpMessage": "Comma delimited list of email addresses",
|
||||||
|
"validation": "^([\\w+-.%]+@[\\w-.]+\\.[A-Za-z]{2,4},?)+$",
|
||||||
|
"type": "str"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"label": "example",
|
||||||
|
"pluginName": "email-notification",
|
||||||
|
"active": true,
|
||||||
|
"id": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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
|
||||||
|
"""
|
||||||
|
parser = paginated_parser.copy()
|
||||||
|
args = parser.parse_args()
|
||||||
|
return service.render(args)
|
||||||
|
|
||||||
|
@marshal_items(FIELDS)
|
||||||
|
def post(self):
|
||||||
|
"""
|
||||||
|
.. http:post:: /notifications
|
||||||
|
|
||||||
|
Creates a new account
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
POST /notifications HTTP/1.1
|
||||||
|
Host: example.com
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"description": "a test",
|
||||||
|
"notificationOptions": [
|
||||||
|
{
|
||||||
|
"name": "interval",
|
||||||
|
"required": true,
|
||||||
|
"value": 5,
|
||||||
|
"helpMessage": "Number of days to be alert before expiration.",
|
||||||
|
"validation": "^\\d+$",
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"available": [
|
||||||
|
"days",
|
||||||
|
"weeks",
|
||||||
|
"months"
|
||||||
|
],
|
||||||
|
"name": "unit",
|
||||||
|
"required": true,
|
||||||
|
"value": "weeks",
|
||||||
|
"helpMessage": "Interval unit",
|
||||||
|
"validation": "",
|
||||||
|
"type": "select"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "recipients",
|
||||||
|
"required": true,
|
||||||
|
"value": "kglisson@netflix.com,example@netflix.com",
|
||||||
|
"helpMessage": "Comma delimited list of email addresses",
|
||||||
|
"validation": "^([\\w+-.%]+@[\\w-.]+\\.[A-Za-z]{2,4},?)+$",
|
||||||
|
"type": "str"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"label": "test",
|
||||||
|
"pluginName": "email-notification",
|
||||||
|
"active": true,
|
||||||
|
"id": 2
|
||||||
|
}
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"description": "a test",
|
||||||
|
"notificationOptions": [
|
||||||
|
{
|
||||||
|
"name": "interval",
|
||||||
|
"required": true,
|
||||||
|
"value": 5,
|
||||||
|
"helpMessage": "Number of days to be alert before expiration.",
|
||||||
|
"validation": "^\\d+$",
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"available": [
|
||||||
|
"days",
|
||||||
|
"weeks",
|
||||||
|
"months"
|
||||||
|
],
|
||||||
|
"name": "unit",
|
||||||
|
"required": true,
|
||||||
|
"value": "weeks",
|
||||||
|
"helpMessage": "Interval unit",
|
||||||
|
"validation": "",
|
||||||
|
"type": "select"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "recipients",
|
||||||
|
"required": true,
|
||||||
|
"value": "kglisson@netflix.com,example@netflix.com",
|
||||||
|
"helpMessage": "Comma delimited list of email addresses",
|
||||||
|
"validation": "^([\\w+-.%]+@[\\w-.]+\\.[A-Za-z]{2,4},?)+$",
|
||||||
|
"type": "str"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"label": "test",
|
||||||
|
"pluginName": "email-notification",
|
||||||
|
"active": true,
|
||||||
|
"id": 2
|
||||||
|
}
|
||||||
|
|
||||||
|
:arg accountNumber: aws account number
|
||||||
|
:arg label: human readable account label
|
||||||
|
:arg comments: some description about the account
|
||||||
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
|
:statuscode 200: no error
|
||||||
|
"""
|
||||||
|
self.reqparse.add_argument('label', type=str, location='json', required=True)
|
||||||
|
self.reqparse.add_argument('plugin', type=dict, location='json', required=True)
|
||||||
|
self.reqparse.add_argument('description', type=str, location='json')
|
||||||
|
self.reqparse.add_argument('certificates', type=list, default=[], location='json')
|
||||||
|
|
||||||
|
args = self.reqparse.parse_args()
|
||||||
|
return service.create(
|
||||||
|
args['label'],
|
||||||
|
args['plugin']['slug'],
|
||||||
|
args['plugin']['pluginOptions'],
|
||||||
|
args['description'],
|
||||||
|
args['certificates']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Notifications(AuthenticatedResource):
|
||||||
|
def __init__(self):
|
||||||
|
self.reqparse = reqparse.RequestParser()
|
||||||
|
super(Notifications, self).__init__()
|
||||||
|
|
||||||
|
@marshal_items(FIELDS)
|
||||||
|
def get(self, notification_id):
|
||||||
|
"""
|
||||||
|
.. http:get:: /notifications/1
|
||||||
|
|
||||||
|
Get a specific account
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /notifications/1 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
|
||||||
|
|
||||||
|
{
|
||||||
|
"description": "a test",
|
||||||
|
"notificationOptions": [
|
||||||
|
{
|
||||||
|
"name": "interval",
|
||||||
|
"required": true,
|
||||||
|
"value": 5,
|
||||||
|
"helpMessage": "Number of days to be alert before expiration.",
|
||||||
|
"validation": "^\\d+$",
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"available": [
|
||||||
|
"days",
|
||||||
|
"weeks",
|
||||||
|
"months"
|
||||||
|
],
|
||||||
|
"name": "unit",
|
||||||
|
"required": true,
|
||||||
|
"value": "weeks",
|
||||||
|
"helpMessage": "Interval unit",
|
||||||
|
"validation": "",
|
||||||
|
"type": "select"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "recipients",
|
||||||
|
"required": true,
|
||||||
|
"value": "kglisson@netflix.com,example@netflix.com",
|
||||||
|
"helpMessage": "Comma delimited list of email addresses",
|
||||||
|
"validation": "^([\\w+-.%]+@[\\w-.]+\\.[A-Za-z]{2,4},?)+$",
|
||||||
|
"type": "str"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"label": "test",
|
||||||
|
"pluginName": "email-notification",
|
||||||
|
"active": true,
|
||||||
|
"id": 2
|
||||||
|
}
|
||||||
|
|
||||||
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
|
:statuscode 200: no error
|
||||||
|
"""
|
||||||
|
return service.get(notification_id)
|
||||||
|
|
||||||
|
@marshal_items(FIELDS)
|
||||||
|
def put(self, notification_id):
|
||||||
|
"""
|
||||||
|
.. http:put:: /notifications/1
|
||||||
|
|
||||||
|
Updates an account
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
POST /notifications/1 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
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"accountNumber": 11111111111,
|
||||||
|
"label": "labelChanged",
|
||||||
|
"comments": "this is a thing"
|
||||||
|
}
|
||||||
|
|
||||||
|
:arg accountNumber: aws account number
|
||||||
|
:arg label: human readable account label
|
||||||
|
:arg comments: some description about the account
|
||||||
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
|
:statuscode 200: no error
|
||||||
|
"""
|
||||||
|
self.reqparse.add_argument('label', type=str, location='json', required=True)
|
||||||
|
self.reqparse.add_argument('plugin', type=dict, location='json', required=True)
|
||||||
|
self.reqparse.add_argument('certificates', type=list, default=[], location='json')
|
||||||
|
self.reqparse.add_argument('description', type=str, location='json')
|
||||||
|
|
||||||
|
args = self.reqparse.parse_args()
|
||||||
|
return service.update(
|
||||||
|
notification_id,
|
||||||
|
args['label'],
|
||||||
|
args['plugin']['pluginOptions'],
|
||||||
|
args['description'],
|
||||||
|
args['certificates']
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete(self, notification_id):
|
||||||
|
service.delete(notification_id)
|
||||||
|
return {'result': True}
|
||||||
|
|
||||||
|
|
||||||
|
class CertificateNotifications(AuthenticatedResource):
|
||||||
|
""" Defines the 'certificate/<int:certificate_id/notifications'' endpoint """
|
||||||
|
def __init__(self):
|
||||||
|
super(CertificateNotifications, self).__init__()
|
||||||
|
|
||||||
|
@marshal_items(FIELDS)
|
||||||
|
def get(self, certificate_id):
|
||||||
|
"""
|
||||||
|
.. http:get:: /certificates/1/notifications
|
||||||
|
|
||||||
|
The current account list for a given certificates
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /certificates/1/notifications 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": [
|
||||||
|
{
|
||||||
|
"description": "An example",
|
||||||
|
"notificationOptions": [
|
||||||
|
{
|
||||||
|
"name": "interval",
|
||||||
|
"required": true,
|
||||||
|
"value": 555,
|
||||||
|
"helpMessage": "Number of days to be alert before expiration.",
|
||||||
|
"validation": "^\\d+$",
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"available": [
|
||||||
|
"days",
|
||||||
|
"weeks",
|
||||||
|
"months"
|
||||||
|
],
|
||||||
|
"name": "unit",
|
||||||
|
"required": true,
|
||||||
|
"value": "weeks",
|
||||||
|
"helpMessage": "Interval unit",
|
||||||
|
"validation": "",
|
||||||
|
"type": "select"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "recipients",
|
||||||
|
"required": true,
|
||||||
|
"value": "kglisson@netflix.com,example@netflix.com",
|
||||||
|
"helpMessage": "Comma delimited list of email addresses",
|
||||||
|
"validation": "^([\\w+-.%]+@[\\w-.]+\\.[A-Za-z]{2,4},?)+$",
|
||||||
|
"type": "str"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"label": "example",
|
||||||
|
"pluginName": "email-notification",
|
||||||
|
"active": true,
|
||||||
|
"id": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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
|
||||||
|
"""
|
||||||
|
parser = paginated_parser.copy()
|
||||||
|
args = parser.parse_args()
|
||||||
|
args['certificate_id'] = certificate_id
|
||||||
|
return service.render(args)
|
||||||
|
|
||||||
|
|
||||||
|
api.add_resource(NotificationsList, '/notifications', endpoint='notifications')
|
||||||
|
api.add_resource(Notifications, '/notifications/<int:notification_id>', endpoint='notification')
|
||||||
|
api.add_resource(CertificateNotifications, '/certificates/<int:certificate_id>/notifications',
|
||||||
|
endpoint='certificateNotifications')
|
|
@ -1,3 +1,4 @@
|
||||||
from .destination import DestinationPlugin # noqa
|
from .destination import DestinationPlugin # noqa
|
||||||
from .issuer import IssuerPlugin # noqa
|
from .issuer import IssuerPlugin # noqa
|
||||||
from .source import SourcePlugin # noqa
|
from .source import SourcePlugin # noqa
|
||||||
|
from .notification import NotificationPlugin, ExpirationNotificationPlugin # noqa
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
"""
|
||||||
|
.. module: lemur.bases.notification
|
||||||
|
:platform: Unix
|
||||||
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
|
:license: Apache, see LICENSE for more details.
|
||||||
|
|
||||||
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
|
"""
|
||||||
|
from lemur.plugins.base import Plugin
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationPlugin(Plugin):
|
||||||
|
"""
|
||||||
|
This is the base class from which all of the supported
|
||||||
|
issuers will inherit from.
|
||||||
|
"""
|
||||||
|
type = 'notification'
|
||||||
|
|
||||||
|
def send(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class ExpirationNotificationPlugin(NotificationPlugin):
|
||||||
|
"""
|
||||||
|
This is the base class for all expiration notification plugins.
|
||||||
|
It contains some default options that are needed for all expiration
|
||||||
|
notification plugins.
|
||||||
|
"""
|
||||||
|
default_options = [
|
||||||
|
{
|
||||||
|
'name': 'interval',
|
||||||
|
'type': 'int',
|
||||||
|
'required': True,
|
||||||
|
'validation': '^\d+$',
|
||||||
|
'helpMessage': 'Number of days to be alert before expiration.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'unit',
|
||||||
|
'type': 'select',
|
||||||
|
'required': True,
|
||||||
|
'validation': '',
|
||||||
|
'available': ['days', 'weeks', 'months'],
|
||||||
|
'helpMessage': 'Interval unit',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def options(self):
|
||||||
|
return list(self.default_options) + self.additional_options
|
||||||
|
|
||||||
|
def send(self):
|
||||||
|
raise NotImplementedError
|
|
@ -61,7 +61,7 @@ class AWSSourcePlugin(SourcePlugin):
|
||||||
options = [
|
options = [
|
||||||
{
|
{
|
||||||
'name': 'accountNumber',
|
'name': 'accountNumber',
|
||||||
'type': 'int',
|
'type': 'str',
|
||||||
'required': True,
|
'required': True,
|
||||||
'validation': '/^[0-9]{12,12}$/',
|
'validation': '/^[0-9]{12,12}$/',
|
||||||
'helpMessage': 'Must be a valid AWS account number!',
|
'helpMessage': 'Must be a valid AWS account number!',
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
try:
|
||||||
|
VERSION = __import__('pkg_resources') \
|
||||||
|
.get_distribution(__name__).version
|
||||||
|
except Exception, e:
|
||||||
|
VERSION = 'unknown'
|
|
@ -0,0 +1,76 @@
|
||||||
|
"""
|
||||||
|
.. module: lemur.plugins.lemur_aws.aws
|
||||||
|
:platform: Unix
|
||||||
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
|
:license: Apache, see LICENSE for more details.
|
||||||
|
|
||||||
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
|
"""
|
||||||
|
import boto.ses
|
||||||
|
from flask import current_app
|
||||||
|
from flask_mail import Message
|
||||||
|
|
||||||
|
from lemur.extensions import smtp_mail
|
||||||
|
|
||||||
|
from lemur.plugins.bases import ExpirationNotificationPlugin
|
||||||
|
from lemur.plugins import lemur_email as email
|
||||||
|
|
||||||
|
|
||||||
|
from lemur.plugins.lemur_email.templates.config import env
|
||||||
|
|
||||||
|
|
||||||
|
def find_value(name, options):
|
||||||
|
for o in options:
|
||||||
|
if o.get(name):
|
||||||
|
return o['value']
|
||||||
|
|
||||||
|
|
||||||
|
class EmailNotificationPlugin(ExpirationNotificationPlugin):
|
||||||
|
title = 'Email'
|
||||||
|
slug = 'email-notification'
|
||||||
|
description = 'Sends expiration email notifications'
|
||||||
|
version = email.VERSION
|
||||||
|
|
||||||
|
author = 'Kevin Glisson'
|
||||||
|
author_url = 'https://github.com/netflix/lemur'
|
||||||
|
|
||||||
|
additional_options = [
|
||||||
|
{
|
||||||
|
'name': 'recipients',
|
||||||
|
'type': 'str',
|
||||||
|
'required': True,
|
||||||
|
'validation': '^([\w+-.%]+@[\w-.]+\.[A-Za-z]{2,4},?)+$',
|
||||||
|
'helpMessage': 'Comma delimited list of email addresses',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def send(event_type, message, targets, options, **kwargs):
|
||||||
|
"""
|
||||||
|
Configures all Lemur email messaging
|
||||||
|
|
||||||
|
:param event_type:
|
||||||
|
:param options:
|
||||||
|
"""
|
||||||
|
subject = 'Notification: Lemur'
|
||||||
|
|
||||||
|
if event_type == 'expiration':
|
||||||
|
subject = 'Notification: SSL Certificate Expiration '
|
||||||
|
|
||||||
|
# jinja template depending on type
|
||||||
|
template = env.get_template('{}.html'.format(event_type))
|
||||||
|
body = template.render(**kwargs)
|
||||||
|
|
||||||
|
s_type = current_app.config.get("LEMUR_EMAIL_SENDER").lower()
|
||||||
|
if s_type == 'ses':
|
||||||
|
conn = boto.connect_ses()
|
||||||
|
conn.send_email(current_app.config.get("LEMUR_EMAIL"), subject, body, targets, format='html')
|
||||||
|
|
||||||
|
elif s_type == 'smtp':
|
||||||
|
msg = Message(subject, recipients=targets)
|
||||||
|
msg.body = "" # kinda a weird api for sending html emails
|
||||||
|
msg.html = body
|
||||||
|
smtp_mail.send(msg)
|
||||||
|
|
||||||
|
else:
|
||||||
|
current_app.logger.error("No mail carrier specified, notification emails were not able to be sent!")
|
|
@ -72,6 +72,12 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ message.owner }}</td>
|
<td>{{ message.owner }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Creator</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ message.creator }}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>Not Before</strong></td>
|
<td><strong>Not Before</strong></td>
|
||||||
</tr>
|
</tr>
|
|
@ -65,13 +65,13 @@ class PluginsList(AuthenticatedResource):
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"accountNumber": 222222222,
|
"accountNumber": 222222222,
|
||||||
"label": "account2",
|
"label": "account2",
|
||||||
"comments": "this is a thing"
|
"description": "this is a thing"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"accountNumber": 11111111111,
|
"accountNumber": 11111111111,
|
||||||
"label": "account1",
|
"label": "account1",
|
||||||
"comments": "this is a thing"
|
"description": "this is a thing"
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"total": 2
|
"total": 2
|
||||||
|
@ -80,19 +80,24 @@ class PluginsList(AuthenticatedResource):
|
||||||
:reqheader Authorization: OAuth token to authenticate
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
"""
|
"""
|
||||||
|
self.reqparse.add_argument('type', type=str, location='args')
|
||||||
|
args = self.reqparse.parse_args()
|
||||||
|
|
||||||
|
if args['type']:
|
||||||
|
return list(plugins.all(plugin_type=args['type']))
|
||||||
|
|
||||||
return plugins.all()
|
return plugins.all()
|
||||||
|
|
||||||
|
|
||||||
class PluginsTypeList(AuthenticatedResource):
|
class Plugins(AuthenticatedResource):
|
||||||
""" Defines the 'plugins' endpoint """
|
""" Defines the the 'plugins' endpoint """
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.reqparse = reqparse.RequestParser()
|
super(Plugins, self).__init__()
|
||||||
super(PluginsTypeList, self).__init__()
|
|
||||||
|
|
||||||
@marshal_items(FIELDS)
|
@marshal_items(FIELDS)
|
||||||
def get(self, plugin_type):
|
def get(self, name):
|
||||||
"""
|
"""
|
||||||
.. http:get:: /plugins/issuer
|
.. http:get:: /plugins/<name>
|
||||||
|
|
||||||
The current plugin list
|
The current plugin list
|
||||||
|
|
||||||
|
@ -100,7 +105,7 @@ class PluginsTypeList(AuthenticatedResource):
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
|
|
||||||
GET /plugins/issuer HTTP/1.1
|
GET /plugins HTTP/1.1
|
||||||
Host: example.com
|
Host: example.com
|
||||||
Accept: application/json, text/javascript
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
@ -113,27 +118,16 @@ class PluginsTypeList(AuthenticatedResource):
|
||||||
Content-Type: text/javascript
|
Content-Type: text/javascript
|
||||||
|
|
||||||
{
|
{
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"accountNumber": 222222222,
|
"accountNumber": 222222222,
|
||||||
"label": "account2",
|
"label": "account2",
|
||||||
"comments": "this is a thing"
|
"description": "this is a thing"
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"accountNumber": 11111111111,
|
|
||||||
"label": "account1",
|
|
||||||
"comments": "this is a thing"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
"total": 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:reqheader Authorization: OAuth token to authenticate
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
"""
|
"""
|
||||||
return list(plugins.all(plugin_type=plugin_type))
|
return plugins.get(name)
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(PluginsList, '/plugins', endpoint='plugins')
|
api.add_resource(PluginsList, '/plugins', endpoint='plugins')
|
||||||
api.add_resource(PluginsTypeList, '/plugins/<plugin_type>', endpoint='pluginType')
|
api.add_resource(Plugins, '/plugins/<name>', endpoint='pluginName')
|
||||||
|
|
|
@ -2,15 +2,29 @@
|
||||||
|
|
||||||
angular.module('lemur')
|
angular.module('lemur')
|
||||||
|
|
||||||
.controller('AuthorityEditController', function ($scope, $routeParams, AuthorityApi, AuthorityService, RoleService){
|
.controller('AuthorityEditController', function ($scope, $modalInstance, AuthorityApi, AuthorityService, RoleService, editId){
|
||||||
AuthorityApi.get($routeParams.id).then(function (authority) {
|
AuthorityApi.get(editId).then(function (authority) {
|
||||||
AuthorityService.getRoles(authority);
|
AuthorityService.getRoles(authority);
|
||||||
$scope.authority = authority;
|
$scope.authority = authority;
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.authorityService = AuthorityService;
|
$scope.authorityService = AuthorityService;
|
||||||
$scope.save = AuthorityService.update;
|
|
||||||
$scope.roleService = RoleService;
|
$scope.roleService = RoleService;
|
||||||
|
|
||||||
|
$scope.save = function (authority) {
|
||||||
|
AuthorityService.update(authority).then(
|
||||||
|
function () {
|
||||||
|
$modalInstance.close();
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.cancel = function () {
|
||||||
|
$modalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
})
|
})
|
||||||
|
|
||||||
.controller('AuthorityCreateController', function ($scope, $modalInstance, AuthorityService, LemurRestangular, RoleService, PluginService, WizardHandler) {
|
.controller('AuthorityCreateController', function ($scope, $modalInstance, AuthorityService, LemurRestangular, RoleService, PluginService, WizardHandler) {
|
||||||
|
@ -25,7 +39,7 @@ angular.module('lemur')
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
PluginService.get('issuer').then(function (plugins) {
|
PluginService.getByType('issuer').then(function (plugins) {
|
||||||
$scope.plugins = plugins;
|
$scope.plugins = plugins;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
<h2 class="featurette-heading">Edit</span> Authority <span class="text-muted"><small>Chain of command
|
<div class="modal-header">
|
||||||
</small></span></h2>
|
<div class="modal-title">
|
||||||
<div class="panel panel-default">
|
<div class="modal-header">Edit Authority <span class="text-muted"><small>chain of command!</small></span></div>
|
||||||
<div class="panel-heading">
|
|
||||||
<a href="#/authorities" class="btn btn-danger pull-right">Cancel</a>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="modal-body">
|
||||||
<form name="createForm" class="form-horizontal" role="form" novalidate>
|
<form name="createForm" class="form-horizontal" role="form" novalidate>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-sm-2">
|
<label class="control-label col-sm-2">
|
||||||
|
@ -37,8 +34,8 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-footer">
|
<div class="modal-footer">
|
||||||
<button ng-click="save(authority)" class="btn btn-success pull-right">Save</button>
|
<button ng-click="save(authority)" type="submit" ng-disabled="createForm.$invalid" class="btn btn-primary">Save</button>
|
||||||
<div class="clearfix"></div>
|
<button ng-click="cancel()" class="btn btn-danger">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -65,6 +65,19 @@ angular.module('lemur')
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AuthorityService.findActiveAuthorityByName = function (filterValue) {
|
||||||
|
return AuthorityApi.getList({'filter[name]': filterValue})
|
||||||
|
.then(function (authorities) {
|
||||||
|
var activeAuthorities = [];
|
||||||
|
_.each(authorities, function (authority) {
|
||||||
|
if (authority.active) {
|
||||||
|
activeAuthorities.push(authority);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return activeAuthorities;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
AuthorityService.create = function (authority) {
|
AuthorityService.create = function (authority) {
|
||||||
authority.attachSubAltName();
|
authority.attachSubAltName();
|
||||||
return AuthorityApi.post(authority).then(
|
return AuthorityApi.post(authority).then(
|
||||||
|
@ -86,7 +99,7 @@ angular.module('lemur')
|
||||||
};
|
};
|
||||||
|
|
||||||
AuthorityService.update = function (authority) {
|
AuthorityService.update = function (authority) {
|
||||||
authority.put().then(
|
return authority.put().then(
|
||||||
function () {
|
function () {
|
||||||
toaster.pop({
|
toaster.pop({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
@ -105,13 +118,13 @@ angular.module('lemur')
|
||||||
};
|
};
|
||||||
|
|
||||||
AuthorityService.getRoles = function (authority) {
|
AuthorityService.getRoles = function (authority) {
|
||||||
authority.getList('roles').then(function (roles) {
|
return authority.getList('roles').then(function (roles) {
|
||||||
authority.roles = roles;
|
authority.roles = roles;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
AuthorityService.updateActive = function (authority) {
|
AuthorityService.updateActive = function (authority) {
|
||||||
authority.put().then(
|
return authority.put().then(
|
||||||
function () {
|
function () {
|
||||||
toaster.pop({
|
toaster.pop({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
|
|
@ -46,7 +46,7 @@ angular.module('lemur')
|
||||||
$scope.edit = function (authorityId) {
|
$scope.edit = function (authorityId) {
|
||||||
var modalInstance = $modal.open({
|
var modalInstance = $modal.open({
|
||||||
animation: true,
|
animation: true,
|
||||||
templateUrl: '/angular/authorities/authority/authorityWizard.tpl.html',
|
templateUrl: '/angular/authorities/authority/authorityEdit.tpl.html',
|
||||||
controller: 'AuthorityEditController',
|
controller: 'AuthorityEditController',
|
||||||
size: 'lg',
|
size: 'lg',
|
||||||
resolve: {
|
resolve: {
|
||||||
|
|
|
@ -1,17 +1,28 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('lemur')
|
angular.module('lemur')
|
||||||
.controller('CertificateEditController', function ($scope, $routeParams, CertificateApi, CertificateService, MomentService) {
|
.controller('CertificateEditController', function ($scope, $modalInstance, CertificateApi, CertificateService, DestinationService, NotificationService, editId) {
|
||||||
CertificateApi.get($routeParams.id).then(function (certificate) {
|
CertificateApi.get(editId).then(function (certificate) {
|
||||||
|
CertificateService.getNotifications(certificate);
|
||||||
|
CertificateService.getDestinations(certificate);
|
||||||
$scope.certificate = certificate;
|
$scope.certificate = certificate;
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.momentService = MomentService;
|
$scope.cancel = function () {
|
||||||
$scope.save = CertificateService.update;
|
$modalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.save = function (certificate) {
|
||||||
|
CertificateService.update(certificate).then(function () {
|
||||||
|
$modalInstance.close();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.destinationService = DestinationService;
|
||||||
|
$scope.notificationService = NotificationService;
|
||||||
})
|
})
|
||||||
|
|
||||||
.controller('CertificateCreateController', function ($scope, $modalInstance, CertificateApi, CertificateService, DestinationService, ELBService, AuthorityService, PluginService, MomentService, WizardHandler, LemurRestangular) {
|
.controller('CertificateCreateController', function ($scope, $modalInstance, CertificateApi, CertificateService, DestinationService, ELBService, AuthorityService, PluginService, MomentService, WizardHandler, LemurRestangular, NotificationService) {
|
||||||
$scope.certificate = LemurRestangular.restangularizeElement(null, {}, 'certificates');
|
$scope.certificate = LemurRestangular.restangularizeElement(null, {}, 'certificates');
|
||||||
|
|
||||||
$scope.create = function (certificate) {
|
$scope.create = function (certificate) {
|
||||||
|
@ -77,11 +88,12 @@ angular.module('lemur')
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
PluginService.get('destination').then(function (plugins) {
|
PluginService.getByType('destination').then(function (plugins) {
|
||||||
$scope.plugins = plugins;
|
$scope.plugins = plugins;
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.elbService = ELBService;
|
$scope.elbService = ELBService;
|
||||||
$scope.authorityService = AuthorityService;
|
$scope.authorityService = AuthorityService;
|
||||||
$scope.destinationService = DestinationService;
|
$scope.destinationService = DestinationService;
|
||||||
|
$scope.notificationService = NotificationService;
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,14 +7,11 @@
|
||||||
<wz-step title="Tracking" canexit="trackingForm.$valid">
|
<wz-step title="Tracking" canexit="trackingForm.$valid">
|
||||||
<ng-include src="'angular/certificates/certificate/tracking.tpl.html'"></ng-include>
|
<ng-include src="'angular/certificates/certificate/tracking.tpl.html'"></ng-include>
|
||||||
</wz-step>
|
</wz-step>
|
||||||
<wz-step title="Options" canenter="enterValidation">
|
|
||||||
<ng-include src="'angular/certificates/certificate/options.tpl.html'"></ng-include>
|
|
||||||
</wz-step>
|
|
||||||
<wz-step title="Distinguished Name" canenter="exitTracking" canexit="exitDN">
|
<wz-step title="Distinguished Name" canenter="exitTracking" canexit="exitDN">
|
||||||
<ng-include src="'angular/certificates/certificate/distinguishedName.tpl.html'"></ng-include>
|
<ng-include src="'angular/certificates/certificate/distinguishedName.tpl.html'"></ng-include>
|
||||||
</wz-step>
|
</wz-step>
|
||||||
<wz-step title="Destinations" canenter="enterValidation">
|
<wz-step title="Options" canenter="enterValidation">
|
||||||
<ng-include src="'angular/certificates/certificate/destinations.tpl.html'"></ng-include>
|
<ng-include src="'angular/certificates/certificate/options.tpl.html'"></ng-include>
|
||||||
</wz-step>
|
</wz-step>
|
||||||
</wizard>
|
</wizard>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<div class="input-group col-sm-12">
|
<div class="input-group col-sm-12">
|
||||||
<input name="selectedAuthority" tooltip="If you are unsure which authority you need; you most likely want to use 'verisign'" type="text" ng-model="certificate.selectedAuthority" placeholder="Authority Name" typeahead-on-select="certificate.attachAuthority($item)"
|
<input name="selectedAuthority" tooltip="If you are unsure which authority you need; you most likely want to use 'verisign'" type="text" ng-model="certificate.selectedAuthority" placeholder="Authority Name" typeahead-on-select="certificate.attachAuthority($item)"
|
||||||
typeahead="authority.name for authority in authorityService.findAuthorityByName($viewValue)" typeahead-loading="loadingAuthorities"
|
typeahead="authority.name for authority in authorityService.findActiveAuthorityByName($viewValue)" typeahead-loading="loadingAuthorities"
|
||||||
class="form-control" typeahead-wait-ms="100" typeahead-template-url="angular/authorities/authority/select.tpl.html" required>
|
class="form-control" typeahead-wait-ms="100" typeahead-template-url="angular/authorities/authority/select.tpl.html" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -77,7 +77,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
||||||
|
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,15 @@
|
||||||
|
|
||||||
angular.module('lemur')
|
angular.module('lemur')
|
||||||
|
|
||||||
.controller('CertificateUploadController', function ($scope, $modalInstance, CertificateService, LemurRestangular, DestinationService, ELBService, PluginService) {
|
.controller('CertificateUploadController', function ($scope, $modalInstance, CertificateService, LemurRestangular, DestinationService, NotificationService, ELBService, PluginService) {
|
||||||
$scope.certificate = LemurRestangular.restangularizeElement(null, {}, 'certificates');
|
$scope.certificate = LemurRestangular.restangularizeElement(null, {}, 'certificates');
|
||||||
$scope.upload = CertificateService.upload;
|
$scope.upload = CertificateService.upload;
|
||||||
|
|
||||||
$scope.destinationService = DestinationService;
|
$scope.destinationService = DestinationService;
|
||||||
|
$scope.notificationService = NotificationService;
|
||||||
$scope.elbService = ELBService;
|
$scope.elbService = ELBService;
|
||||||
|
|
||||||
PluginService.get('destination').then(function (plugins) {
|
PluginService.getByType('destination').then(function (plugins) {
|
||||||
$scope.plugins = plugins;
|
$scope.plugins = plugins;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@
|
||||||
class="help-block">Enter a valid certificate.</p>
|
class="help-block">Enter a valid certificate.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div ng-include="'angular/certificates/certificate/notifications.tpl.html'"></div>
|
||||||
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
<div ng-include="'angular/certificates/certificate/destinations.tpl.html'"></div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -67,6 +67,16 @@ angular.module('lemur')
|
||||||
removeDestination: function (index) {
|
removeDestination: function (index) {
|
||||||
this.destinations.splice(index, 1);
|
this.destinations.splice(index, 1);
|
||||||
},
|
},
|
||||||
|
attachNotification: function (notification) {
|
||||||
|
this.selectedNotification = null;
|
||||||
|
if (this.notifications === undefined) {
|
||||||
|
this.notifications = [];
|
||||||
|
}
|
||||||
|
this.notifications.push(notification);
|
||||||
|
},
|
||||||
|
removeNotification: function (index) {
|
||||||
|
this.notifications.splice(index, 1);
|
||||||
|
},
|
||||||
attachELB: function (elb) {
|
attachELB: function (elb) {
|
||||||
this.selectedELB = null;
|
this.selectedELB = null;
|
||||||
if (this.elbs === undefined) {
|
if (this.elbs === undefined) {
|
||||||
|
@ -89,7 +99,7 @@ angular.module('lemur')
|
||||||
});
|
});
|
||||||
return LemurRestangular.all('certificates');
|
return LemurRestangular.all('certificates');
|
||||||
})
|
})
|
||||||
.service('CertificateService', function ($location, CertificateApi, toaster) {
|
.service('CertificateService', function ($location, CertificateApi, LemurRestangular, toaster) {
|
||||||
var CertificateService = this;
|
var CertificateService = this;
|
||||||
CertificateService.findCertificatesByName = function (filterValue) {
|
CertificateService.findCertificatesByName = function (filterValue) {
|
||||||
return CertificateApi.getList({'filter[name]': filterValue})
|
return CertificateApi.getList({'filter[name]': filterValue})
|
||||||
|
@ -120,7 +130,7 @@ angular.module('lemur')
|
||||||
};
|
};
|
||||||
|
|
||||||
CertificateService.update = function (certificate) {
|
CertificateService.update = function (certificate) {
|
||||||
certificate.put().then(function () {
|
return LemurRestangular.copy(certificate).put().then(function () {
|
||||||
toaster.pop({
|
toaster.pop({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: certificate.name,
|
title: certificate.name,
|
||||||
|
@ -131,7 +141,7 @@ angular.module('lemur')
|
||||||
};
|
};
|
||||||
|
|
||||||
CertificateService.upload = function (certificate) {
|
CertificateService.upload = function (certificate) {
|
||||||
CertificateApi.customPOST(certificate, 'upload').then(
|
return CertificateApi.customPOST(certificate, 'upload').then(
|
||||||
function () {
|
function () {
|
||||||
toaster.pop({
|
toaster.pop({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
@ -150,7 +160,7 @@ angular.module('lemur')
|
||||||
};
|
};
|
||||||
|
|
||||||
CertificateService.loadPrivateKey = function (certificate) {
|
CertificateService.loadPrivateKey = function (certificate) {
|
||||||
certificate.customGET('key').then(
|
return certificate.customGET('key').then(
|
||||||
function (response) {
|
function (response) {
|
||||||
if (response.key === null) {
|
if (response.key === null) {
|
||||||
toaster.pop({
|
toaster.pop({
|
||||||
|
@ -172,43 +182,49 @@ angular.module('lemur')
|
||||||
};
|
};
|
||||||
|
|
||||||
CertificateService.getAuthority = function (certificate) {
|
CertificateService.getAuthority = function (certificate) {
|
||||||
certificate.customGET('authority').then(function (authority) {
|
return certificate.customGET('authority').then(function (authority) {
|
||||||
certificate.authority = authority;
|
certificate.authority = authority;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CertificateService.getCreator = function (certificate) {
|
CertificateService.getCreator = function (certificate) {
|
||||||
certificate.customGET('creator').then(function (creator) {
|
return certificate.customGET('creator').then(function (creator) {
|
||||||
certificate.creator = creator;
|
certificate.creator = creator;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CertificateService.getDestinations = function (certificate) {
|
CertificateService.getDestinations = function (certificate) {
|
||||||
certificate.getList('destinations').then(function (destinations) {
|
return certificate.getList('destinations').then(function (destinations) {
|
||||||
certificate.destinations = destinations;
|
certificate.destinations = destinations;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CertificateService.getNotifications = function (certificate) {
|
||||||
|
return certificate.getList('notifications').then(function (notifications) {
|
||||||
|
certificate.notifications = notifications;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
CertificateService.getListeners = function (certificate) {
|
CertificateService.getListeners = function (certificate) {
|
||||||
certificate.getList('listeners').then(function (listeners) {
|
return certificate.getList('listeners').then(function (listeners) {
|
||||||
certificate.listeners = listeners;
|
certificate.listeners = listeners;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CertificateService.getELBs = function (certificate) {
|
CertificateService.getELBs = function (certificate) {
|
||||||
certificate.getList('listeners').then(function (elbs) {
|
return certificate.getList('listeners').then(function (elbs) {
|
||||||
certificate.elbs = elbs;
|
certificate.elbs = elbs;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CertificateService.getDomains = function (certificate) {
|
CertificateService.getDomains = function (certificate) {
|
||||||
certificate.getList('domains').then(function (domains) {
|
return certificate.getList('domains').then(function (domains) {
|
||||||
certificate.domains = domains;
|
certificate.domains = domains;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CertificateService.updateActive = function (certificate) {
|
CertificateService.updateActive = function (certificate) {
|
||||||
certificate.put().then(
|
return certificate.put().then(
|
||||||
function () {
|
function () {
|
||||||
toaster.pop({
|
toaster.pop({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
|
|
@ -27,7 +27,7 @@ angular.module('lemur')
|
||||||
_.each(data, function (certificate) {
|
_.each(data, function (certificate) {
|
||||||
CertificateService.getDomains(certificate);
|
CertificateService.getDomains(certificate);
|
||||||
CertificateService.getDestinations(certificate);
|
CertificateService.getDestinations(certificate);
|
||||||
CertificateService.getListeners(certificate);
|
CertificateService.getNotifications(certificate);
|
||||||
CertificateService.getAuthority(certificate);
|
CertificateService.getAuthority(certificate);
|
||||||
CertificateService.getCreator(certificate);
|
CertificateService.getCreator(certificate);
|
||||||
});
|
});
|
||||||
|
@ -74,6 +74,24 @@ angular.module('lemur')
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.edit = function (certificateId) {
|
||||||
|
var modalInstance = $modal.open({
|
||||||
|
animation: true,
|
||||||
|
controller: 'CertificateEditController',
|
||||||
|
templateUrl: '/angular/certificates/certificate/edit.tpl.html',
|
||||||
|
size: 'lg',
|
||||||
|
resolve: {
|
||||||
|
editId: function () {
|
||||||
|
return certificateId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
modalInstance.result.then(function () {
|
||||||
|
$scope.certificateTable.reload();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
$scope.import = function () {
|
$scope.import = function () {
|
||||||
var modalInstance = $modal.open({
|
var modalInstance = $modal.open({
|
||||||
animation: true,
|
animation: true,
|
||||||
|
|
|
@ -30,11 +30,6 @@
|
||||||
<li><span class="text-muted">{{ certificate.owner }}</span></li>
|
<li><span class="text-muted">{{ certificate.owner }}</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<td data-title="'Destinations'" filter="{ 'destination': 'select' }" filter-date="getDestinationDropDown()">
|
|
||||||
<div class="btn-group">
|
|
||||||
<a href="#/destinations/{{ destination.id }}/edit" class="btn btn-sm btn-default" ng-repeat="account in certificate.destinations">{{ destination.label }}</a>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td data-title="'Active'" filter="{ 'active': 'select' }" filter-data="getCertificateStatus()">
|
<td data-title="'Active'" filter="{ 'active': 'select' }" filter-data="getCertificateStatus()">
|
||||||
<form>
|
<form>
|
||||||
<switch ng-change="certificateService.updateActive(certificate)" id="status" name="status" ng-model="certificate.active" class="green small"></switch>
|
<switch ng-change="certificateService.updateActive(certificate)" id="status" name="status" ng-model="certificate.active" class="green small"></switch>
|
||||||
|
@ -47,14 +42,16 @@
|
||||||
{{ certificate.cn }}
|
{{ certificate.cn }}
|
||||||
</td>
|
</td>
|
||||||
<td data-title="''">
|
<td data-title="''">
|
||||||
<div class="btn-group-vertical pull-right">
|
<div class="btn-group pull-right">
|
||||||
<button ng-model="certificate.toggle" class="btn btn-sm btn-info" btn-checkbox btn-checkbox-true="1" butn-checkbox-false="0">View</button>
|
<button ng-model="certificate.toggle" class="btn btn-sm btn-info" btn-checkbox btn-checkbox-true="1" butn-checkbox-false="0">More</button>
|
||||||
|
<button class="btn btn-sm btn-warning" ng-click="edit(certificate.id)">Edit</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="warning" ng-show="certificate.toggle" ng-repeat-end>
|
<tr class="warning" ng-show="certificate.toggle" ng-repeat-end>
|
||||||
<td colspan="5">
|
<td colspan="6">
|
||||||
<div class="col-md-6">
|
<tabset justified="true" class="col-md-6">
|
||||||
|
<tab heading="Basic Info">
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<strong>Creator</strong>
|
<strong>Creator</strong>
|
||||||
|
@ -102,15 +99,23 @@
|
||||||
<span class="pull-right">{{ certificate.description }}</span>
|
<span class="pull-right">{{ certificate.description }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h4>Domains</h4>
|
</tab>
|
||||||
|
<tab heading="Notifications">
|
||||||
|
<div class="list-group">
|
||||||
|
<a href="#/domains/{{ domain.id }}" class="list-group-item" ng-repeat="notification in certificate.notifications">{{ notification.label }}</a>
|
||||||
|
</div>
|
||||||
|
</tab>
|
||||||
|
<tab heading="Destinations">
|
||||||
|
<div class="list-group">
|
||||||
|
<a href="#/domains/{{ domain.id }}" class="list-group-item" ng-repeat="destination in certificate.destinations">{{ destination.label }}</a>
|
||||||
|
</div>
|
||||||
|
</tab>
|
||||||
|
<tab heading="Domains">
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
<a href="#/domains/{{ domain.id }}" class="list-group-item" ng-repeat="domain in certificate.domains">{{ domain.name }}</a>
|
<a href="#/domains/{{ domain.id }}" class="list-group-item" ng-repeat="domain in certificate.domains">{{ domain.name }}</a>
|
||||||
</div>
|
</div>
|
||||||
<h4 ng-show="certificate.destinations.total">ARNs</h4>
|
</tab>
|
||||||
<ul class="list-group">
|
</tabset>
|
||||||
<li class="list-group-item" ng-repeat="arn in certificate.arns">{{ arn }}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<tabset justified="true" class="col-md-6">
|
<tabset justified="true" class="col-md-6">
|
||||||
<tab heading="Chain">
|
<tab heading="Chain">
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -5,9 +5,10 @@ angular.module('lemur')
|
||||||
.controller('DestinationsCreateController', function ($scope, $modalInstance, PluginService, DestinationService, LemurRestangular){
|
.controller('DestinationsCreateController', function ($scope, $modalInstance, PluginService, DestinationService, LemurRestangular){
|
||||||
$scope.destination = LemurRestangular.restangularizeElement(null, {}, 'destinations');
|
$scope.destination = LemurRestangular.restangularizeElement(null, {}, 'destinations');
|
||||||
|
|
||||||
PluginService.get('destination').then(function (plugins) {
|
PluginService.getByType('destination').then(function (plugins) {
|
||||||
$scope.plugins = plugins;
|
$scope.plugins = plugins;
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.save = function (destination) {
|
$scope.save = function (destination) {
|
||||||
DestinationService.create(destination).then(function () {
|
DestinationService.create(destination).then(function () {
|
||||||
$modalInstance.close();
|
$modalInstance.close();
|
||||||
|
@ -24,8 +25,14 @@ angular.module('lemur')
|
||||||
$scope.destination = destination;
|
$scope.destination = destination;
|
||||||
});
|
});
|
||||||
|
|
||||||
PluginService.get('destination').then(function (plugins) {
|
PluginService.getByType('destination').then(function (plugins) {
|
||||||
$scope.plugins = plugins;
|
$scope.plugins = plugins;
|
||||||
|
_.each($scope.plugins, function (plugin) {
|
||||||
|
if (plugin.slug === $scope.destination.pluginName) {
|
||||||
|
plugin.pluginOptions = $scope.destination.destinationOptions;
|
||||||
|
$scope.destination.plugin = plugin;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.save = function (destination) {
|
$scope.save = function (destination) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ angular.module('lemur')
|
||||||
.service('DestinationApi', function (LemurRestangular) {
|
.service('DestinationApi', function (LemurRestangular) {
|
||||||
return LemurRestangular.all('destinations');
|
return LemurRestangular.all('destinations');
|
||||||
})
|
})
|
||||||
.service('DestinationService', function ($location, DestinationApi, toaster) {
|
.service('DestinationService', function ($location, DestinationApi, PluginService, toaster) {
|
||||||
var DestinationService = this;
|
var DestinationService = this;
|
||||||
DestinationService.findDestinationsByName = function (filterValue) {
|
DestinationService.findDestinationsByName = function (filterValue) {
|
||||||
return DestinationApi.getList({'filter[label]': filterValue})
|
return DestinationApi.getList({'filter[label]': filterValue})
|
||||||
|
@ -49,5 +49,11 @@ angular.module('lemur')
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DestinationService.getPlugin = function (destination) {
|
||||||
|
return PluginService.getByName(destination.pluginName).then(function (plugin) {
|
||||||
|
destination.plugin = plugin;
|
||||||
|
});
|
||||||
|
};
|
||||||
return DestinationService;
|
return DestinationService;
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,6 +23,9 @@ angular.module('lemur')
|
||||||
getData: function ($defer, params) {
|
getData: function ($defer, params) {
|
||||||
DestinationApi.getList(params.url()).then(
|
DestinationApi.getList(params.url()).then(
|
||||||
function (data) {
|
function (data) {
|
||||||
|
_.each(data, function (destination) {
|
||||||
|
DestinationService.getPlugin(destination);
|
||||||
|
});
|
||||||
params.total(data.total);
|
params.total(data.total);
|
||||||
$defer.resolve(data);
|
$defer.resolve(data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,23 @@ angular.module('lemur')
|
||||||
})
|
})
|
||||||
.service('PluginService', function (PluginApi) {
|
.service('PluginService', function (PluginApi) {
|
||||||
var PluginService = this;
|
var PluginService = this;
|
||||||
PluginService.get = function (type) {
|
PluginService.get = function () {
|
||||||
return PluginApi.customGETLIST(type).then(function (plugins) {
|
return PluginApi.getList().then(function (plugins) {
|
||||||
return plugins;
|
return plugins;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
PluginService.getByType = function (type) {
|
||||||
|
return PluginApi.getList({'type': type}).then(function (plugins) {
|
||||||
|
return plugins;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
PluginService.getByName = function (pluginName) {
|
||||||
|
return PluginApi.customGET(pluginName).then(function (plugin) {
|
||||||
|
return plugin;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return PluginService;
|
||||||
});
|
});
|
||||||
|
|
|
@ -49,13 +49,20 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-collapse collapse" ng-controller="LoginController">
|
<div class="navbar-collapse collapse" ng-controller="LoginController">
|
||||||
<ul class="nav navbar-nav navbar-left">
|
<ul class="nav navbar-nav navbar-left">
|
||||||
<li data-match-route="/dashboard"><a href="/#/dashboard">Dashboard</a></li>
|
<li><a href="/#/dashboard">Dashboard</a></li>
|
||||||
<li data-match-route="/certificates"><a href="/#/certificates">Certificates</a></li>
|
<li><a href="/#/certificates">Certificates</a></li>
|
||||||
<li data-match-route="/authorities"><a href="/#/authorities">Authorities</a></li>
|
<li><a href="/#/authorities">Authorities</a></li>
|
||||||
<li data-match-route="/domains"><a href="/#/domains">Domains</a></li>
|
<li><a href="/#/notifications">Notifications</a></li>
|
||||||
|
<li><a href="/#/destinations">Destinations</a></li>
|
||||||
|
<li></li>
|
||||||
|
<li class="dropdown" dropdown on-toggle="toggled(open)">
|
||||||
|
<a href class="dropdown-toggle" dropdown-toggle>Settings <span class="caret"></span></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
<li><a href="/#/roles">Roles</a></li>
|
<li><a href="/#/roles">Roles</a></li>
|
||||||
<li><a href="/#/users">Users</a></li>
|
<li><a href="/#/users">Users</a></li>
|
||||||
<li><a href="/#/destinations">Destinations</a></li>
|
<li><a href="/#/domains">Domains</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul ng-show="!currentUser.username" class="nav navbar-nav navbar-right">
|
<ul ng-show="!currentUser.username" class="nav navbar-nav navbar-right">
|
||||||
<li><a href="/#/login">Login</a></li>
|
<li><a href="/#/login">Login</a></li>
|
||||||
|
@ -63,7 +70,7 @@
|
||||||
<ul ng-show="currentUser.username" class="nav navbar-nav navbar-right">
|
<ul ng-show="currentUser.username" class="nav navbar-nav navbar-right">
|
||||||
<li class="dropdown" dropdown on-toggle="toggled(open)">
|
<li class="dropdown" dropdown on-toggle="toggled(open)">
|
||||||
<a href class="dropdown-toggle profile-nav" dropdown-toggle>
|
<a href class="dropdown-toggle profile-nav" dropdown-toggle>
|
||||||
{{ currentUser.username }}<img ng-show="currentUser.profileImage" src="{{ currentUser.profileImage }}" class="profile img-circle">
|
{{ currentUser.username }}<img ng-if="currentUser.profileImage" src="{{ currentUser.profileImage }}" class="profile img-circle">
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a ng-click="logout()">Logout</a></li>
|
<li><a ng-click="logout()">Logout</a></li>
|
||||||
|
|
|
@ -27,7 +27,7 @@ def test_create_basic_csr():
|
||||||
country=u'US',
|
country=u'US',
|
||||||
state=u'CA',
|
state=u'CA',
|
||||||
location=u'A place',
|
location=u'A place',
|
||||||
extensions=dict(subAltNames=[u'test.example.com', u'test2.example.com'])
|
extensions=dict(names=dict(subAltNames=[u'test.example.com', u'test2.example.com']))
|
||||||
)
|
)
|
||||||
csr, pem = create_csr(csr_config)
|
csr, pem = create_csr(csr_config)
|
||||||
|
|
||||||
|
@ -56,15 +56,15 @@ def test_cert_is_san():
|
||||||
from lemur.tests.certs import INTERNAL_VALID_SAN_CERT, INTERNAL_VALID_LONG_CERT
|
from lemur.tests.certs import INTERNAL_VALID_SAN_CERT, INTERNAL_VALID_LONG_CERT
|
||||||
from lemur.certificates.models import cert_is_san
|
from lemur.certificates.models import cert_is_san
|
||||||
|
|
||||||
assert cert_is_san(INTERNAL_VALID_LONG_CERT) == None
|
assert cert_is_san(INTERNAL_VALID_LONG_CERT) == None # noqa
|
||||||
assert cert_is_san(INTERNAL_VALID_SAN_CERT) == True
|
assert cert_is_san(INTERNAL_VALID_SAN_CERT) == True # noqa
|
||||||
|
|
||||||
|
|
||||||
def test_cert_is_wildcard():
|
def test_cert_is_wildcard():
|
||||||
from lemur.tests.certs import INTERNAL_VALID_WILDCARD_CERT, INTERNAL_VALID_LONG_CERT
|
from lemur.tests.certs import INTERNAL_VALID_WILDCARD_CERT, INTERNAL_VALID_LONG_CERT
|
||||||
from lemur.certificates.models import cert_is_wildcard
|
from lemur.certificates.models import cert_is_wildcard
|
||||||
assert cert_is_wildcard(INTERNAL_VALID_WILDCARD_CERT) == True
|
assert cert_is_wildcard(INTERNAL_VALID_WILDCARD_CERT) == True # noqa
|
||||||
assert cert_is_wildcard(INTERNAL_VALID_LONG_CERT) == None
|
assert cert_is_wildcard(INTERNAL_VALID_LONG_CERT) == None # noqa
|
||||||
|
|
||||||
|
|
||||||
def test_cert_get_bitstrength():
|
def test_cert_get_bitstrength():
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
from lemur.notifications.service import * # noqa
|
||||||
|
from lemur.notifications.views import * # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_crud(session):
|
||||||
|
notification = create('testnotify', 'email-notification', {}, 'notify1', [])
|
||||||
|
assert notification.id > 0
|
||||||
|
|
||||||
|
notification = update(notification.id, 'testnotify2', {}, 'notify2', [])
|
||||||
|
assert notification.label == 'testnotify2'
|
||||||
|
|
||||||
|
assert len(get_all()) == 1
|
||||||
|
|
||||||
|
delete(1)
|
||||||
|
assert len(get_all()) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_notification_get(client):
|
||||||
|
assert client.get(api.url_for(Notifications, notification_id=1)).status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_notification_post(client):
|
||||||
|
assert client.post(api.url_for(Notifications, notification_id=1), data={}).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
|
def test_notification_put(client):
|
||||||
|
assert client.put(api.url_for(Notifications, notification_id=1), data={}).status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_notification_delete(client):
|
||||||
|
assert client.delete(api.url_for(Notifications, notification_id=1)).status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_notification_patch(client):
|
||||||
|
assert client.patch(api.url_for(Notifications, notification_id=1), data={}).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
|
VALID_USER_HEADER_TOKEN = {
|
||||||
|
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'}
|
||||||
|
|
||||||
|
|
||||||
|
def test_auth_notification_get(client):
|
||||||
|
assert client.get(api.url_for(Notifications, notification_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_auth_notification_post_(client):
|
||||||
|
assert client.post(api.url_for(Notifications, notification_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
|
def test_auth_notification_put(client):
|
||||||
|
assert client.put(api.url_for(Notifications, notification_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
def test_auth_notification_delete(client):
|
||||||
|
assert client.delete(api.url_for(Notifications, notification_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_auth_notification_patch(client):
|
||||||
|
assert client.patch(api.url_for(Notifications, notification_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
|
VALID_ADMIN_HEADER_TOKEN = {
|
||||||
|
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'}
|
||||||
|
|
||||||
|
|
||||||
|
def test_admin_notification_get(client):
|
||||||
|
assert client.get(api.url_for(Notifications, notification_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_admin_notification_post(client):
|
||||||
|
assert client.post(api.url_for(Notifications, notification_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
|
def test_admin_notification_put(client):
|
||||||
|
assert client.put(api.url_for(Notifications, notification_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
def test_admin_notification_delete(client):
|
||||||
|
assert client.delete(api.url_for(Notifications, notification_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_admin_notification_patch(client):
|
||||||
|
assert client.patch(api.url_for(Notifications, notification_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
|
def test_notifications_get(client):
|
||||||
|
assert client.get(api.url_for(NotificationsList)).status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_notifications_post(client):
|
||||||
|
assert client.post(api.url_for(NotificationsList), data={}).status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_notifications_put(client):
|
||||||
|
assert client.put(api.url_for(NotificationsList), data={}).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
|
def test_notifications_delete(client):
|
||||||
|
assert client.delete(api.url_for(NotificationsList)).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
|
def test_notifications_patch(client):
|
||||||
|
assert client.patch(api.url_for(NotificationsList), data={}).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
|
def test_auth_notifications_get(client):
|
||||||
|
assert client.get(api.url_for(NotificationsList), headers=VALID_USER_HEADER_TOKEN).status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_auth_notifications_post(client):
|
||||||
|
assert client.post(api.url_for(NotificationsList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
def test_admin_notifications_get(client):
|
||||||
|
resp = client.get(api.url_for(NotificationsList), headers=VALID_ADMIN_HEADER_TOKEN)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.json == {'items': [], 'total': 0}
|
37
setup.py
37
setup.py
|
@ -22,27 +22,27 @@ from subprocess import check_output
|
||||||
ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__)))
|
ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__)))
|
||||||
|
|
||||||
install_requires = [
|
install_requires = [
|
||||||
'Flask>=0.10.1',
|
'Flask==0.10.1',
|
||||||
'Flask-RESTful>=0.3.3',
|
'Flask-RESTful==0.3.3',
|
||||||
'Flask-SQLAlchemy>=1.0.5',
|
'Flask-SQLAlchemy==2.0',
|
||||||
'Flask-Script>=2.0.5',
|
'Flask-Script==2.0.5',
|
||||||
'Flask-Migrate>=1.4.0',
|
'Flask-Migrate==1.4.0',
|
||||||
'Flask-Bcrypt>=0.6.2',
|
'Flask-Bcrypt==0.6.2',
|
||||||
'Flask-Principal>=0.4.0',
|
'Flask-Principal==0.4.0',
|
||||||
'Flask-Mail==0.9.1',
|
'Flask-Mail==0.9.1',
|
||||||
'SQLAlchemy-Utils>=0.30.11',
|
'SQLAlchemy-Utils==0.30.11',
|
||||||
'BeautifulSoup4',
|
'BeautifulSoup4',
|
||||||
'requests>=2.7.0',
|
'requests==2.7.0',
|
||||||
'psycopg2>=2.6.1',
|
'psycopg2==2.6.1',
|
||||||
'arrow>=0.5.4',
|
'arrow==0.5.4',
|
||||||
'boto>=2.38.0', # we might make this optional
|
'boto==2.38.0', # we might make this optional
|
||||||
'six>=1.9.0',
|
'six==1.9.0',
|
||||||
'gunicorn>=19.3.0',
|
'gunicorn==19.3.0',
|
||||||
'pycrypto>=2.6.1',
|
'pycrypto==2.6.1',
|
||||||
'cryptography>=1.0dev',
|
'cryptography>=1.0dev',
|
||||||
'pyopenssl>=0.15.1',
|
'pyopenssl==0.15.1',
|
||||||
'pyjwt>=1.0.1',
|
'pyjwt==1.0.1',
|
||||||
'xmltodict>=0.9.2'
|
'xmltodict==0.9.2'
|
||||||
]
|
]
|
||||||
|
|
||||||
tests_require = [
|
tests_require = [
|
||||||
|
@ -138,6 +138,7 @@ setup(
|
||||||
'cloudca_source = lemur.plugins.lemur_cloudca.plugin:CloudCASourcePlugin'
|
'cloudca_source = lemur.plugins.lemur_cloudca.plugin:CloudCASourcePlugin'
|
||||||
'aws_destination = lemur.plugins.lemur_aws.plugin:AWSDestinationPlugin',
|
'aws_destination = lemur.plugins.lemur_aws.plugin:AWSDestinationPlugin',
|
||||||
'aws_source = lemur.plugins.lemur_aws.plugin:AWSSourcePlugin'
|
'aws_source = lemur.plugins.lemur_aws.plugin:AWSSourcePlugin'
|
||||||
|
'email_notification = lemur.plugins.lemur_email.plugin:EmailNotificationPlugin'
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
classifiers=[
|
classifiers=[
|
||||||
|
|
Loading…
Reference in New Issue