diff --git a/lemur/__init__.py b/lemur/__init__.py index aada8abe..59d8fff1 100644 --- a/lemur/__init__.py +++ b/lemur/__init__.py @@ -15,11 +15,12 @@ from lemur.roles.views import mod as roles_bp from lemur.auth.views import mod as auth_bp from lemur.domains.views import mod as domains_bp from lemur.elbs.views import mod as elbs_bp -from lemur.accounts.views import mod as accounts_bp +from lemur.destinations.views import mod as destinations_bp from lemur.authorities.views import mod as authorities_bp from lemur.listeners.views import mod as listeners_bp from lemur.certificates.views import mod as certificates_bp from lemur.status.views import mod as status_bp +from lemur.plugins.views import mod as plugins_bp LEMUR_BLUEPRINTS = ( users_bp, @@ -27,11 +28,12 @@ LEMUR_BLUEPRINTS = ( auth_bp, domains_bp, elbs_bp, - accounts_bp, + destinations_bp, authorities_bp, listeners_bp, certificates_bp, - status_bp + status_bp, + plugins_bp, ) def create_app(config=None): diff --git a/lemur/accounts/models.py b/lemur/accounts/models.py deleted file mode 100644 index 7adba330..00000000 --- a/lemur/accounts/models.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -.. module: lemur.accounts.models - :platform: unix - :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more - :license: Apache, see LICENSE for more details. -.. moduleauthor:: Kevin Glisson -""" -from sqlalchemy import Column, Integer, String, Text -from sqlalchemy.orm import relationship - -from lemur.database import db - - -class Account(db.Model): - __tablename__ = 'accounts' - id = Column(Integer, primary_key=True) - account_number = Column(String(32), unique=True) - label = Column(String(32)) - notes = Column(Text()) - elbs = relationship("ELB", backref='account', cascade="all, delete, delete-orphan") - - def as_dict(self): - return {c.name: getattr(self, c.name) for c in self.__table__.columns} - - def serialize(self): - blob = self.as_dict() - blob['elbs'] = [x.id for x in self.elbs] - return blob - diff --git a/lemur/accounts/service.py b/lemur/accounts/service.py deleted file mode 100644 index 61fe0633..00000000 --- a/lemur/accounts/service.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -.. module: lemur.accounts.views - :platform: Unix - :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more - :license: Apache, see LICENSE for more details. -.. moduleauthor:: Kevin Glisson -""" -from lemur import database -from lemur.accounts.models import Account -from lemur.certificates.models import Certificate - - -def create(account_number, label=None, comments=None): - """ - Creates a new account, that can then be used as a destination for certificates. - - :param account_number: AWS assigned ID - :param label: Account common name - :param comments: - :rtype : Account - :return: New account - """ - acct = Account(account_number=account_number, label=label, notes=comments) - return database.create(acct) - - -def update(account_id, account_number, label, comments=None): - """ - Updates an existing account. - - :param account_id: Lemur assigned ID - :param account_number: AWS assigned ID - :param label: Account common name - :param comments: - :rtype : Account - :return: - """ - account = get(account_id) - - account.account_number = account_number - account.label = label - account.notes = comments - - return database.update(account) - - -def delete(account_id): - """ - Deletes an account. - - :param account_id: Lemur assigned ID - """ - database.delete(get(account_id)) - - -def get(account_id): - """ - Retrieves an account by it's lemur assigned ID. - - :param account_id: Lemur assigned ID - :rtype : Account - :return: - """ - return database.get(Account, account_id) - - -def get_by_account_number(account_number): - """ - Retrieves an account by it's amazon assigned ID. - - :rtype : Account - :param account_number: AWS assigned ID - :return: - """ - return database.get(Account, account_number, field='account_number') - - -def get_all(): - """ - Retrieves all account currently known by Lemur. - - :return: - """ - query = database.session_query(Account) - return database.find_all(query, Account, {}).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(Account).join(Certificate, Account.certificate) - query = query.filter(Certificate.id == certificate_id) - else: - query = database.session_query(Account) - - if filt: - terms = filt.split(';') - query = database.filter(query, Account, terms) - - query = database.find_all(query, Account, args) - - if sort_by and sort_dir: - query = database.sort(query, Account, sort_by, sort_dir) - - return database.paginate(query, page, count) - diff --git a/lemur/authorities/service.py b/lemur/authorities/service.py index f414d6a8..25c42e8c 100644 --- a/lemur/authorities/service.py +++ b/lemur/authorities/service.py @@ -54,7 +54,7 @@ def create(kwargs): kwargs['creator'] = g.current_user.email cert_body, intermediate, issuer_roles = issuer.create_authority(kwargs) - cert = cert_service.save_cert(cert_body, None, intermediate, None) + cert = cert_service.save_cert(cert_body, None, intermediate, []) cert.user = g.current_user # we create and attach any roles that the issuer gives us diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py index 5a46a015..10e0900d 100644 --- a/lemur/certificates/models.py +++ b/lemur/certificates/models.py @@ -22,7 +22,7 @@ from lemur.database import db from lemur.domains.models import Domain from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE, NONSTANDARD_NAMING_TEMPLATE -from lemur.models import certificate_associations, certificate_account_associations +from lemur.models import certificate_associations, certificate_destination_associations def create_name(issuer, not_before, not_after, subject, san): @@ -215,7 +215,7 @@ class Certificate(db.Model): date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False) user_id = Column(Integer, ForeignKey('users.id')) authority_id = Column(Integer, ForeignKey('authorities.id')) - accounts = relationship("Account", secondary=certificate_account_associations, backref='certificate') + accounts = relationship("Destination", secondary=certificate_destination_associations, backref='certificate') domains = relationship("Domain", secondary=certificate_associations, backref="certificate") elb_listeners = relationship("Listener", lazy='dynamic', backref='certificate') diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 3b3b2089..b89cf159 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -13,12 +13,10 @@ from sqlalchemy import func, or_ from flask import g, current_app from lemur import database -from lemur.common.services.aws import iam from lemur.plugins.base import plugins from lemur.certificates.models import Certificate -from lemur.accounts.models import Account -from lemur.accounts import service as account_service +from lemur.destinations.models import Destination from lemur.authorities.models import Authority from lemur.roles.models import Role @@ -59,28 +57,6 @@ def delete(cert_id): database.delete(get(cert_id)) -def disassociate_aws_account(certs, account): - """ - Removes the account association from a certificate. We treat AWS as a completely - external service. Certificates are added and removed from this service but a record - of that certificate is always kept and tracked by Lemur. This allows us to migrate - certificates to different accounts with ease. - - :param certs: - :param account: - """ - account_certs = Certificate.query.filter(Certificate.accounts.any(Account.id == 1)).\ - filter(~Certificate.body.in_(certs)).all() - - for a_cert in account_certs: - try: - a_cert.accounts.remove(account) - except Exception as e: - current_app.logger.debug("Skipping {0} account {1} is already disassociated".format(a_cert.name, account.label)) - continue - database.update(a_cert) - - def get_all_certs(): """ Retrieves all certificates within Lemur. @@ -134,7 +110,7 @@ def mint(issuer_options): issuer_options['creator'] = g.user.email cert_body, cert_chain = issuer.create_certificate(csr, issuer_options) - cert = save_cert(cert_body, private_key, cert_chain, issuer_options.get('accounts')) + cert = save_cert(cert_body, private_key, cert_chain, issuer_options.get('destinations')) cert.user = g.user cert.authority = authority database.update(cert) @@ -154,9 +130,10 @@ def import_certificate(**kwargs): :param kwargs: """ + from lemur.users import service as user_service cert = Certificate(kwargs['public_certificate']) - cert.owner = kwargs.get('owner', ) - cert.creator = kwargs.get('creator', 'Lemur') + cert.owner = kwargs.get('owner', current_app.config.get('LEMUR_SECURITY_TEAM_EMAIL')) + cert.creator = kwargs.get('creator', user_service.get_by_email('lemur@nobody')) # NOTE existing certs may not follow our naming standard we will # overwrite the generated name with the actual cert name @@ -166,31 +143,29 @@ def import_certificate(**kwargs): if kwargs.get('user'): cert.user = kwargs.get('user') - if kwargs.get('account'): - cert.accounts.append(kwargs.get('account')) + if kwargs.get('destination'): + cert.destinations.append(kwargs.get('destination')) cert = database.create(cert) return cert -def save_cert(cert_body, private_key, cert_chain, accounts): +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 challenge: - :param csr_config: - :param accounts: + :param destinations: """ cert = Certificate(cert_body, private_key, cert_chain) - # if we have an AWS accounts lets upload them - if accounts: - for account in accounts: - account = account_service.get(account['id']) - iam.upload_cert(account.account_number, cert, private_key, cert_chain) - cert.accounts.append(account) + + # 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 @@ -198,13 +173,11 @@ def upload(**kwargs): """ Allows for pre-made certificates to be imported into Lemur. """ - # save this cert the same way we save all of our certs, including uploading - # to aws if necessary cert = save_cert( kwargs.get('public_cert'), kwargs.get('private_key'), kwargs.get('intermediate_cert'), - kwargs.get('accounts') + kwargs.get('destinations') ) cert.owner = kwargs['owner'] @@ -237,7 +210,7 @@ def render(args): query = database.session_query(Certificate) time_range = args.pop('time_range') - account_id = args.pop('account_id') + destination_id = args.pop('destination_id') show = args.pop('show') owner = args.pop('owner') creator = args.pop('creator') # TODO we should enabling filtering by owner @@ -260,8 +233,8 @@ def render(args): ) return database.sort_and_page(query, Certificate, args) - if 'account' in terms: - query = query.filter(Certificate.accounts.any(Account.id == terms[1])) + if 'destination' in terms: + query = query.filter(Certificate.destinations.any(Destination.id == terms[1])) elif 'active' in filt: # this is really weird but strcmp seems to not work here?? query = query.filter(Certificate.active == terms[1]) else: @@ -276,8 +249,8 @@ def render(args): ) ) - if account_id: - query = query.filter(Certificate.accounts.any(Account.id == account_id)) + if destination_id: + query = query.filter(Certificate.destinations.any(Destination.id == destination_id)) if time_range: to = arrow.now().replace(weeks=+time_range).format('YYYY-MM-DD') @@ -404,8 +377,8 @@ def stats(**kwargs): if kwargs.get('active') == 'true': query = query.filter(Certificate.elb_listeners.any()) - if kwargs.get('account_id'): - query = query.filter(Certificate.accounts.any(Account.id == kwargs.get('account_id'))) + if kwargs.get('destination_id'): + query = query.filter(Certificate.destinations.any(Destination.id == kwargs.get('destination_id'))) if kwargs.get('metric') == 'not_after': start = arrow.utcnow() diff --git a/lemur/certificates/sync.py b/lemur/certificates/sync.py index 92438aa4..a3ec0d2f 100644 --- a/lemur/certificates/sync.py +++ b/lemur/certificates/sync.py @@ -7,10 +7,6 @@ to 'sync' with as many different datasources as possible to try and track any certificate that may be in use. - This include querying AWS for certificates attached to ELBs, querying our own - internal CA for certificates issued. As well as some rudimentary source code - scraping that attempts to find certificates checked into source code. - These operations are typically run on a periodic basis from either the command line or a cron job. @@ -18,151 +14,33 @@ :license: Apache, see LICENSE for more details. .. moduleauthor:: Kevin Glisson """ -import requests -from bs4 import BeautifulSoup - from flask import current_app -from lemur.users import service as user_service -from lemur.accounts import service as account_service from lemur.certificates import service as cert_service -from lemur.certificates.models import Certificate, get_name_from_arn -from lemur.common.services.aws.iam import get_all_server_certs -from lemur.common.services.aws.iam import get_cert_from_arn from lemur.plugins.base import plugins +from lemur.plugins.bases.source import SourcePlugin -def aws(): - """ - Attempts to retrieve all certificates located in known AWS accounts - :raise e: - """ - new = 0 - updated = 0 +def sync(): + for plugin in plugins: + new = 0 + updated = 0 + if isinstance(plugin, SourcePlugin): + if plugin.is_enabled(): + current_app.logger.error("Retrieving certificates from {0}".format(plugin.title)) + certificates = plugin.get_certificates() - # all certificates 'discovered' by lemur are tracked by the lemur - # user - user = user_service.get_by_email('lemur@nobody') + for certificate in certificates: + exists = cert_service.find_duplicates(certificate) - # we don't need to check regions as IAM is a global service - for account in account_service.get_all(): - certificate_bodies = [] - try: - cert_arns = get_all_server_certs(account.account_number) - except Exception as e: - current_app.logger.error("Failed to to get Certificates from '{}/{}' reason {}".format( - account.label, account.account_number, e.message) - ) - raise e + if not exists: + cert_service.import_certificate(**certificate) + new += 1 - current_app.logger.info("found {} certs from '{}/{}' ... ".format( - len(cert_arns), account.account_number, account.label) - ) + if len(exists) == 1: + updated += 1 - for cert in cert_arns: - cert_body = get_cert_from_arn(cert.arn)[0] - certificate_bodies.append(cert_body) - existing = cert_service.find_duplicates(cert_body) + # TODO associated cert with source + # TODO update cert if found from different source + # TODO dissassociate source if missing - if not existing: - cert_service.import_certificate( - **{'owner': 'secops@netflix.com', - 'creator': 'Lemur', - 'name': get_name_from_arn(cert.arn), - 'account': account, - 'user': user, - 'public_certificate': cert_body - } - ) - new += 1 - - elif len(existing) == 1: # we check to make sure we know about the current account for this certificate - for e_account in existing[0].accounts: - if e_account.account_number == account.account_number: - break - else: # we have a new account - existing[0].accounts.append(account) - updated += 1 - - else: - current_app.logger.error( - "Multiple certificates with the same body found, unable to correctly determine which entry to update" - ) - - # make sure we remove any certs that have been removed from AWS - cert_service.disassociate_aws_account(certificate_bodies, account) - current_app.logger.info("found {} new certificates in aws {}".format(new, account.label)) - - -def cloudca(): - """ - Attempts to retrieve all certificates that are stored in CloudCA - """ - user = user_service.get_by_email('lemur@nobody') - # sync all new certificates/authorities not created through lemur - issuer = plugins.get('cloudca') - authorities = issuer.get_authorities() - total = 0 - new = 1 - for authority in authorities: - certs = issuer.get_cert(ca_name=authority) - for cert in certs: - total += 1 - cert['user'] = user - existing = cert_service.find_duplicates(cert['public_certificate']) - if not existing: - new += 1 - try: - cert_service.import_certificate(**cert) - except NameError as e: - current_app.logger.error("Cannot import certificate {0}".format(cert)) - - current_app.logger.debug("Found {0} total certificates in cloudca".format(total)) - current_app.logger.debug("Found {0} new certificates in cloudca".format(new)) - - -def source(): - """ - Attempts to track certificates that are stored in Source Code - """ - new = 0 - keywords = ['"--- Begin Certificate ---"'] - endpoint = current_app.config.get('LEMUR_SOURCE_SEARCH') - maxresults = 25000 - - current_app.logger.info("Searching {0} for new certificates".format(endpoint)) - - for keyword in keywords: - current_app.logger.info("Looking for keyword: {0}".format(keyword)) - url = "{}/source/s?n={}&start=1&sort=relevancy&q={}&project=github%2Cperforce%2Cstash".format(endpoint, maxresults, keyword) - - current_app.logger.debug("Request url: {0}".format(url)) - r = requests.get(url, timeout=20) - - if r.status_code != 200: - current_app.logger.error("Unable to retrieve: {0} Status Code: {1}".format(url, r.status_code)) - continue - - soup = BeautifulSoup(r.text, "lxml") - results = soup.find_all(title='Download') - for result in results: - parts = result['href'].split('/') - path = "/".join(parts[:-1]) - filename = parts[-1:][0] - r = requests.get("{0}{1}/{2}".format(endpoint, path, filename)) - - if r.status_code != 200: - current_app.logger.error("Unable to retrieve: {0} Status Code: {1}".format(url, r.status_code)) - continue - - try: - # validate we have a real certificate - cert = Certificate(r.content) - # do a lookup to see if we know about this certificate - existing = cert_service.find_duplicates(r.content) - if not existing: - current_app.logger.debug(cert.name) - cert_service.import_certificate() - new += 1 - except Exception as e: - current_app.logger.debug("Could not parse the following 'certificate': {0} Reason: {1}".format(r.content, e)) diff --git a/lemur/certificates/views.py b/lemur/certificates/views.py index 1312d665..f94011ce 100644 --- a/lemur/certificates/views.py +++ b/lemur/certificates/views.py @@ -164,7 +164,7 @@ class CertificatesList(AuthenticatedResource): 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('accountId', type=int, dest="account_id", 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') @@ -271,7 +271,7 @@ class CertificatesList(AuthenticatedResource): :statuscode 403: unauthenticated """ self.reqparse.add_argument('extensions', type=dict, location='json') - self.reqparse.add_argument('accounts', type=list, 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('owner', type=str, location='json') self.reqparse.add_argument('validityStart', type=str, location='json') # parse date @@ -330,7 +330,7 @@ class CertificatesUpload(AuthenticatedResource): "publicCert": "---Begin Public...", "intermediateCert": "---Begin Public...", "privateKey": "---Begin Private..." - "accounts": [] + "destinations": [] } **Example response**: @@ -364,19 +364,19 @@ class CertificatesUpload(AuthenticatedResource): :arg publicCert: valid PEM public key for certificate :arg intermediateCert valid PEM intermediate key for certificate :arg privateKey: valid PEM private key for certificate - :arg accounts: list of aws accounts to upload the certificate to + :arg destinations: list of aws destinations to upload the certificate to :reqheader Authorization: OAuth token to authenticate :statuscode 403: unauthenticated :statuscode 200: no error """ 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('accounts', type=list, dest='accounts', location='json') + self.reqparse.add_argument('destinations', type=list, default=[], dest='destinations', 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') args = self.reqparse.parse_args() - if args.get('accounts'): + if args.get('destinations'): if args.get('private_key'): return service.upload(**args) else: @@ -393,7 +393,7 @@ class CertificatesStats(AuthenticatedResource): def get(self): self.reqparse.add_argument('metric', type=str, location='args') self.reqparse.add_argument('range', default=32, type=int, location='args') - self.reqparse.add_argument('accountId', dest='account_id', location='args') + self.reqparse.add_argument('destinationId', dest='destination_id', location='args') self.reqparse.add_argument('active', type=str, default='true', location='args') args = self.reqparse.parse_args() diff --git a/lemur/common/utils.py b/lemur/common/utils.py index 55f411e2..b0f1ded7 100644 --- a/lemur/common/utils.py +++ b/lemur/common/utils.py @@ -41,7 +41,7 @@ class marshal_items(object): return {'items': _filter_items(resp.items), 'total': resp.total} if isinstance(resp, list): - return _filter_items(resp) + return {'items': _filter_items(resp), 'total': len(resp)} return marshal(resp, self.fields) except Exception as e: diff --git a/lemur/accounts/__init__.py b/lemur/destinations/__init__.py similarity index 100% rename from lemur/accounts/__init__.py rename to lemur/destinations/__init__.py diff --git a/lemur/destinations/models.py b/lemur/destinations/models.py new file mode 100644 index 00000000..20f910bc --- /dev/null +++ b/lemur/destinations/models.py @@ -0,0 +1,28 @@ +""" +.. module: lemur.destinations.models + :platform: unix + :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more + :license: Apache, see LICENSE for more details. +.. moduleauthor:: Kevin Glisson +""" +import copy +from sqlalchemy import Column, Integer, String, Text +from sqlalchemy_utils import JSONType +from lemur.database import db + +from lemur.plugins.base import plugins + +class Destination(db.Model): + __tablename__ = 'destinations' + id = Column(Integer, primary_key=True) + label = Column(String(32)) + options = Column(JSONType) + description = Column(Text()) + plugin_name = Column(String(32)) + + @property + def plugin(self): + p = plugins.get(self.plugin_name) + c = copy.deepcopy(p) + c.options = self.options + return c diff --git a/lemur/destinations/service.py b/lemur/destinations/service.py new file mode 100644 index 00000000..d43d64d0 --- /dev/null +++ b/lemur/destinations/service.py @@ -0,0 +1,110 @@ +""" +.. module: lemur.destinations.service + :platform: Unix + :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more + :license: Apache, see LICENSE for more details. +.. moduleauthor:: Kevin Glisson +""" +from lemur import database +from lemur.destinations.models import Destination +from lemur.certificates.models import Certificate + + +def create(label, plugin_name, options, description=None): + """ + Creates a new destination, that can then be used as a destination for certificates. + + :param label: Destination common name + :param description: + :rtype : Destination + :return: New destination + """ + destination = Destination(label=label, options=options, plugin_name=plugin_name, description=description) + return database.create(destination) + + +def update(destination_id, label, options, description): + """ + Updates an existing destination. + + :param destination_id: Lemur assigned ID + :param destination_number: AWS assigned ID + :param label: Destination common name + :param comments: + :rtype : Destination + :return: + """ + destination = get(destination_id) + + destination.label = label + description.options = options + destination.description = description + + return database.update(destination) + + +def delete(destination_id): + """ + Deletes an destination. + + :param destination_id: Lemur assigned ID + """ + database.delete(get(destination_id)) + + +def get(destination_id): + """ + Retrieves an destination by it's lemur assigned ID. + + :param destination_id: Lemur assigned ID + :rtype : Destination + :return: + """ + return database.get(Destination, destination_id) + + +def get_by_label(label): + """ + Retrieves a destination by it's label + + :param label: + :return: + """ + return database.get(Destination, label, field='label') + + +def get_all(): + """ + Retrieves all destination currently known by Lemur. + + :return: + """ + query = database.session_query(Destination) + return database.find_all(query, Destination, {}).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(Destination).join(Certificate, Destination.certificate) + query = query.filter(Certificate.id == certificate_id) + else: + query = database.session_query(Destination) + + if filt: + terms = filt.split(';') + query = database.filter(query, Destination, terms) + + query = database.find_all(query, Destination, args) + + if sort_by and sort_dir: + query = database.sort(query, Destination, sort_by, sort_dir) + + return database.paginate(query, page, count) + diff --git a/lemur/accounts/views.py b/lemur/destinations/views.py similarity index 69% rename from lemur/accounts/views.py rename to lemur/destinations/views.py index 2729ec47..10c195ca 100644 --- a/lemur/accounts/views.py +++ b/lemur/destinations/views.py @@ -1,5 +1,5 @@ """ -.. module: lemur.accounts.views +.. module: lemur.destinations.views :platform: Unix :synopsis: This module contains all of the accounts view code. :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more @@ -8,35 +8,36 @@ """ from flask import Blueprint from flask.ext.restful import Api, reqparse, fields -from lemur.accounts import service +from lemur.destinations import service from lemur.auth.service import AuthenticatedResource from lemur.auth.permissions import admin_permission from lemur.common.utils import paginated_parser, marshal_items +from lemur.plugins.views import FIELDS as PLUGIN_FIELDS -mod = Blueprint('accounts', __name__) +mod = Blueprint('destinations', __name__) api = Api(mod) FIELDS = { - 'accountNumber': fields.Integer(attribute='account_number'), + 'description': fields.String, + 'plugin': fields.Nested(PLUGIN_FIELDS, attribute='plugin'), 'label': fields.String, - 'comments': fields.String(attribute='notes'), 'id': fields.Integer, } -class AccountsList(AuthenticatedResource): - """ Defines the 'accounts' endpoint """ +class DestinationsList(AuthenticatedResource): + """ Defines the 'destinations' endpoint """ def __init__(self): self.reqparse = reqparse.RequestParser() - super(AccountsList, self).__init__() + super(DestinationsList, self).__init__() @marshal_items(FIELDS) def get(self): """ - .. http:get:: /accounts + .. http:get:: /destinations The current account list @@ -44,7 +45,7 @@ class AccountsList(AuthenticatedResource): .. sourcecode:: http - GET /accounts HTTP/1.1 + GET /destinations HTTP/1.1 Host: example.com Accept: application/json, text/javascript @@ -90,7 +91,7 @@ class AccountsList(AuthenticatedResource): @marshal_items(FIELDS) def post(self): """ - .. http:post:: /accounts + .. http:post:: /destinations Creates a new account @@ -98,7 +99,7 @@ class AccountsList(AuthenticatedResource): .. sourcecode:: http - POST /accounts HTTP/1.1 + POST /destinations HTTP/1.1 Host: example.com Accept: application/json, text/javascript @@ -129,23 +130,23 @@ class AccountsList(AuthenticatedResource): :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error """ - self.reqparse.add_argument('accountNumber', type=int, dest="account_number", location='json', required=True) self.reqparse.add_argument('label', type=str, location='json', required=True) - self.reqparse.add_argument('comments', type=str, location='json') + self.reqparse.add_argument('plugin', type=dict, location='json', required=True) + self.reqparse.add_argument('description', type=str, location='json') args = self.reqparse.parse_args() - return service.create(args['account_number'], args['label'], args['comments']) + return service.create(args['label'], args['plugin']['slug'], args['plugin']['pluginOptions'], args['description']) -class Accounts(AuthenticatedResource): +class Destinations(AuthenticatedResource): def __init__(self): self.reqparse = reqparse.RequestParser() - super(Accounts, self).__init__() + super(Destinations, self).__init__() @marshal_items(FIELDS) - def get(self, account_id): + def get(self, destination_id): """ - .. http:get:: /accounts/1 + .. http:get:: /destinations/1 Get a specific account @@ -153,7 +154,7 @@ class Accounts(AuthenticatedResource): .. sourcecode:: http - GET /accounts/1 HTTP/1.1 + GET /destinations/1 HTTP/1.1 Host: example.com Accept: application/json, text/javascript @@ -175,13 +176,13 @@ class Accounts(AuthenticatedResource): :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error """ - return service.get(account_id) + return service.get(destination_id) @admin_permission.require(http_exception=403) @marshal_items(FIELDS) - def put(self, account_id): + def put(self, destination_id): """ - .. http:put:: /accounts/1 + .. http:put:: /destinations/1 Updates an account @@ -189,15 +190,10 @@ class Accounts(AuthenticatedResource): .. sourcecode:: http - POST /accounts/1 HTTP/1.1 + POST /destinations/1 HTTP/1.1 Host: example.com Accept: application/json, text/javascript - { - "accountNumber": 11111111111, - "label": "labelChanged, - "comments": "this is a thing" - } **Example response**: @@ -220,29 +216,29 @@ class Accounts(AuthenticatedResource): :reqheader Authorization: OAuth token to authenticate :statuscode 200: no error """ - self.reqparse.add_argument('accountNumber', type=int, dest="account_number", location='json', required=True) self.reqparse.add_argument('label', type=str, location='json', required=True) - self.reqparse.add_argument('comments', type=str, location='json') + self.reqparse.add_argument('pluginOptions', type=dict, location='json', required=True) + self.reqparse.add_argument('description', type=str, location='json') args = self.reqparse.parse_args() - return service.update(account_id, args['account_number'], args['label'], args['comments']) + return service.update(destination_id, args['label'], args['options'], args['description']) @admin_permission.require(http_exception=403) - def delete(self, account_id): - service.delete(account_id) + def delete(self, destination_id): + service.delete(destination_id) return {'result': True} -class CertificateAccounts(AuthenticatedResource): - """ Defines the 'certificate/', endpoint='account') -api.add_resource(CertificateAccounts, '/certificates//accounts', endpoint='certificateAccounts') +api.add_resource(DestinationsList, '/destinations', endpoint='destinations') +api.add_resource(Destinations, '/destinations/', endpoint='account') +api.add_resource(CertificateDestinations, '/certificates//destinations', endpoint='certificateDestinations') diff --git a/lemur/elbs/models.py b/lemur/elbs/models.py index eab22933..b334df8f 100644 --- a/lemur/elbs/models.py +++ b/lemur/elbs/models.py @@ -16,7 +16,7 @@ from lemur.listeners.models import Listener class ELB(db.Model): __tablename__ = 'elbs' id = Column(BigInteger, primary_key=True) - account_id = Column(BigInteger, ForeignKey("accounts.id"), index=True) + #account_id = Column(BigInteger, ForeignKey("accounts.id"), index=True) region = Column(String(32)) name = Column(String(128)) vpc_id = Column(String(128)) diff --git a/lemur/elbs/sync.py b/lemur/elbs/sync.py index f90c7bba..f3d90ab0 100644 --- a/lemur/elbs/sync.py +++ b/lemur/elbs/sync.py @@ -12,9 +12,9 @@ """ from flask import current_app -from lemur.accounts import service as account_service +#from lemur.accounts import service as account_service from lemur.elbs import service as elb_service -from lemur.common.services.aws.elb import get_all_elbs, get_all_regions +#from lemur.common.services.aws.elb import get_all_elbs, get_all_regions def create_new(known, aws, account): diff --git a/lemur/listeners/service.py b/lemur/listeners/service.py index 5cbe2429..25c19d8e 100644 --- a/lemur/listeners/service.py +++ b/lemur/listeners/service.py @@ -18,7 +18,7 @@ from lemur.listeners.models import Listener from lemur.elbs import service as elb_service from lemur.certificates import service as certificate_service -from lemur.common.services.aws.elb import update_listeners, create_new_listeners, delete_listeners +#from lemur.common.services.aws.elb import update_listeners, create_new_listeners, delete_listeners def verify_attachment(certificate_id, elb_account_number): diff --git a/lemur/manage.py b/lemur/manage.py index 1ff0d9f5..8b255e33 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -13,9 +13,11 @@ from flask_script.commands import ShowUrls, Clean, Server from lemur import database from lemur.users import service as user_service from lemur.roles import service as role_service -from lemur.accounts import service as account_service +from lemur.destinations import service as destination_service from lemur.certificates import service as cert_service +from lemur.plugins.base import plugins + from lemur.certificates.verify import verify_string from lemur.certificates import sync from lemur.elbs.sync import sync_all_elbs @@ -27,7 +29,7 @@ from lemur.users.models import User from lemur.roles.models import Role from lemur.authorities.models import Authority from lemur.certificates.models import Certificate -from lemur.accounts.models import Account +from lemur.destinations.models import Destination from lemur.domains.models import Domain from lemur.elbs.models import ELB from lemur.listeners.models import Listener @@ -96,12 +98,12 @@ SQLALCHEMY_DATABASE_URI = '' ## AWS ## ######### -# Lemur will need STS assume role access to every account you want to monitor +# Lemur will need STS assume role access to every destination you want to monitor #AWS_ACCOUNT_MAPPINGS = {{ # '1111111111': 'myawsacount' #}} -## This is useful if you know you only want to monitor one account +## This is useful if you know you only want to monitor one destination #AWS_REGIONS = ['us-east-1'] #LEMUR_INSTANCE_PROFILE = 'Lemur' @@ -133,6 +135,11 @@ def create(): stamp(revision='head') +@MigrateCommand.command +def drop_all(): + database.db.drop_all() + + @manager.command def check_revoked(): """ @@ -227,7 +234,7 @@ class Sync(Command): class InitializeApp(Command): """ - This command will bootstrap our database with any accounts as + This command will bootstrap our database with any destinations as specified by our config. Additionally a Lemur user will be created as a default user @@ -262,15 +269,20 @@ class InitializeApp(Command): sys.stdout.write("[-] Default user has already been created, skipping...!\n") if current_app.config.get('AWS_ACCOUNT_MAPPINGS'): - for account_name, account_number in current_app.config.get('AWS_ACCOUNT_MAPPINGS').items(): - account = account_service.get_by_account_number(account_number) + if plugins.get('aws-destination'): + for account_name, account_number in current_app.config.get('AWS_ACCOUNT_MAPPINGS').items(): - if not account: - account_service.create(account_number, label=account_name) - sys.stdout.write("[+] Added new account {0}:{1}!\n".format(account_number, account_name)) - else: - sys.stdout.write("[-] Account already exists, skipping...!\n") + destination = destination_service.get_by_label(account_name) + options = dict(account_number=account_number) + if not destination: + destination_service.create(account_name, 'aws-destination', options, + description="This is an auto-generated AWS destination.") + sys.stdout.write("[+] Added new destination {0}:{1}!\n".format(account_number, account_name)) + else: + sys.stdout.write("[-] Account already exists, skipping...!\n") + else: + sys.stdout.write("[!] Skipping adding AWS destinations AWS plugin no available\n") sys.stdout.write("[/] Done!\n") diff --git a/lemur/models.py b/lemur/models.py index 7ecfa129..493e1778 100644 --- a/lemur/models.py +++ b/lemur/models.py @@ -18,8 +18,8 @@ certificate_associations = db.Table('certificate_associations', Column('certificate_id', Integer, ForeignKey('certificates.id')) ) -certificate_account_associations = db.Table('certificate_account_associations', - Column('account_id', Integer, ForeignKey('accounts.id', ondelete='cascade')), +certificate_destination_associations = db.Table('certificate_destination_associations', + Column('destination_id', Integer, ForeignKey('destinations.id', ondelete='cascade')), Column('certificate_id', Integer, ForeignKey('certificates.id', ondelete='cascade')) ) diff --git a/lemur/plugins/base/manager.py b/lemur/plugins/base/manager.py index 32234cdc..0ec270d0 100644 --- a/lemur/plugins/base/manager.py +++ b/lemur/plugins/base/manager.py @@ -8,7 +8,6 @@ from flask import current_app from lemur.common.managers import InstanceManager - # inspired by https://github.com/getsentry/sentry class PluginManager(InstanceManager): def __iter__(self): @@ -17,8 +16,10 @@ class PluginManager(InstanceManager): def __len__(self): return sum(1 for i in self.all()) - def all(self, version=1): + def all(self, version=1, plugin_type=None): for plugin in sorted(super(PluginManager, self).all(), key=lambda x: x.get_title()): + if not plugin.type == plugin_type and plugin_type: + continue if not plugin.is_enabled(): continue if version is not None and plugin.__version__ != version: diff --git a/lemur/plugins/base/v1.py b/lemur/plugins/base/v1.py index 448e6d95..2055577b 100644 --- a/lemur/plugins/base/v1.py +++ b/lemur/plugins/base/v1.py @@ -47,12 +47,13 @@ class IPlugin(local): # Configuration specifics conf_key = None conf_title = None + options = {} # Global enabled state enabled = True can_disable = True - def is_enabled(self, project=None): + def is_enabled(self): """ Returns a boolean representing if this plugin is enabled. If ``project`` is passed, it will limit the scope to that project. diff --git a/lemur/plugins/bases/__init__.py b/lemur/plugins/bases/__init__.py index d43aa85e..2e501d35 100644 --- a/lemur/plugins/bases/__init__.py +++ b/lemur/plugins/bases/__init__.py @@ -1,2 +1,3 @@ from .destination import DestinationPlugin # NOQA -from .issuer import IssuerPlugin # NOQA \ No newline at end of file +from .issuer import IssuerPlugin # NOQA +from .source import SourcePlugin \ No newline at end of file diff --git a/lemur/plugins/bases/destination.py b/lemur/plugins/bases/destination.py index b95d2c7e..b3dcef5f 100644 --- a/lemur/plugins/bases/destination.py +++ b/lemur/plugins/bases/destination.py @@ -9,5 +9,8 @@ from lemur.plugins.base import Plugin class DestinationPlugin(Plugin): - pass + type = 'destination' + + def upload(self): + raise NotImplemented diff --git a/lemur/plugins/bases/issuer.py b/lemur/plugins/bases/issuer.py index b2e5c964..bfa7dbd6 100644 --- a/lemur/plugins/bases/issuer.py +++ b/lemur/plugins/bases/issuer.py @@ -13,6 +13,8 @@ class IssuerPlugin(Plugin): This is the base class from which all of the supported issuers will inherit from. """ + type = 'issuer' + def create_certificate(self): raise NotImplementedError diff --git a/lemur/plugins/bases/source.py b/lemur/plugins/bases/source.py index e69de29b..a706acf2 100644 --- a/lemur/plugins/bases/source.py +++ b/lemur/plugins/bases/source.py @@ -0,0 +1,19 @@ +""" +.. module: lemur.bases.source + :platform: Unix + :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more + :license: Apache, see LICENSE for more details. + +.. moduleauthor:: Kevin Glisson +""" +from lemur.plugins.base import Plugin + +class SourcePlugin(Plugin): + type = 'source' + + def get_certificates(self): + raise NotImplemented + + def get_options(self): + return {} + diff --git a/lemur/plugins/lemur_aws/__init__.py b/lemur/plugins/lemur_aws/__init__.py index e69de29b..d29488d2 100644 --- a/lemur/plugins/lemur_aws/__init__.py +++ b/lemur/plugins/lemur_aws/__init__.py @@ -0,0 +1,5 @@ +try: + VERSION = __import__('pkg_resources') \ + .get_distribution(__name__).version +except Exception, e: + VERSION = 'unknown' \ No newline at end of file diff --git a/lemur/common/services/aws/elb.py b/lemur/plugins/lemur_aws/elb.py similarity index 98% rename from lemur/common/services/aws/elb.py rename to lemur/plugins/lemur_aws/elb.py index b59b8eaf..1edfd5b4 100644 --- a/lemur/common/services/aws/elb.py +++ b/lemur/plugins/lemur_aws/elb.py @@ -10,7 +10,7 @@ import boto.ec2 from flask import current_app from lemur.exceptions import InvalidListener -from lemur.common.services.aws.sts import assume_service +from lemur.plugins.lemur_aws.sts import assume_service def is_valid(listener_tuple): diff --git a/lemur/common/services/aws/iam.py b/lemur/plugins/lemur_aws/iam.py similarity index 90% rename from lemur/common/services/aws/iam.py rename to lemur/plugins/lemur_aws/iam.py index 4cd2b452..fa8e50e0 100644 --- a/lemur/common/services/aws/iam.py +++ b/lemur/plugins/lemur_aws/iam.py @@ -6,8 +6,17 @@ :license: Apache, see LICENSE for more details. .. moduleauthor:: Kevin Glisson """ -from flask import current_app -from lemur.common.services.aws.sts import assume_service +from lemur.plugins.lemur_aws.sts import assume_service + + +def get_name_from_arn(arn): + """ + Extract the certificate name from an arn. + + :param arn: IAM SSL arn + :return: name of the certificate as uploaded to AWS + """ + return arn.split("/", 1)[1] def ssl_split(param_string): diff --git a/lemur/plugins/lemur_aws/plugin.py b/lemur/plugins/lemur_aws/plugin.py new file mode 100644 index 00000000..35800f51 --- /dev/null +++ b/lemur/plugins/lemur_aws/plugin.py @@ -0,0 +1,77 @@ +""" +.. 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 +""" +from lemur.plugins.bases import DestinationPlugin, SourcePlugin +from lemur.plugins.lemur_aws import iam, elb +from lemur.plugins import lemur_aws as aws + + +def find_value(name, options): + for o in options: + if o.get(name): + return o['value'] + + +class AWSDestinationPlugin(DestinationPlugin): + title = 'AWS' + slug = 'aws-destination' + description = 'Allow the uploading of certificates to AWS IAM' + version = aws.VERSION + + author = 'Kevin Glisson' + author_url = 'https://github.com/netflix/lemur' + + options = [ + { + 'name': 'accountNumber', + 'type': 'int', + 'required': True, + 'validation': '/^[0-9]{12,12}$/', + 'helpMessage': 'Must be a valid AWS account number!', + } + ] + #'elb': { + # 'name': {'type': 'name'}, + # 'region': {'type': 'str'}, + # 'port': {'type': 'int'} + #} + + def upload(self, cert, private_key, cert_chain, options, **kwargs): + iam.upload_cert(find_value('accountNumber', options), cert, private_key, cert_chain=cert_chain) + + e = find_value('elb', options) + if e: + elb.attach_certificate(kwargs['accountNumber'], ['region'], e['name'], e['port'], e['certificateId']) + + +class AWSSourcePlugin(SourcePlugin): + title = 'AWS' + slug = 'aws-source' + description = 'Discovers all SSL certificates in an AWS account' + version = aws.VERSION + + author = 'Kevin Glisson' + author_url = 'https://github.com/netflix/lemur' + + options = { + 'accountNumber': {'type': 'int'}, + 'pollRate': {'type': 'int', 'default': '60'} + } + + def get_certificates(self, **kwargs): + certs = [] + arns = elb.get_all_server_certs(kwargs['account_number']) + for arn in arns: + cert_body = iam.get_cert_from_arn(arn) + cert_name = iam.get_name_from_arn(arn) + cert = dict( + public_certificate=cert_body, + name=cert_name + ) + certs.append(cert) + return certs diff --git a/lemur/common/services/aws/sts.py b/lemur/plugins/lemur_aws/sts.py similarity index 100% rename from lemur/common/services/aws/sts.py rename to lemur/plugins/lemur_aws/sts.py diff --git a/lemur/plugins/lemur_cloudca/plugin.py b/lemur/plugins/lemur_cloudca/plugin.py index 8d42cb3b..68de48d3 100644 --- a/lemur/plugins/lemur_cloudca/plugin.py +++ b/lemur/plugins/lemur_cloudca/plugin.py @@ -18,12 +18,12 @@ from requests.adapters import HTTPAdapter from flask import current_app from lemur.exceptions import LemurException -from lemur.plugins.bases import IssuerPlugin +from lemur.plugins.bases import IssuerPlugin, SourcePlugin from lemur.plugins import lemur_cloudca as cloudca from lemur.authorities import service as authority_service -API_ENDPOINT = '/v1/ca/netflix' +API_ENDPOINT = '/v1/ca/netflix' # TODO this should be configurable class CloudCAException(LemurException): @@ -142,15 +142,7 @@ def get_auth_data(ca_name): raise CloudCAException("You do not have the required role to issue certificates from {0}".format(ca_name)) -class CloudCAPlugin(IssuerPlugin): - title = 'CloudCA' - slug = 'cloudca' - description = 'Enables the creation of certificates from the cloudca API.' - version = cloudca.VERSION - - author = 'Kevin Glisson' - author_url = 'https://github.com/netflix/lemur' - +class CloudCA(object): def __init__(self, *args, **kwargs): self.session = requests.Session() self.session.mount('https://', CloudCAHostNameCheckingAdapter()) @@ -162,7 +154,69 @@ class CloudCAPlugin(IssuerPlugin): else: current_app.logger.warning("No CLOUDCA credentials found, lemur will be unable to request certificates from CLOUDCA") - super(CloudCAPlugin, self).__init__(*args, **kwargs) + super(CloudCA, self).__init__(*args, **kwargs) + + def post(self, endpoint, data): + """ + HTTP POST to CloudCA + + :param endpoint: + :param data: + :return: + """ + data = dumps(dict(data.items() + get_auth_data(data['caName']).items())) + + # we set a low timeout, if cloudca is down it shouldn't bring down + # lemur + response = self.session.post(self.url + endpoint, data=data, timeout=10, verify=self.ca_bundle) + return process_response(response) + + def get(self, endpoint): + """ + HTTP GET to CloudCA + + :param endpoint: + :return: + """ + response = self.session.get(self.url + endpoint, timeout=10, verify=self.ca_bundle) + return process_response(response) + + def random(self, length=10): + """ + Uses CloudCA as a decent source of randomness. + + :param length: + :return: + """ + endpoint = '/v1/random/{0}'.format(length) + response = self.session.get(self.url + endpoint, verify=self.ca_bundle) + return response + + def get_authorities(self): + """ + Retrieves authorities that were made outside of Lemur. + + :return: + """ + endpoint = '{0}/listCAs'.format(API_ENDPOINT) + authorities = [] + for ca in self.get(endpoint)['data']['caList']: + try: + authorities.append(ca['caName']) + except AttributeError as e: + current_app.logger.error("No authority has been defined for {}".format(ca['caName'])) + + return authorities + + +class CloudCAIssuerPlugin(IssuerPlugin, CloudCA): + title = 'CloudCA' + slug = 'cloudca-issuer' + description = 'Enables the creation of certificates from the cloudca API.' + version = cloudca.VERSION + + author = 'Kevin Glisson' + author_url = 'https://github.com/netflix/lemur' def create_authority(self, options): """ @@ -205,22 +259,6 @@ class CloudCAPlugin(IssuerPlugin): return cert, "".join(intermediates), roles, - def get_authorities(self): - """ - Retrieves authorities that were made outside of Lemur. - - :return: - """ - endpoint = '{0}/listCAs'.format(API_ENDPOINT) - authorities = [] - for ca in self.get(endpoint)['data']['caList']: - try: - authorities.append(ca['caName']) - except AttributeError as e: - current_app.logger.error("No authority has been defined for {}".format(ca['caName'])) - - return authorities - def create_certificate(self, csr, options): """ Creates a new certificate from cloudca @@ -259,16 +297,25 @@ class CloudCAPlugin(IssuerPlugin): return cert, "".join(intermediates), - def random(self, length=10): - """ - Uses CloudCA as a decent source of randomness. - :param length: - :return: - """ - endpoint = '/v1/random/{0}'.format(length) - response = self.session.get(self.url + endpoint, verify=self.ca_bundle) - return response +class CloudCASourcePlugin(SourcePlugin, CloudCA): + title = 'CloudCA' + slug = 'cloudca-source' + description = 'Discovers all SSL certificates in CloudCA' + version = cloudca.VERSION + + author = 'Kevin Glisson' + author_url = 'https://github.com/netflix/lemur' + + options = { + 'pollRate': {'type': 'int', 'default': '60'} + } + + def get_certificates(self, **kwargs): + certs = [] + for authority in self.get_authorities(): + certs += self.get_cert(ca_name=authority) + return def get_cert(self, ca_name=None, cert_handle=None): """ @@ -297,29 +344,3 @@ class CloudCAPlugin(IssuerPlugin): }) return certs - - def post(self, endpoint, data): - """ - HTTP POST to CloudCA - - :param endpoint: - :param data: - :return: - """ - data = dumps(dict(data.items() + get_auth_data(data['caName']).items())) - - # we set a low timeout, if cloudca is down it shouldn't bring down - # lemur - response = self.session.post(self.url + endpoint, data=data, timeout=10, verify=self.ca_bundle) - return process_response(response) - - def get(self, endpoint): - """ - HTTP GET to CloudCA - - :param endpoint: - :return: - """ - response = self.session.get(self.url + endpoint, timeout=10, verify=self.ca_bundle) - return process_response(response) - diff --git a/lemur/plugins/lemur_verisign/plugin.py b/lemur/plugins/lemur_verisign/plugin.py index 59adaeaa..eb00907d 100644 --- a/lemur/plugins/lemur_verisign/plugin.py +++ b/lemur/plugins/lemur_verisign/plugin.py @@ -75,9 +75,9 @@ def handle_response(content): return d -class VerisignPlugin(IssuerPlugin): - title = 'VeriSign' - slug = 'verisign' +class VerisignIssuerPlugin(IssuerPlugin): + title = 'Verisign' + slug = 'verisign-issuer' description = 'Enables the creation of certificates by the VICE2.0 verisign API.' version = verisign.VERSION @@ -87,7 +87,7 @@ class VerisignPlugin(IssuerPlugin): def __init__(self, *args, **kwargs): self.session = requests.Session() self.session.cert = current_app.config.get('VERISIGN_PEM_PATH') - super(VerisignPlugin, self).__init__(*args, **kwargs) + super(VerisignIssuerPlugin, self).__init__(*args, **kwargs) def create_certificate(self, csr, issuer_options): """ diff --git a/lemur/plugins/lemur_aws/aws.py b/lemur/plugins/service.py similarity index 100% rename from lemur/plugins/lemur_aws/aws.py rename to lemur/plugins/service.py diff --git a/lemur/plugins/views.py b/lemur/plugins/views.py new file mode 100644 index 00000000..a1b7a000 --- /dev/null +++ b/lemur/plugins/views.py @@ -0,0 +1,140 @@ +""" +.. module: lemur.plugins.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 +""" +from flask import Blueprint +from flask.ext.restful import Api, reqparse, fields +from lemur.auth.service import AuthenticatedResource + +from lemur.common.utils import marshal_items + +from lemur.plugins.base import plugins + +mod = Blueprint('plugins', __name__) +api = Api(mod) + + +FIELDS = { + 'title': fields.String, + 'pluginOptions': fields.Raw(attribute='options'), + 'description': fields.String, + 'version': fields.String, + 'author': fields.String, + 'authorUrl': fields.String, + 'type': fields.String, + 'slug': fields.String, +} + + +class PluginsList(AuthenticatedResource): + """ Defines the 'plugins' endpoint """ + def __init__(self): + self.reqparse = reqparse.RequestParser() + super(PluginsList, self).__init__() + + @marshal_items(FIELDS) + def get(self): + """ + .. http:get:: /plugins + + The current plugin list + + **Example request**: + + .. sourcecode:: http + + GET /plugins 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": 2, + "accountNumber": 222222222, + "label": "account2", + "comments": "this is a thing" + }, + { + "id": 1, + "accountNumber": 11111111111, + "label": "account1", + "comments": "this is a thing" + }, + ] + "total": 2 + } + + :reqheader Authorization: OAuth token to authenticate + :statuscode 200: no error + """ + return plugins.all() + + +class PluginsTypeList(AuthenticatedResource): + """ Defines the 'plugins' endpoint """ + def __init__(self): + self.reqparse = reqparse.RequestParser() + super(PluginsTypeList, self).__init__() + + @marshal_items(FIELDS) + def get(self, plugin_type): + """ + .. http:get:: /plugins/issuer + + The current plugin list + + **Example request**: + + .. sourcecode:: http + + GET /plugins/issuer 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": 2, + "accountNumber": 222222222, + "label": "account2", + "comments": "this is a thing" + }, + { + "id": 1, + "accountNumber": 11111111111, + "label": "account1", + "comments": "this is a thing" + }, + ] + "total": 2 + } + + :reqheader Authorization: OAuth token to authenticate + :statuscode 200: no error + """ + return list(plugins.all(plugin_type=plugin_type)) + +api.add_resource(PluginsList, '/plugins', endpoint='plugins') +api.add_resource(PluginsTypeList, '/plugins/', endpoint='pluginType') + diff --git a/lemur/static/app/angular/accounts/account/account.js b/lemur/static/app/angular/accounts/account/account.js deleted file mode 100644 index 34281f49..00000000 --- a/lemur/static/app/angular/accounts/account/account.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -angular.module('lemur') - - .config(function config($routeProvider) { - $routeProvider.when('/accounts/create', { - templateUrl: '/angular/accounts/account/account.tpl.html', - controller: 'AccountsCreateController' - }); - $routeProvider.when('/accounts/:id/edit', { - templateUrl: '/angular/accounts/account/account.tpl.html', - controller: 'AccountsEditController' - }); - }) - - .controller('AccountsCreateController', function ($scope, AccountService, LemurRestangular){ - $scope.account = LemurRestangular.restangularizeElement(null, {}, 'accounts'); - $scope.save = AccountService.create; - }) - - .controller('AccountsEditController', function ($scope, $routeParams, AccountService, AccountApi) { - AccountApi.get($routeParams.id).then(function (account) { - $scope.account = account; - }); - - $scope.save = AccountService.update; - }); diff --git a/lemur/static/app/angular/accounts/account/account.tpl.html b/lemur/static/app/angular/accounts/account/account.tpl.html deleted file mode 100644 index c30b91fd..00000000 --- a/lemur/static/app/angular/accounts/account/account.tpl.html +++ /dev/null @@ -1,45 +0,0 @@ -

CreateEdit Account next in line please -

-
-
- Cancel -
-
-
-
-
- -
- -

You must enter an account name

-
-
-
- -
- -

You must enter an account number

-
-
-
- -
- -
-
-
-
- -
- diff --git a/lemur/static/app/angular/accounts/services.js b/lemur/static/app/angular/accounts/services.js deleted file mode 100644 index 00bff23a..00000000 --- a/lemur/static/app/angular/accounts/services.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; -angular.module('lemur') - .service('AccountApi', function (LemurRestangular) { - return LemurRestangular.all('accounts'); - }) - .service('AccountService', function ($location, AccountApi, toaster) { - var AccountService = this; - AccountService.findAccountsByName = function (filterValue) { - return AccountApi.getList({'filter[label]': filterValue}) - .then(function (accounts) { - return accounts; - }); - }; - - AccountService.create = function (account) { - AccountApi.post(account).then( - function () { - toaster.pop({ - type: 'success', - title: account.label, - body: 'Successfully created!' - }); - $location.path('accounts'); - }, - function (response) { - toaster.pop({ - type: 'error', - title: account.label, - body: 'Was not created! ' + response.data.message - }); - }); - }; - - AccountService.update = function (account) { - account.put().then( - function () { - toaster.pop({ - type: 'success', - title: account.label, - body: 'Successfully updated!' - }); - $location.path('accounts'); - }, - function (response) { - toaster.pop({ - type: 'error', - title: account.label, - body: 'Was not updated! ' + response.data.message - }); - }); - }; - return AccountService; - }); diff --git a/lemur/static/app/angular/accounts/view/view.js b/lemur/static/app/angular/accounts/view/view.js deleted file mode 100644 index 447ee97c..00000000 --- a/lemur/static/app/angular/accounts/view/view.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -angular.module('lemur') - - .config(function config($routeProvider) { - $routeProvider.when('/accounts', { - templateUrl: '/angular/accounts/view/view.tpl.html', - controller: 'AccountsViewController' - }); - }) - - .controller('AccountsViewController', function ($scope, AccountApi, AccountService, ngTableParams, toaster) { - $scope.filter = {}; - $scope.accountsTable = new ngTableParams({ - page: 1, // show first page - count: 10, // count per page - sorting: { - id: 'desc' // initial sorting - }, - filter: $scope.filter - }, { - total: 0, // length of data - getData: function ($defer, params) { - AccountApi.getList(params.url()).then( - function (data) { - params.total(data.total); - $defer.resolve(data); - } - ); - } - }); - - $scope.remove = function (account) { - account.remove().then( - function () { - $scope.accountsTable.reload(); - }, - function (response) { - toaster.pop({ - type: 'error', - title: 'Opps', - body: 'I see what you did there' + response.data.message - }); - } - ); - }; - - $scope.toggleFilter = function (params) { - params.settings().$scope.show_filter = !params.settings().$scope.show_filter; - }; - - }); diff --git a/lemur/static/app/angular/accounts/view/view.tpl.html b/lemur/static/app/angular/accounts/view/view.tpl.html deleted file mode 100644 index 176a4fae..00000000 --- a/lemur/static/app/angular/accounts/view/view.tpl.html +++ /dev/null @@ -1,44 +0,0 @@ -
-
-

Accounts - next in line please

-
-
-
- Create -
-
- -
-
-
-
- - - - - - - - -
-
    -
  • {{ account.label }}
  • -
  • {{ account.comments }}
  • -
-
- {{ account.accountNumber }} - -
- - Edit - - -
-
-
-
-
-
diff --git a/lemur/static/app/angular/authorities/authority/authority.js b/lemur/static/app/angular/authorities/authority/authority.js index 6129b250..e5f4ffc2 100644 --- a/lemur/static/app/angular/authorities/authority/authority.js +++ b/lemur/static/app/angular/authorities/authority/authority.js @@ -2,17 +2,6 @@ angular.module('lemur') - .config(function config($routeProvider) { - $routeProvider.when('/authorities/create', { - templateUrl: '/angular/authorities/authority/authorityWizard.tpl.html', - controller: 'AuthorityCreateController' - }); - $routeProvider.when('/authorities/:id/edit', { - templateUrl: '/angular/authorities/authority/authorityEdit.tpl.html', - controller: 'AuthorityEditController' - }); - }) - .controller('AuthorityEditController', function ($scope, $routeParams, AuthorityApi, AuthorityService, RoleService){ AuthorityApi.get($routeParams.id).then(function (authority) { AuthorityService.getRoles(authority); @@ -24,16 +13,21 @@ angular.module('lemur') $scope.roleService = RoleService; }) - .controller('AuthorityCreateController', function ($scope, $modal, AuthorityService, LemurRestangular, RoleService) { + .controller('AuthorityCreateController', function ($scope, $modalInstance, AuthorityService, LemurRestangular, RoleService, PluginService, WizardHandler) { $scope.authority = LemurRestangular.restangularizeElement(null, {}, 'authorities'); - $scope.save = function (authority) { - var loadingModal = $modal.open({backdrop: 'static', template: '', windowTemplateUrl: 'angular/loadingModal.html', size: 'large'}); - return AuthorityService.create(authority).then(function (response) { - loadingModal.close(); - }); + $scope.loading = false; + $scope.create = function (authority) { + WizardHandler.wizard().context.loading = true; + AuthorityService.create(authority).then(function (resposne) { + WizardHandler.wizard().context.loading = false; + $modalInstance.close(); + }) }; + PluginService.get('issuer').then(function (plugins) { + $scope.plugins = plugins; + }); $scope.roleService = RoleService; diff --git a/lemur/static/app/angular/authorities/authority/authorityWizard.tpl.html b/lemur/static/app/angular/authorities/authority/authorityWizard.tpl.html index 70cc0e5f..8eef96f1 100644 --- a/lemur/static/app/angular/authorities/authority/authorityWizard.tpl.html +++ b/lemur/static/app/angular/authorities/authority/authorityWizard.tpl.html @@ -1,17 +1,21 @@ -

CreateEdit Authority The nail that sticks out farthest gets hammered the hardest -
- - - - - - - - - - - - - - + + diff --git a/lemur/static/app/angular/authorities/authority/options.tpl.html b/lemur/static/app/angular/authorities/authority/options.tpl.html index 3cb0b912..26c6fdc6 100644 --- a/lemur/static/app/angular/authorities/authority/options.tpl.html +++ b/lemur/static/app/angular/authorities/authority/options.tpl.html @@ -15,7 +15,7 @@
@@ -69,10 +69,10 @@
- +
diff --git a/lemur/static/app/angular/authorities/authority/tracking.tpl.html b/lemur/static/app/angular/authorities/authority/tracking.tpl.html index 161ce2a0..5d9e9b58 100644 --- a/lemur/static/app/angular/authorities/authority/tracking.tpl.html +++ b/lemur/static/app/angular/authorities/authority/tracking.tpl.html @@ -31,37 +31,40 @@
+ ng-class="{'has-error': trackingForm.commonName.$invalid, 'has-success': !trackingForm.$invalid&&trackingForm.commonName.$dirty}">
-

You must enter a common name

+

You must enter a common name

-
+
- - - - + +

A start date is required!

+ + +
- +
- - - - + +

A end date is required!

+ + +
diff --git a/lemur/static/app/angular/authorities/view/view.js b/lemur/static/app/angular/authorities/view/view.js index 0b93466d..0208307f 100644 --- a/lemur/static/app/angular/authorities/view/view.js +++ b/lemur/static/app/angular/authorities/view/view.js @@ -9,7 +9,7 @@ angular.module('lemur') }); }) - .controller('AuthoritiesViewController', function ($scope, $q, AuthorityApi, AuthorityService, ngTableParams) { + .controller('AuthoritiesViewController', function ($scope, $q, $modal, AuthorityApi, AuthorityService, ngTableParams) { $scope.filter = {}; $scope.authoritiesTable = new ngTableParams({ page: 1, // show first page @@ -43,4 +43,36 @@ angular.module('lemur') params.settings().$scope.show_filter = !params.settings().$scope.show_filter; }; + $scope.edit = function (authorityId) { + var modalInstance = $modal.open({ + animation: true, + templateUrl: '/angular/authorities/authority/authorityWizard.tpl.html', + controller: 'AuthorityEditController', + size: 'lg', + resolve: { + editId: function () { + return authorityId; + } + } + }); + + modalInstance.result.then(function () { + $scope.authoritiesTable.reload(); + }); + + }; + + $scope.create = function () { + var modalInstance = $modal.open({ + animation: true, + controller: 'AuthorityCreateController', + templateUrl: '/angular/authorities/authority/authorityWizard.tpl.html', + size: 'lg' + }); + + modalInstance.result.then(function () { + $scope.authoritiesTable.reload(); + }); + + }; }); diff --git a/lemur/static/app/angular/authorities/view/view.tpl.html b/lemur/static/app/angular/authorities/view/view.tpl.html index 9bbbaeb7..f6a60c3a 100644 --- a/lemur/static/app/angular/authorities/view/view.tpl.html +++ b/lemur/static/app/angular/authorities/view/view.tpl.html @@ -5,7 +5,7 @@
- Create +
@@ -36,9 +36,9 @@ diff --git a/lemur/static/app/angular/certificates/certificate/certificate.js b/lemur/static/app/angular/certificates/certificate/certificate.js index 4815121e..c2c47f0f 100644 --- a/lemur/static/app/angular/certificates/certificate/certificate.js +++ b/lemur/static/app/angular/certificates/certificate/certificate.js @@ -1,18 +1,6 @@ 'use strict'; angular.module('lemur') - .config(function config($routeProvider) { - $routeProvider.when('/certificates/create', { - templateUrl: '/angular/certificates/certificate/certificateWizard.tpl.html', - controller: 'CertificateCreateController' - }); - - $routeProvider.when('/certificates/:id/edit', { - templateUrl: '/angular/certificates/certificate/edit.tpl.html', - controller: 'CertificateEditController' - }); - }) - .controller('CertificateEditController', function ($scope, $routeParams, CertificateApi, CertificateService, MomentService) { CertificateApi.get($routeParams.id).then(function (certificate) { $scope.certificate = certificate; @@ -23,13 +11,14 @@ angular.module('lemur') }) - .controller('CertificateCreateController', function ($scope, $modal, CertificateApi, CertificateService, AccountService, ELBService, AuthorityService, MomentService, LemurRestangular) { + .controller('CertificateCreateController', function ($scope, $modalInstance, CertificateApi, CertificateService, DestinationService, ELBService, AuthorityService, PluginService, MomentService, WizardHandler, LemurRestangular) { $scope.certificate = LemurRestangular.restangularizeElement(null, {}, 'certificates'); - $scope.save = function (certificate) { - var loadingModal = $modal.open({backdrop: 'static', template: '', windowTemplateUrl: 'angular/loadingModal.html', size: 'large'}); + $scope.create = function (certificate) { + WizardHandler.wizard().context.loading = true; CertificateService.create(certificate).then(function (response) { - loadingModal.close(); + WizardHandler.wizard().context.loading = false; + $modalInstance.close(); }); }; @@ -88,7 +77,11 @@ angular.module('lemur') }; + PluginService.get('destination').then(function (plugins) { + $scope.plugins = plugins; + }); + $scope.elbService = ELBService; $scope.authorityService = AuthorityService; - $scope.accountService = AccountService; + $scope.destinationService = DestinationService; }); diff --git a/lemur/static/app/angular/certificates/certificate/certificate.tpl.html b/lemur/static/app/angular/certificates/certificate/certificate.tpl.html deleted file mode 100644 index 73d63a26..00000000 --- a/lemur/static/app/angular/certificates/certificate/certificate.tpl.html +++ /dev/null @@ -1,20 +0,0 @@ -

Create a certificate encrypt all the things -

-
-
- Cancel -
-
-
-
- -
-
- -
diff --git a/lemur/static/app/angular/certificates/certificate/certificateWizard.tpl.html b/lemur/static/app/angular/certificates/certificate/certificateWizard.tpl.html index dbd7595f..a7e36c1f 100644 --- a/lemur/static/app/angular/certificates/certificate/certificateWizard.tpl.html +++ b/lemur/static/app/angular/certificates/certificate/certificateWizard.tpl.html @@ -1,17 +1,23 @@ -

CreateEdit Certificate encrypt all the things -
- - - - - - - - - - - - - - -
+ + + + diff --git a/lemur/static/app/angular/certificates/certificate/destinations.tpl.html b/lemur/static/app/angular/certificates/certificate/destinations.tpl.html index 443d6fab..58d88dc7 100644 --- a/lemur/static/app/angular/certificates/certificate/destinations.tpl.html +++ b/lemur/static/app/angular/certificates/certificate/destinations.tpl.html @@ -1,62 +1,28 @@ -

Destinations are purely optional, if you think the created certificate will be used in AWS select one or more accounts and Lemur will upload it for you.

-
-
+
- -
- - - + + +
{{ account.label }}{{ account.comments }}
{{ destination.label }}{{ destination.description }} - +
-
-
diff --git a/lemur/static/app/angular/certificates/certificate/tracking.tpl.html b/lemur/static/app/angular/certificates/certificate/tracking.tpl.html index 4dab6044..8ceb50c0 100644 --- a/lemur/static/app/angular/certificates/certificate/tracking.tpl.html +++ b/lemur/static/app/angular/certificates/certificate/tracking.tpl.html @@ -47,7 +47,7 @@ Common Name
- +

You must enter a common name

@@ -65,7 +65,7 @@

- +
diff --git a/lemur/static/app/angular/certificates/certificate/upload.js b/lemur/static/app/angular/certificates/certificate/upload.js index de2c88b4..0bf57b4f 100644 --- a/lemur/static/app/angular/certificates/certificate/upload.js +++ b/lemur/static/app/angular/certificates/certificate/upload.js @@ -2,20 +2,16 @@ angular.module('lemur') - .config(function config($routeProvider) { - $routeProvider.when('/certificates/upload', { - templateUrl: '/angular/certificates/certificate/upload.tpl.html', - controller: 'CertificatesUploadController' - }); - }) - - .controller('CertificatesUploadController', function ($scope, CertificateService, LemurRestangular, AccountService, ELBService) { + .controller('CertificateUploadController', function ($scope, $modalInstance, CertificateService, LemurRestangular, DestinationService, ELBService, PluginService) { $scope.certificate = LemurRestangular.restangularizeElement(null, {}, 'certificates'); $scope.upload = CertificateService.upload; - $scope.accountService = AccountService; + $scope.destinationService = DestinationService; $scope.elbService = ELBService; + PluginService.get('destination').then(function (plugins) { + $scope.plugins = plugins; + }); $scope.attachELB = function (elb) { $scope.certificate.attachELB(elb); @@ -23,4 +19,9 @@ angular.module('lemur') $scope.certificate.elb.listeners = listeners; }); }; + + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + }); diff --git a/lemur/static/app/angular/certificates/certificate/upload.tpl.html b/lemur/static/app/angular/certificates/certificate/upload.tpl.html index adfebba9..afefa5e3 100644 --- a/lemur/static/app/angular/certificates/certificate/upload.tpl.html +++ b/lemur/static/app/angular/certificates/certificate/upload.tpl.html @@ -1,136 +1,71 @@ -

Upload a certificate encrypt all the things -

-
-
- Cancel -
-
-
-
-
- + diff --git a/lemur/static/app/angular/certificates/services.js b/lemur/static/app/angular/certificates/services.js index b5dd898e..bb7eba53 100644 --- a/lemur/static/app/angular/certificates/services.js +++ b/lemur/static/app/angular/certificates/services.js @@ -58,15 +58,15 @@ angular.module('lemur') removeCustom: function (index) { this.extensions.custom.splice(index, 1); }, - attachAccount: function (account) { - this.selectedAccount = null; - if (this.accounts === undefined) { - this.accounts = []; + attachDestination: function (destination) { + this.selectedDestination = null; + if (this.destinations === undefined) { + this.destinations = []; } - this.accounts.push(account); + this.destinations.push(destination); }, - removeAccount: function (index) { - this.accounts.splice(index, 1); + removeDestination: function (index) { + this.destinations.splice(index, 1); }, attachELB: function (elb) { this.selectedELB = null; @@ -99,13 +99,6 @@ angular.module('lemur') }); }; - CertificateService.getARNs = function (certificate) { - certificate.arns = []; - _.each(certificate.accounts, function (account) { - certificate.arns.push('arn:aws:iam::' + account.accountNumber + ':server-certificate/' + certificate.name); - }); - }; - CertificateService.create = function (certificate) { certificate.attachSubAltName(); return CertificateApi.post(certificate).then( @@ -191,10 +184,9 @@ angular.module('lemur') }); }; - CertificateService.getAccounts = function (certificate) { - certificate.getList('accounts').then(function (accounts) { - certificate.accounts = accounts; - CertificateService.getARNs(certificate); + CertificateService.getDestinations = function (certificate) { + certificate.getList('destinations').then(function (destinations) { + certificate.destinations = destinations; }); }; diff --git a/lemur/static/app/angular/certificates/view/view.js b/lemur/static/app/angular/certificates/view/view.js index eba4e63f..230f9181 100644 --- a/lemur/static/app/angular/certificates/view/view.js +++ b/lemur/static/app/angular/certificates/view/view.js @@ -9,7 +9,7 @@ angular.module('lemur') }); }) - .controller('CertificatesViewController', function ($q, $scope, CertificateApi, CertificateService, MomentService, ngTableParams) { + .controller('CertificatesViewController', function ($q, $scope, $modal, CertificateApi, CertificateService, MomentService, ngTableParams) { $scope.filter = {}; $scope.certificateTable = new ngTableParams({ page: 1, // show first page @@ -26,7 +26,7 @@ angular.module('lemur') // TODO we should attempt to resolve all of these in parallel _.each(data, function (certificate) { CertificateService.getDomains(certificate); - CertificateService.getAccounts(certificate); + CertificateService.getDestinations(certificate); CertificateService.getListeners(certificate); CertificateService.getAuthority(certificate); CertificateService.getCreator(certificate); @@ -60,4 +60,30 @@ angular.module('lemur') $scope.toggleFilter = function (params) { params.settings().$scope.show_filter = !params.settings().$scope.show_filter; }; + + $scope.create = function () { + var modalInstance = $modal.open({ + animation: true, + controller: 'CertificateCreateController', + templateUrl: '/angular/certificates/certificate/certificateWizard.tpl.html', + size: 'lg' + }); + + modalInstance.result.then(function () { + $scope.certificateTable.reload(); + }); + }; + + $scope.import = function () { + var modalInstance = $modal.open({ + animation: true, + controller: 'CertificateUploadController', + templateUrl: '/angular/certificates/certificate/upload.tpl.html', + size: 'lg' + }); + + modalInstance.result.then(function () { + $scope.certificateTable.reload(); + }); + }; }); diff --git a/lemur/static/app/angular/certificates/view/view.tpl.html b/lemur/static/app/angular/certificates/view/view.tpl.html index d7bac81e..abe3c5f3 100644 --- a/lemur/static/app/angular/certificates/view/view.tpl.html +++ b/lemur/static/app/angular/certificates/view/view.tpl.html @@ -5,14 +5,14 @@
@@ -30,9 +30,9 @@
  • {{ certificate.owner }}
  • - + @@ -106,7 +106,7 @@ -

    ARNs

    +

    ARNs

    • {{ arn }}
    diff --git a/lemur/static/app/angular/components/filters.js b/lemur/static/app/angular/components/filters.js index 96471a44..6229e25d 100644 --- a/lemur/static/app/angular/components/filters.js +++ b/lemur/static/app/angular/components/filters.js @@ -1,8 +1,8 @@ angular.module('lemur'). filter('titleCase', function () { return function (str) { - return (str === undefined || str === null) ? '' : str.replace(/_|-/, ' ').replace(/\w\S*/g, function (txt) { - return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + return (str === undefined || str === null) ? '' : str.replace(/([A-Z])/g, ' $1').replace(/^./, function (txt) { + return txt.toUpperCase(); }); }; }); diff --git a/lemur/static/app/angular/destinations/destination/destination.js b/lemur/static/app/angular/destinations/destination/destination.js new file mode 100644 index 00000000..15fb2b42 --- /dev/null +++ b/lemur/static/app/angular/destinations/destination/destination.js @@ -0,0 +1,40 @@ +'use strict'; + +angular.module('lemur') + + .controller('DestinationsCreateController', function ($scope, $modalInstance, PluginService, DestinationService, LemurRestangular){ + $scope.destination = LemurRestangular.restangularizeElement(null, {}, 'destinations'); + + PluginService.get('destination').then(function (plugins) { + $scope.plugins = plugins; + }); + $scope.save = function (destination) { + DestinationService.create(destination).then(function () { + $modalInstance.close(); + }); + }; + + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + }) + + .controller('DestinationsEditController', function ($scope, $modalInstance, DestinationService, DestinationApi, PluginService, editId) { + DestinationApi.get(editId).then(function (destination) { + $scope.destination = destination; + }); + + PluginService.get('destination').then(function (plugins) { + $scope.plugins = plugins; + }); + + $scope.save = function (destination) { + DestinationService.update(destination).then(function () { + $modalInstance.close(); + }); + } + + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + }); diff --git a/lemur/static/app/angular/destinations/destination/destination.tpl.html b/lemur/static/app/angular/destinations/destination/destination.tpl.html new file mode 100644 index 00000000..5ae1fdf3 --- /dev/null +++ b/lemur/static/app/angular/destinations/destination/destination.tpl.html @@ -0,0 +1,56 @@ + + diff --git a/lemur/static/app/angular/destinations/services.js b/lemur/static/app/angular/destinations/services.js new file mode 100644 index 00000000..08d304d8 --- /dev/null +++ b/lemur/static/app/angular/destinations/services.js @@ -0,0 +1,53 @@ +'use strict'; +angular.module('lemur') + .service('DestinationApi', function (LemurRestangular) { + return LemurRestangular.all('destinations'); + }) + .service('DestinationService', function ($location, DestinationApi, toaster) { + var DestinationService = this; + DestinationService.findDestinationsByName = function (filterValue) { + return DestinationApi.getList({'filter[label]': filterValue}) + .then(function (destinations) { + return destinations; + }); + }; + + DestinationService.create = function (destination) { + return DestinationApi.post(destination).then( + function () { + toaster.pop({ + type: 'success', + title: destination.label, + body: 'Successfully created!' + }); + $location.path('destinations'); + }, + function (response) { + toaster.pop({ + type: 'error', + title: destination.label, + body: 'Was not created! ' + response.data.message + }); + }); + }; + + DestinationService.update = function (destination) { + return destination.put().then( + function () { + toaster.pop({ + type: 'success', + title: destination.label, + body: 'Successfully updated!' + }); + $location.path('destinations'); + }, + function (response) { + toaster.pop({ + type: 'error', + title: destination.label, + body: 'Was not updated! ' + response.data.message + }); + }); + }; + return DestinationService; + }); diff --git a/lemur/static/app/angular/destinations/view/view.js b/lemur/static/app/angular/destinations/view/view.js new file mode 100644 index 00000000..bd4974dd --- /dev/null +++ b/lemur/static/app/angular/destinations/view/view.js @@ -0,0 +1,85 @@ +'use strict'; + +angular.module('lemur') + + .config(function config($routeProvider) { + $routeProvider.when('/destinations', { + templateUrl: '/angular/destinations/view/view.tpl.html', + controller: 'DestinationsViewController' + }); + }) + + .controller('DestinationsViewController', function ($scope, $modal, DestinationApi, DestinationService, ngTableParams, toaster) { + $scope.filter = {}; + $scope.destinationsTable = new ngTableParams({ + page: 1, // show first page + count: 10, // count per page + sorting: { + id: 'desc' // initial sorting + }, + filter: $scope.filter + }, { + total: 0, // length of data + getData: function ($defer, params) { + DestinationApi.getList(params.url()).then( + function (data) { + params.total(data.total); + $defer.resolve(data); + } + ); + } + }); + + $scope.remove = function (destination) { + destination.remove().then( + function () { + $scope.destinationsTable.reload(); + }, + function (response) { + toaster.pop({ + type: 'error', + title: 'Opps', + body: 'I see what you did there' + response.data.message + }); + } + ); + }; + + $scope.edit = function (destinationId) { + var modalInstance = $modal.open({ + animation: true, + templateUrl: '/angular/destinations/destination/destination.tpl.html', + controller: 'DestinationsEditController', + size: 'lg', + resolve: { + editId: function () { + return destinationId; + } + } + }); + + modalInstance.result.then(function () { + $scope.destinationsTable.reload(); + }); + + }; + + $scope.create = function () { + var modalInstance = $modal.open({ + animation: true, + controller: 'DestinationsCreateController', + templateUrl: '/angular/destinations/destination/destination.tpl.html', + size: 'lg' + }); + + modalInstance.result.then(function () { + $scope.destinationsTable.reload(); + }); + + }; + + $scope.toggleFilter = function (params) { + params.settings().$scope.show_filter = !params.settings().$scope.show_filter; + }; + + }); diff --git a/lemur/static/app/angular/destinations/view/view.tpl.html b/lemur/static/app/angular/destinations/view/view.tpl.html new file mode 100644 index 00000000..83e9205a --- /dev/null +++ b/lemur/static/app/angular/destinations/view/view.tpl.html @@ -0,0 +1,47 @@ +
    +
    +

    Destinations + oh the places you will go

    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + + + + + + + +
    +
      +
    • {{ destination.label }}
    • +
    • {{ destination.description }}
    • +
    +
    +
      +
    • {{ destination.plugin.title }}
    • +
    • {{ destination.plugin.description }}
    • +
    +
    +
    + + +
    +
    +
    +
    +
    +
    diff --git a/lemur/static/app/angular/loadingModal.html b/lemur/static/app/angular/loadingModal.html deleted file mode 100644 index 1f65a57e..00000000 --- a/lemur/static/app/angular/loadingModal.html +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/lemur/static/app/angular/roles/role/role.js b/lemur/static/app/angular/roles/role/role.js index fb8bbee4..2e977637 100644 --- a/lemur/static/app/angular/roles/role/role.js +++ b/lemur/static/app/angular/roles/role/role.js @@ -2,29 +2,37 @@ angular.module('lemur') - .config(function config($routeProvider) { - $routeProvider.when('/roles/create', { - templateUrl: '/angular/roles/role/role.tpl.html', - controller: 'RoleCreateController' - }); - $routeProvider.when('/roles/:id/edit', { - templateUrl: '/angular/roles/role/role.tpl.html', - controller: 'RoleEditController' - }); - }) - .controller('RoleEditController', function ($scope, $routeParams, RoleApi, RoleService, UserService) { - RoleApi.get($routeParams.id).then(function (role) { + .controller('RolesEditController', function ($scope, $modalInstance, RoleApi, RoleService, UserService, editId) { + RoleApi.get(editId).then(function (role) { $scope.role = role; RoleService.getUsers(role); }); - $scope.save = RoleService.update; + $scope.save = function (role) { + RoleService.update(role).then(function () { + $modalInstance.close(); + }); + }; + + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + $scope.userService = UserService; $scope.roleService = RoleService; }) - .controller('RoleCreateController', function ($scope, RoleApi, RoleService, UserService, LemurRestangular ) { + .controller('RolesCreateController', function ($scope,$modalInstance, RoleApi, RoleService, UserService, LemurRestangular) { $scope.role = LemurRestangular.restangularizeElement(null, {}, 'roles'); $scope.userService = UserService; - $scope.save = RoleService.create; + + $scope.save = function (role) { + RoleService.create(role).then(function () { + $modalInstance.close(); + }); + }; + + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; }); diff --git a/lemur/static/app/angular/roles/role/role.tpl.html b/lemur/static/app/angular/roles/role/role.tpl.html index d88e08d4..dc36bf19 100644 --- a/lemur/static/app/angular/roles/role/role.tpl.html +++ b/lemur/static/app/angular/roles/role/role.tpl.html @@ -1,85 +1,82 @@ -

    CreateEdit Role The nail that sticks out farthest gets hammered the hardest -

    -
    -
    - - Cancel -
    -
    -
    -
    -
    - -
    - -

    You must enter an role name

    -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    + -
    - -
    - - - - - - -
    {{ user.username }} - -
    -
    -
    - -
    - +
    diff --git a/lemur/static/app/angular/roles/services.js b/lemur/static/app/angular/roles/services.js index 8d3afd56..84e972c5 100644 --- a/lemur/static/app/angular/roles/services.js +++ b/lemur/static/app/angular/roles/services.js @@ -38,7 +38,7 @@ angular.module('lemur') }; RoleService.create = function (role) { - RoleApi.post(role).then( + return RoleApi.post(role).then( function () { toaster.pop({ type: 'success', @@ -57,7 +57,7 @@ angular.module('lemur') }; RoleService.update = function (role) { - role.put().then( + return role.put().then( function () { toaster.pop({ type: 'success', diff --git a/lemur/static/app/angular/roles/view/view.js b/lemur/static/app/angular/roles/view/view.js index c79b3755..17d60ae4 100644 --- a/lemur/static/app/angular/roles/view/view.js +++ b/lemur/static/app/angular/roles/view/view.js @@ -9,7 +9,7 @@ angular.module('lemur') }); }) - .controller('RolesViewController', function ($scope, RoleApi, RoleService, ngTableParams) { + .controller('RolesViewController', function ($scope, $modal, RoleApi, RoleService, ngTableParams) { $scope.filter = {}; $scope.rolesTable = new ngTableParams({ page: 1, // show first page @@ -39,4 +39,38 @@ angular.module('lemur') params.settings().$scope.show_filter = !params.settings().$scope.show_filter; }; + + $scope.edit = function (roleId) { + var modalInstance = $modal.open({ + animation: true, + templateUrl: '/angular/roles/role/role.tpl.html', + controller: 'RolesEditController', + size: 'lg', + resolve: { + editId: function () { + return roleId; + } + } + }); + + modalInstance.result.then(function () { + $scope.rolesTable.reload(); + }); + + }; + + $scope.create = function () { + var modalInstance = $modal.open({ + animation: true, + controller: 'RolesCreateController', + templateUrl: '/angular/roles/role/role.tpl.html', + size: 'lg' + }); + + modalInstance.result.then(function () { + $scope.rolesTable.reload(); + }); + + }; + }); diff --git a/lemur/static/app/angular/roles/view/view.tpl.html b/lemur/static/app/angular/roles/view/view.tpl.html index 86258c57..90490de3 100644 --- a/lemur/static/app/angular/roles/view/view.tpl.html +++ b/lemur/static/app/angular/roles/view/view.tpl.html @@ -5,7 +5,7 @@
    - Create +
    @@ -24,9 +24,9 @@
    - + Remove diff --git a/lemur/static/app/angular/users/services.js b/lemur/static/app/angular/users/services.js index b1077b6f..3075beb3 100644 --- a/lemur/static/app/angular/users/services.js +++ b/lemur/static/app/angular/users/services.js @@ -50,7 +50,7 @@ angular.module('lemur') }; UserService.create = function (user) { - UserApi.post(user).then( + return UserApi.post(user).then( function () { toaster.pop({ type: 'success', @@ -69,7 +69,7 @@ angular.module('lemur') }; UserService.update = function (user) { - user.put().then( + return user.put().then( function () { toaster.pop({ type: 'success', diff --git a/lemur/static/app/angular/users/user/user.js b/lemur/static/app/angular/users/user/user.js index 009133cb..7a3524f2 100644 --- a/lemur/static/app/angular/users/user/user.js +++ b/lemur/static/app/angular/users/user/user.js @@ -2,19 +2,8 @@ angular.module('lemur') - .config(function config($routeProvider) { - $routeProvider.when('/users/create', { - templateUrl: '/angular/users/user/user.tpl.html', - controller: 'UsersCreateController' - }); - $routeProvider.when('/users/:id/edit', { - templateUrl: '/angular/users/user/user.tpl.html', - controller: 'UsersEditController' - }); - }) - - .controller('UsersEditController', function ($scope, $routeParams, UserApi, UserService, RoleService) { - UserApi.get($routeParams.id).then(function (user) { + .controller('UsersEditController', function ($scope, $modalInstance, UserApi, UserService, RoleService, editId) { + UserApi.get(editId).then(function (user) { UserService.getRoles(user); $scope.user = user; }); @@ -24,15 +13,36 @@ angular.module('lemur') $scope.rolePage = 1; + + $scope.save = function (user) { + UserService.update(user).then(function () { + $modalInstance.close(); + }); + } + + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + $scope.loadMoreRoles = function () { $scope.rolePage += 1; UserService.loadMoreRoles($scope.user, $scope.rolePage); }; }) - .controller('UsersCreateController', function ($scope, UserService, LemurRestangular, RoleService) { + .controller('UsersCreateController', function ($scope, $modalInstance, UserService, LemurRestangular, RoleService) { $scope.user = LemurRestangular.restangularizeElement(null, {}, 'users'); $scope.save = UserService.create; $scope.roleService = RoleService; + $scope.create = function (user) { + UserService.create(user).then(function () { + $modalInstance.close(); + }); + }; + + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + }); diff --git a/lemur/static/app/angular/users/user/user.tpl.html b/lemur/static/app/angular/users/user/user.tpl.html index 21ade85b..9b1aca52 100644 --- a/lemur/static/app/angular/users/user/user.tpl.html +++ b/lemur/static/app/angular/users/user/user.tpl.html @@ -1,89 +1,86 @@ -

    CreateEdit User what was your name again? -

    -
    -
    - Cancel -
    -
    -
    -
    -
    - -
    - -

    You must enter a username

    -
    -
    -
    - -
    - -

    You must enter an email

    -
    -
    -
    - -
    - -

    You must enter an password

    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    - + - -
    - +
    diff --git a/lemur/static/app/angular/users/view/view.js b/lemur/static/app/angular/users/view/view.js index 71dc8ff6..104fc245 100644 --- a/lemur/static/app/angular/users/view/view.js +++ b/lemur/static/app/angular/users/view/view.js @@ -9,7 +9,7 @@ angular.module('lemur') }); }) - .controller('UsersViewController', function ($scope, UserApi, UserService, ngTableParams) { + .controller('UsersViewController', function ($scope, $modal, UserApi, UserService, ngTableParams) { $scope.filter = {}; $scope.usersTable = new ngTableParams({ page: 1, // show first page @@ -36,6 +36,39 @@ angular.module('lemur') }); }; + $scope.edit = function (userId) { + var modalInstance = $modal.open({ + animation: true, + templateUrl: '/angular/users/user/user.tpl.html', + controller: 'UsersEditController', + size: 'lg', + resolve: { + editId: function () { + return userId; + } + } + }); + + modalInstance.result.then(function () { + $scope.usersTable.reload(); + }); + + }; + + $scope.create = function () { + var modalInstance = $modal.open({ + animation: true, + controller: 'UsersCreateController', + templateUrl: '/angular/users/user/user.tpl.html', + size: 'lg' + }); + + modalInstance.result.then(function () { + $scope.usersTable.reload(); + }); + + }; + $scope.toggleFilter = function (params) { params.settings().$scope.show_filter = !params.settings().$scope.show_filter; }; diff --git a/lemur/static/app/angular/users/view/view.tpl.html b/lemur/static/app/angular/users/view/view.tpl.html index 41f91dea..b27b2c05 100644 --- a/lemur/static/app/angular/users/view/view.tpl.html +++ b/lemur/static/app/angular/users/view/view.tpl.html @@ -5,7 +5,7 @@
    - Create +
    @@ -29,9 +29,9 @@ diff --git a/lemur/static/app/angular/wizard.html b/lemur/static/app/angular/wizard.html index 56090a3f..1ff4b641 100644 --- a/lemur/static/app/angular/wizard.html +++ b/lemur/static/app/angular/wizard.html @@ -1,23 +1,12 @@
    -
    - -
    + -
    diff --git a/lemur/static/app/index.html b/lemur/static/app/index.html index 3a81560c..f8bd8abb 100644 --- a/lemur/static/app/index.html +++ b/lemur/static/app/index.html @@ -55,7 +55,7 @@
  • Domains
  • Roles
  • Users
  • -
  • Accounts
  • +
  • Destinations