From 0c7204cdb970903ffcfe10ed7f68620c757d04e3 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Fri, 10 Jul 2015 17:06:57 -0700 Subject: [PATCH] Refactored 'accounts' to be more general with 'destinations' --- lemur/__init__.py | 8 +- lemur/accounts/models.py | 29 ---- lemur/accounts/service.py | 112 ------------ lemur/certificates/models.py | 4 +- lemur/certificates/service.py | 73 +++----- lemur/certificates/sync.py | 160 +++--------------- lemur/certificates/views.py | 14 +- lemur/{accounts => destinations}/__init__.py | 0 lemur/destinations/models.py | 28 +++ lemur/destinations/service.py | 110 ++++++++++++ lemur/{accounts => destinations}/views.py | 98 +++++------ lemur/elbs/models.py | 2 +- lemur/elbs/sync.py | 4 +- lemur/listeners/service.py | 2 +- lemur/manage.py | 36 ++-- lemur/models.py | 4 +- lemur/plugins/bases/destination.py | 5 +- lemur/plugins/lemur_aws/__init__.py | 5 + .../services/aws => plugins/lemur_aws}/elb.py | 2 +- .../services/aws => plugins/lemur_aws}/iam.py | 13 +- lemur/plugins/lemur_aws/plugin.py | 77 +++++++++ .../services/aws => plugins/lemur_aws}/sts.py | 0 .../plugins/{lemur_aws/aws.py => service.py} | 0 .../app/angular/accounts/account/account.js | 27 --- .../angular/accounts/account/account.tpl.html | 45 ----- lemur/static/app/angular/accounts/services.js | 53 ------ .../static/app/angular/accounts/view/view.js | 52 ------ .../app/angular/accounts/view/view.tpl.html | 44 ----- lemur/tests/test_accounts.py | 122 ++++++------- 29 files changed, 421 insertions(+), 708 deletions(-) delete mode 100644 lemur/accounts/models.py delete mode 100644 lemur/accounts/service.py rename lemur/{accounts => destinations}/__init__.py (100%) create mode 100644 lemur/destinations/models.py create mode 100644 lemur/destinations/service.py rename lemur/{accounts => destinations}/views.py (69%) rename lemur/{common/services/aws => plugins/lemur_aws}/elb.py (98%) rename lemur/{common/services/aws => plugins/lemur_aws}/iam.py (90%) create mode 100644 lemur/plugins/lemur_aws/plugin.py rename lemur/{common/services/aws => plugins/lemur_aws}/sts.py (100%) rename lemur/plugins/{lemur_aws/aws.py => service.py} (100%) delete mode 100644 lemur/static/app/angular/accounts/account/account.js delete mode 100644 lemur/static/app/angular/accounts/account/account.tpl.html delete mode 100644 lemur/static/app/angular/accounts/services.js delete mode 100644 lemur/static/app/angular/accounts/view/view.js delete mode 100644 lemur/static/app/angular/accounts/view/view.tpl.html 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/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/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/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/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_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/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/tests/test_accounts.py b/lemur/tests/test_accounts.py index 7665bfc6..c5dec77e 100644 --- a/lemur/tests/test_accounts.py +++ b/lemur/tests/test_accounts.py @@ -1,15 +1,15 @@ -from lemur.accounts.service import * -from lemur.accounts.views import * +from lemur.destinations.service import * +from lemur.destinations.views import * from json import dumps def test_crud(session): - account = create('111111', 'account1') - assert account.id > 0 + destination = create('111111', 'destination1') + assert destination.id > 0 - account = update(account.id, 11111, 'account2') - assert account.label == 'account2' + destination = update(destination.id, 11111, 'destination2') + assert destination.label == 'destination2' assert len(get_all()) == 1 @@ -17,116 +17,116 @@ def test_crud(session): assert len(get_all()) == 0 -def test_account_get(client): - assert client.get(api.url_for(Accounts, account_id=1)).status_code == 401 +def test_destination_get(client): + assert client.get(api.url_for(Destinations, destination_id=1)).status_code == 401 -def test_account_post(client): - assert client.post(api.url_for(Accounts, account_id=1), data={}).status_code == 405 +def test_destination_post(client): + assert client.post(api.url_for(Destinations, destination_id=1), data={}).status_code == 405 -def test_account_put(client): - assert client.put(api.url_for(Accounts, account_id=1), data={}).status_code == 401 +def test_destination_put(client): + assert client.put(api.url_for(Destinations, destination_id=1), data={}).status_code == 401 -def test_account_delete(client): - assert client.delete(api.url_for(Accounts, account_id=1)).status_code == 401 +def test_destination_delete(client): + assert client.delete(api.url_for(Destinations, destination_id=1)).status_code == 401 -def test_account_patch(client): - assert client.patch(api.url_for(Accounts, account_id=1), data={}).status_code == 405 +def test_destination_patch(client): + assert client.patch(api.url_for(Destinations, destination_id=1), data={}).status_code == 405 VALID_USER_HEADER_TOKEN = { 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'} -def test_auth_account_get(client): - assert client.get(api.url_for(Accounts, account_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 +def test_auth_destination_get(client): + assert client.get(api.url_for(Destinations, destination_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 -def test_auth_account_post_(client): - assert client.post(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 +def test_auth_destination_post_(client): + assert client.post(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 -def test_auth_account_put(client): - assert client.put(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 +def test_auth_destination_put(client): + assert client.put(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 -def test_auth_account_delete(client): - assert client.delete(api.url_for(Accounts, account_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 403 +def test_auth_destination_delete(client): + assert client.delete(api.url_for(Destinations, destination_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 403 -def test_auth_account_patch(client): - assert client.patch(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 +def test_auth_destination_patch(client): + assert client.patch(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 VALID_ADMIN_HEADER_TOKEN = { 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'} -def test_admin_account_get(client): - assert client.get(api.url_for(Accounts, account_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 +def test_admin_destination_get(client): + assert client.get(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 -def test_admin_account_post(client): - assert client.post(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 +def test_admin_destination_post(client): + assert client.post(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 -def test_admin_account_put(client): - assert client.put(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 +def test_admin_destination_put(client): + assert client.put(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 -def test_admin_account_delete(client): - assert client.delete(api.url_for(Accounts, account_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 500 +def test_admin_destination_delete(client): + assert client.delete(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 500 -def test_admin_account_patch(client): - assert client.patch(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 +def test_admin_destination_patch(client): + assert client.patch(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 -def test_accounts_get(client): - assert client.get(api.url_for(AccountsList)).status_code == 401 +def test_destinations_get(client): + assert client.get(api.url_for(DestinationsList)).status_code == 401 -def test_accounts_post(client): - assert client.post(api.url_for(AccountsList), data={}).status_code == 401 +def test_destinations_post(client): + assert client.post(api.url_for(DestinationsList), data={}).status_code == 401 -def test_accounts_put(client): - assert client.put(api.url_for(AccountsList), data={}).status_code == 405 +def test_destinations_put(client): + assert client.put(api.url_for(DestinationsList), data={}).status_code == 405 -def test_accounts_delete(client): - assert client.delete(api.url_for(AccountsList)).status_code == 405 +def test_destinations_delete(client): + assert client.delete(api.url_for(DestinationsList)).status_code == 405 -def test_accounts_patch(client): - assert client.patch(api.url_for(AccountsList), data={}).status_code == 405 +def test_destinations_patch(client): + assert client.patch(api.url_for(DestinationsList), data={}).status_code == 405 -def test_auth_accounts_get(client): - assert client.get(api.url_for(AccountsList), headers=VALID_USER_HEADER_TOKEN).status_code == 200 +def test_auth_destinations_get(client): + assert client.get(api.url_for(DestinationsList), headers=VALID_USER_HEADER_TOKEN).status_code == 200 -def test_auth_accounts_post(client): - assert client.post(api.url_for(AccountsList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 +def test_auth_destinations_post(client): + assert client.post(api.url_for(DestinationsList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 -def test_admin_accounts_get(client): - resp = client.get(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN) +def test_admin_destinations_get(client): + resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN) assert resp.status_code == 200 assert resp.json == {'items': [], 'total': 0} -def test_admin_accounts_crud(client): - assert client.post(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 - data = {'accountNumber': 111, 'label': 'test', 'comments': 'test'} - resp = client.post(api.url_for(AccountsList), data=dumps(data), content_type='application/json', headers=VALID_ADMIN_HEADER_TOKEN) +def test_admin_destinations_crud(client): + assert client.post(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 + data = {'destinationNumber': 111, 'label': 'test', 'comments': 'test'} + resp = client.post(api.url_for(DestinationsList), data=dumps(data), content_type='application/json', headers=VALID_ADMIN_HEADER_TOKEN) assert resp.status_code == 200 - assert client.get(api.url_for(Accounts, account_id=resp.json['id']), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 - resp = client.get(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN) + assert client.get(api.url_for(Destinations, destination_id=resp.json['id']), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN) assert resp.status_code == 200 - assert resp.json == {'items': [{'accountNumber': 111, 'label': 'test', 'comments': 'test', 'id': 2}], 'total': 1} - assert client.delete(api.url_for(Accounts, account_id=2), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 - resp = client.get(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN) + assert resp.json == {'items': [{'destinationNumber': 111, 'label': 'test', 'comments': 'test', 'id': 2}], 'total': 1} + assert client.delete(api.url_for(Destinations, destination_id=2), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 + resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN) assert resp.status_code == 200 assert resp.json == {'items': [], 'total': 0}