Refactored 'accounts' to be more general with 'destinations'
This commit is contained in:
parent
b26de2b000
commit
0c7204cdb9
|
@ -15,11 +15,12 @@ from lemur.roles.views import mod as roles_bp
|
||||||
from lemur.auth.views import mod as auth_bp
|
from lemur.auth.views import mod as auth_bp
|
||||||
from lemur.domains.views import mod as domains_bp
|
from lemur.domains.views import mod as domains_bp
|
||||||
from lemur.elbs.views import mod as elbs_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.authorities.views import mod as authorities_bp
|
||||||
from lemur.listeners.views import mod as listeners_bp
|
from lemur.listeners.views import mod as listeners_bp
|
||||||
from lemur.certificates.views import mod as certificates_bp
|
from lemur.certificates.views import mod as certificates_bp
|
||||||
from lemur.status.views import mod as status_bp
|
from lemur.status.views import mod as status_bp
|
||||||
|
from lemur.plugins.views import mod as plugins_bp
|
||||||
|
|
||||||
LEMUR_BLUEPRINTS = (
|
LEMUR_BLUEPRINTS = (
|
||||||
users_bp,
|
users_bp,
|
||||||
|
@ -27,11 +28,12 @@ LEMUR_BLUEPRINTS = (
|
||||||
auth_bp,
|
auth_bp,
|
||||||
domains_bp,
|
domains_bp,
|
||||||
elbs_bp,
|
elbs_bp,
|
||||||
accounts_bp,
|
destinations_bp,
|
||||||
authorities_bp,
|
authorities_bp,
|
||||||
listeners_bp,
|
listeners_bp,
|
||||||
certificates_bp,
|
certificates_bp,
|
||||||
status_bp
|
status_bp,
|
||||||
|
plugins_bp,
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_app(config=None):
|
def create_app(config=None):
|
||||||
|
|
|
@ -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 <kglisson@netflix.com>
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
|
|
|
@ -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 <kglisson@netflix.com>
|
|
||||||
"""
|
|
||||||
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)
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ from lemur.database import db
|
||||||
from lemur.domains.models import Domain
|
from lemur.domains.models import Domain
|
||||||
|
|
||||||
from lemur.constants import SAN_NAMING_TEMPLATE, DEFAULT_NAMING_TEMPLATE, NONSTANDARD_NAMING_TEMPLATE
|
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):
|
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)
|
date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False)
|
||||||
user_id = Column(Integer, ForeignKey('users.id'))
|
user_id = Column(Integer, ForeignKey('users.id'))
|
||||||
authority_id = Column(Integer, ForeignKey('authorities.id'))
|
authority_id = Column(Integer, ForeignKey('authorities.id'))
|
||||||
accounts = relationship("Account", secondary=certificate_account_associations, backref='certificate')
|
accounts = relationship("Destination", secondary=certificate_destination_associations, backref='certificate')
|
||||||
domains = relationship("Domain", secondary=certificate_associations, backref="certificate")
|
domains = relationship("Domain", secondary=certificate_associations, backref="certificate")
|
||||||
elb_listeners = relationship("Listener", lazy='dynamic', backref='certificate')
|
elb_listeners = relationship("Listener", lazy='dynamic', backref='certificate')
|
||||||
|
|
||||||
|
|
|
@ -13,12 +13,10 @@ from sqlalchemy import func, or_
|
||||||
from flask import g, current_app
|
from flask import g, current_app
|
||||||
|
|
||||||
from lemur import database
|
from lemur import database
|
||||||
from lemur.common.services.aws import iam
|
|
||||||
from lemur.plugins.base import plugins
|
from lemur.plugins.base import plugins
|
||||||
from lemur.certificates.models import Certificate
|
from lemur.certificates.models import Certificate
|
||||||
|
|
||||||
from lemur.accounts.models import Account
|
from lemur.destinations.models import Destination
|
||||||
from lemur.accounts import service as account_service
|
|
||||||
from lemur.authorities.models import Authority
|
from lemur.authorities.models import Authority
|
||||||
|
|
||||||
from lemur.roles.models import Role
|
from lemur.roles.models import Role
|
||||||
|
@ -59,28 +57,6 @@ def delete(cert_id):
|
||||||
database.delete(get(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():
|
def get_all_certs():
|
||||||
"""
|
"""
|
||||||
Retrieves all certificates within Lemur.
|
Retrieves all certificates within Lemur.
|
||||||
|
@ -134,7 +110,7 @@ def mint(issuer_options):
|
||||||
issuer_options['creator'] = g.user.email
|
issuer_options['creator'] = g.user.email
|
||||||
cert_body, cert_chain = issuer.create_certificate(csr, issuer_options)
|
cert_body, cert_chain = issuer.create_certificate(csr, issuer_options)
|
||||||
|
|
||||||
cert = save_cert(cert_body, private_key, cert_chain, issuer_options.get('accounts'))
|
cert = save_cert(cert_body, private_key, cert_chain, issuer_options.get('destinations'))
|
||||||
cert.user = g.user
|
cert.user = g.user
|
||||||
cert.authority = authority
|
cert.authority = authority
|
||||||
database.update(cert)
|
database.update(cert)
|
||||||
|
@ -154,9 +130,10 @@ def import_certificate(**kwargs):
|
||||||
|
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
"""
|
"""
|
||||||
|
from lemur.users import service as user_service
|
||||||
cert = Certificate(kwargs['public_certificate'])
|
cert = Certificate(kwargs['public_certificate'])
|
||||||
cert.owner = kwargs.get('owner', )
|
cert.owner = kwargs.get('owner', current_app.config.get('LEMUR_SECURITY_TEAM_EMAIL'))
|
||||||
cert.creator = kwargs.get('creator', 'Lemur')
|
cert.creator = kwargs.get('creator', user_service.get_by_email('lemur@nobody'))
|
||||||
|
|
||||||
# NOTE existing certs may not follow our naming standard we will
|
# NOTE existing certs may not follow our naming standard we will
|
||||||
# overwrite the generated name with the actual cert name
|
# overwrite the generated name with the actual cert name
|
||||||
|
@ -166,31 +143,29 @@ def import_certificate(**kwargs):
|
||||||
if kwargs.get('user'):
|
if kwargs.get('user'):
|
||||||
cert.user = kwargs.get('user')
|
cert.user = kwargs.get('user')
|
||||||
|
|
||||||
if kwargs.get('account'):
|
if kwargs.get('destination'):
|
||||||
cert.accounts.append(kwargs.get('account'))
|
cert.destinations.append(kwargs.get('destination'))
|
||||||
|
|
||||||
cert = database.create(cert)
|
cert = database.create(cert)
|
||||||
return cert
|
return cert
|
||||||
|
|
||||||
|
|
||||||
def save_cert(cert_body, private_key, cert_chain, accounts):
|
def save_cert(cert_body, private_key, cert_chain, destinations):
|
||||||
"""
|
"""
|
||||||
Determines if the certificate needs to be uploaded to AWS or other services.
|
Determines if the certificate needs to be uploaded to AWS or other services.
|
||||||
|
|
||||||
:param cert_body:
|
:param cert_body:
|
||||||
:param private_key:
|
:param private_key:
|
||||||
:param cert_chain:
|
:param cert_chain:
|
||||||
:param challenge:
|
:param destinations:
|
||||||
:param csr_config:
|
|
||||||
:param accounts:
|
|
||||||
"""
|
"""
|
||||||
cert = Certificate(cert_body, private_key, cert_chain)
|
cert = Certificate(cert_body, private_key, cert_chain)
|
||||||
# if we have an AWS accounts lets upload them
|
|
||||||
if accounts:
|
# we should save them to any destination that is requested
|
||||||
for account in accounts:
|
for destination in destinations:
|
||||||
account = account_service.get(account['id'])
|
destination_plugin = plugins.get(destination['plugin']['slug'])
|
||||||
iam.upload_cert(account.account_number, cert, private_key, cert_chain)
|
destination_plugin.upload(cert, private_key, cert_chain, destination['plugin']['pluginOptions'])
|
||||||
cert.accounts.append(account)
|
|
||||||
return cert
|
return cert
|
||||||
|
|
||||||
|
|
||||||
|
@ -198,13 +173,11 @@ def upload(**kwargs):
|
||||||
"""
|
"""
|
||||||
Allows for pre-made certificates to be imported into Lemur.
|
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(
|
cert = save_cert(
|
||||||
kwargs.get('public_cert'),
|
kwargs.get('public_cert'),
|
||||||
kwargs.get('private_key'),
|
kwargs.get('private_key'),
|
||||||
kwargs.get('intermediate_cert'),
|
kwargs.get('intermediate_cert'),
|
||||||
kwargs.get('accounts')
|
kwargs.get('destinations')
|
||||||
)
|
)
|
||||||
|
|
||||||
cert.owner = kwargs['owner']
|
cert.owner = kwargs['owner']
|
||||||
|
@ -237,7 +210,7 @@ def render(args):
|
||||||
query = database.session_query(Certificate)
|
query = database.session_query(Certificate)
|
||||||
|
|
||||||
time_range = args.pop('time_range')
|
time_range = args.pop('time_range')
|
||||||
account_id = args.pop('account_id')
|
destination_id = args.pop('destination_id')
|
||||||
show = args.pop('show')
|
show = args.pop('show')
|
||||||
owner = args.pop('owner')
|
owner = args.pop('owner')
|
||||||
creator = args.pop('creator') # TODO we should enabling filtering by owner
|
creator = args.pop('creator') # TODO we should enabling filtering by owner
|
||||||
|
@ -260,8 +233,8 @@ def render(args):
|
||||||
)
|
)
|
||||||
return database.sort_and_page(query, Certificate, args)
|
return database.sort_and_page(query, Certificate, args)
|
||||||
|
|
||||||
if 'account' in terms:
|
if 'destination' in terms:
|
||||||
query = query.filter(Certificate.accounts.any(Account.id == terms[1]))
|
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??
|
elif 'active' in filt: # this is really weird but strcmp seems to not work here??
|
||||||
query = query.filter(Certificate.active == terms[1])
|
query = query.filter(Certificate.active == terms[1])
|
||||||
else:
|
else:
|
||||||
|
@ -276,8 +249,8 @@ def render(args):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if account_id:
|
if destination_id:
|
||||||
query = query.filter(Certificate.accounts.any(Account.id == account_id))
|
query = query.filter(Certificate.destinations.any(Destination.id == destination_id))
|
||||||
|
|
||||||
if time_range:
|
if time_range:
|
||||||
to = arrow.now().replace(weeks=+time_range).format('YYYY-MM-DD')
|
to = arrow.now().replace(weeks=+time_range).format('YYYY-MM-DD')
|
||||||
|
@ -404,8 +377,8 @@ def stats(**kwargs):
|
||||||
if kwargs.get('active') == 'true':
|
if kwargs.get('active') == 'true':
|
||||||
query = query.filter(Certificate.elb_listeners.any())
|
query = query.filter(Certificate.elb_listeners.any())
|
||||||
|
|
||||||
if kwargs.get('account_id'):
|
if kwargs.get('destination_id'):
|
||||||
query = query.filter(Certificate.accounts.any(Account.id == kwargs.get('account_id')))
|
query = query.filter(Certificate.destinations.any(Destination.id == kwargs.get('destination_id')))
|
||||||
|
|
||||||
if kwargs.get('metric') == 'not_after':
|
if kwargs.get('metric') == 'not_after':
|
||||||
start = arrow.utcnow()
|
start = arrow.utcnow()
|
||||||
|
|
|
@ -7,10 +7,6 @@
|
||||||
to 'sync' with as many different datasources as possible to try and track
|
to 'sync' with as many different datasources as possible to try and track
|
||||||
any certificate that may be in use.
|
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
|
These operations are typically run on a periodic basis from either the command
|
||||||
line or a cron job.
|
line or a cron job.
|
||||||
|
|
||||||
|
@ -18,151 +14,33 @@
|
||||||
:license: Apache, see LICENSE for more details.
|
:license: Apache, see LICENSE for more details.
|
||||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
"""
|
"""
|
||||||
import requests
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
|
|
||||||
from flask import current_app
|
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 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.base import plugins
|
||||||
|
from lemur.plugins.bases.source import SourcePlugin
|
||||||
|
|
||||||
def aws():
|
def sync():
|
||||||
"""
|
for plugin in plugins:
|
||||||
Attempts to retrieve all certificates located in known AWS accounts
|
|
||||||
:raise e:
|
|
||||||
"""
|
|
||||||
new = 0
|
new = 0
|
||||||
updated = 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
|
for certificate in certificates:
|
||||||
# user
|
exists = cert_service.find_duplicates(certificate)
|
||||||
user = user_service.get_by_email('lemur@nobody')
|
|
||||||
|
|
||||||
# we don't need to check regions as IAM is a global service
|
if not exists:
|
||||||
for account in account_service.get_all():
|
cert_service.import_certificate(**certificate)
|
||||||
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
|
|
||||||
|
|
||||||
current_app.logger.info("found {} certs from '{}/{}' ... ".format(
|
|
||||||
len(cert_arns), account.account_number, account.label)
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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
|
new += 1
|
||||||
|
|
||||||
elif len(existing) == 1: # we check to make sure we know about the current account for this certificate
|
if len(exists) == 1:
|
||||||
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
|
updated += 1
|
||||||
|
|
||||||
else:
|
# TODO associated cert with source
|
||||||
current_app.logger.error(
|
# TODO update cert if found from different source
|
||||||
"Multiple certificates with the same body found, unable to correctly determine which entry to update"
|
# TODO dissassociate source if missing
|
||||||
)
|
|
||||||
|
|
||||||
# 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))
|
|
||||||
|
|
|
@ -164,7 +164,7 @@ class CertificatesList(AuthenticatedResource):
|
||||||
parser.add_argument('owner', type=bool, location='args')
|
parser.add_argument('owner', type=bool, location='args')
|
||||||
parser.add_argument('id', type=str, location='args')
|
parser.add_argument('id', type=str, location='args')
|
||||||
parser.add_argument('active', type=bool, 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('creator', type=str, location='args')
|
||||||
parser.add_argument('show', type=str, location='args')
|
parser.add_argument('show', type=str, location='args')
|
||||||
|
|
||||||
|
@ -271,7 +271,7 @@ class CertificatesList(AuthenticatedResource):
|
||||||
:statuscode 403: unauthenticated
|
:statuscode 403: unauthenticated
|
||||||
"""
|
"""
|
||||||
self.reqparse.add_argument('extensions', type=dict, location='json')
|
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('elbs', type=list, location='json')
|
||||||
self.reqparse.add_argument('owner', type=str, location='json')
|
self.reqparse.add_argument('owner', type=str, location='json')
|
||||||
self.reqparse.add_argument('validityStart', type=str, location='json') # parse date
|
self.reqparse.add_argument('validityStart', type=str, location='json') # parse date
|
||||||
|
@ -330,7 +330,7 @@ class CertificatesUpload(AuthenticatedResource):
|
||||||
"publicCert": "---Begin Public...",
|
"publicCert": "---Begin Public...",
|
||||||
"intermediateCert": "---Begin Public...",
|
"intermediateCert": "---Begin Public...",
|
||||||
"privateKey": "---Begin Private..."
|
"privateKey": "---Begin Private..."
|
||||||
"accounts": []
|
"destinations": []
|
||||||
}
|
}
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
|
@ -364,19 +364,19 @@ class CertificatesUpload(AuthenticatedResource):
|
||||||
:arg publicCert: valid PEM public key for certificate
|
:arg publicCert: valid PEM public key for certificate
|
||||||
:arg intermediateCert valid PEM intermediate key for certificate
|
:arg intermediateCert valid PEM intermediate key for certificate
|
||||||
:arg privateKey: valid PEM private 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
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
:statuscode 403: unauthenticated
|
:statuscode 403: unauthenticated
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
"""
|
"""
|
||||||
self.reqparse.add_argument('owner', type=str, required=True, location='json')
|
self.reqparse.add_argument('owner', type=str, required=True, location='json')
|
||||||
self.reqparse.add_argument('publicCert', type=pem_str, required=True, dest='public_cert', location='json')
|
self.reqparse.add_argument('publicCert', type=pem_str, required=True, dest='public_cert', location='json')
|
||||||
self.reqparse.add_argument('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('intermediateCert', type=pem_str, dest='intermediate_cert', location='json')
|
||||||
self.reqparse.add_argument('privateKey', type=private_key_str, dest='private_key', location='json')
|
self.reqparse.add_argument('privateKey', type=private_key_str, dest='private_key', location='json')
|
||||||
|
|
||||||
args = self.reqparse.parse_args()
|
args = self.reqparse.parse_args()
|
||||||
if args.get('accounts'):
|
if args.get('destinations'):
|
||||||
if args.get('private_key'):
|
if args.get('private_key'):
|
||||||
return service.upload(**args)
|
return service.upload(**args)
|
||||||
else:
|
else:
|
||||||
|
@ -393,7 +393,7 @@ class CertificatesStats(AuthenticatedResource):
|
||||||
def get(self):
|
def get(self):
|
||||||
self.reqparse.add_argument('metric', type=str, location='args')
|
self.reqparse.add_argument('metric', type=str, location='args')
|
||||||
self.reqparse.add_argument('range', default=32, type=int, 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')
|
self.reqparse.add_argument('active', type=str, default='true', location='args')
|
||||||
|
|
||||||
args = self.reqparse.parse_args()
|
args = self.reqparse.parse_args()
|
||||||
|
|
|
@ -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 <kglisson@netflix.com>
|
||||||
|
"""
|
||||||
|
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
|
|
@ -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 <kglisson@netflix.com>
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
.. module: lemur.accounts.views
|
.. module: lemur.destinations.views
|
||||||
:platform: Unix
|
:platform: Unix
|
||||||
:synopsis: This module contains all of the accounts view code.
|
:synopsis: This module contains all of the accounts view code.
|
||||||
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
:copyright: (c) 2015 by Netflix Inc., see AUTHORS for more
|
||||||
|
@ -8,35 +8,36 @@
|
||||||
"""
|
"""
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask.ext.restful import Api, reqparse, fields
|
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.service import AuthenticatedResource
|
||||||
from lemur.auth.permissions import admin_permission
|
from lemur.auth.permissions import admin_permission
|
||||||
from lemur.common.utils import paginated_parser, marshal_items
|
from lemur.common.utils import paginated_parser, marshal_items
|
||||||
|
|
||||||
|
from lemur.plugins.views import FIELDS as PLUGIN_FIELDS
|
||||||
|
|
||||||
mod = Blueprint('accounts', __name__)
|
mod = Blueprint('destinations', __name__)
|
||||||
api = Api(mod)
|
api = Api(mod)
|
||||||
|
|
||||||
|
|
||||||
FIELDS = {
|
FIELDS = {
|
||||||
'accountNumber': fields.Integer(attribute='account_number'),
|
'description': fields.String,
|
||||||
|
'plugin': fields.Nested(PLUGIN_FIELDS, attribute='plugin'),
|
||||||
'label': fields.String,
|
'label': fields.String,
|
||||||
'comments': fields.String(attribute='notes'),
|
|
||||||
'id': fields.Integer,
|
'id': fields.Integer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class AccountsList(AuthenticatedResource):
|
class DestinationsList(AuthenticatedResource):
|
||||||
""" Defines the 'accounts' endpoint """
|
""" Defines the 'destinations' endpoint """
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.reqparse = reqparse.RequestParser()
|
self.reqparse = reqparse.RequestParser()
|
||||||
super(AccountsList, self).__init__()
|
super(DestinationsList, self).__init__()
|
||||||
|
|
||||||
@marshal_items(FIELDS)
|
@marshal_items(FIELDS)
|
||||||
def get(self):
|
def get(self):
|
||||||
"""
|
"""
|
||||||
.. http:get:: /accounts
|
.. http:get:: /destinations
|
||||||
|
|
||||||
The current account list
|
The current account list
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ class AccountsList(AuthenticatedResource):
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
|
|
||||||
GET /accounts HTTP/1.1
|
GET /destinations HTTP/1.1
|
||||||
Host: example.com
|
Host: example.com
|
||||||
Accept: application/json, text/javascript
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
@ -90,7 +91,7 @@ class AccountsList(AuthenticatedResource):
|
||||||
@marshal_items(FIELDS)
|
@marshal_items(FIELDS)
|
||||||
def post(self):
|
def post(self):
|
||||||
"""
|
"""
|
||||||
.. http:post:: /accounts
|
.. http:post:: /destinations
|
||||||
|
|
||||||
Creates a new account
|
Creates a new account
|
||||||
|
|
||||||
|
@ -98,7 +99,7 @@ class AccountsList(AuthenticatedResource):
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
|
|
||||||
POST /accounts HTTP/1.1
|
POST /destinations HTTP/1.1
|
||||||
Host: example.com
|
Host: example.com
|
||||||
Accept: application/json, text/javascript
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
@ -129,23 +130,23 @@ class AccountsList(AuthenticatedResource):
|
||||||
:reqheader Authorization: OAuth token to authenticate
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
"""
|
"""
|
||||||
self.reqparse.add_argument('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('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()
|
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):
|
def __init__(self):
|
||||||
self.reqparse = reqparse.RequestParser()
|
self.reqparse = reqparse.RequestParser()
|
||||||
super(Accounts, self).__init__()
|
super(Destinations, self).__init__()
|
||||||
|
|
||||||
@marshal_items(FIELDS)
|
@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
|
Get a specific account
|
||||||
|
|
||||||
|
@ -153,7 +154,7 @@ class Accounts(AuthenticatedResource):
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
|
|
||||||
GET /accounts/1 HTTP/1.1
|
GET /destinations/1 HTTP/1.1
|
||||||
Host: example.com
|
Host: example.com
|
||||||
Accept: application/json, text/javascript
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
@ -175,13 +176,13 @@ class Accounts(AuthenticatedResource):
|
||||||
:reqheader Authorization: OAuth token to authenticate
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
"""
|
"""
|
||||||
return service.get(account_id)
|
return service.get(destination_id)
|
||||||
|
|
||||||
@admin_permission.require(http_exception=403)
|
@admin_permission.require(http_exception=403)
|
||||||
@marshal_items(FIELDS)
|
@marshal_items(FIELDS)
|
||||||
def put(self, account_id):
|
def put(self, destination_id):
|
||||||
"""
|
"""
|
||||||
.. http:put:: /accounts/1
|
.. http:put:: /destinations/1
|
||||||
|
|
||||||
Updates an account
|
Updates an account
|
||||||
|
|
||||||
|
@ -189,15 +190,10 @@ class Accounts(AuthenticatedResource):
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
|
|
||||||
POST /accounts/1 HTTP/1.1
|
POST /destinations/1 HTTP/1.1
|
||||||
Host: example.com
|
Host: example.com
|
||||||
Accept: application/json, text/javascript
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
{
|
|
||||||
"accountNumber": 11111111111,
|
|
||||||
"label": "labelChanged,
|
|
||||||
"comments": "this is a thing"
|
|
||||||
}
|
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
|
|
||||||
|
@ -220,29 +216,29 @@ class Accounts(AuthenticatedResource):
|
||||||
:reqheader Authorization: OAuth token to authenticate
|
:reqheader Authorization: OAuth token to authenticate
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
"""
|
"""
|
||||||
self.reqparse.add_argument('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('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()
|
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)
|
@admin_permission.require(http_exception=403)
|
||||||
def delete(self, account_id):
|
def delete(self, destination_id):
|
||||||
service.delete(account_id)
|
service.delete(destination_id)
|
||||||
return {'result': True}
|
return {'result': True}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CertificateAccounts(AuthenticatedResource):
|
class CertificateDestinations(AuthenticatedResource):
|
||||||
""" Defines the 'certificate/<int:certificate_id/accounts'' endpoint """
|
""" Defines the 'certificate/<int:certificate_id/destinations'' endpoint """
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(CertificateAccounts, self).__init__()
|
super(CertificateDestinations, self).__init__()
|
||||||
|
|
||||||
@marshal_items(FIELDS)
|
@marshal_items(FIELDS)
|
||||||
def get(self, certificate_id):
|
def get(self, certificate_id):
|
||||||
"""
|
"""
|
||||||
.. http:get:: /certificates/1/accounts
|
.. http:get:: /certificates/1/destinations
|
||||||
|
|
||||||
The current account list for a given certificates
|
The current account list for a given certificates
|
||||||
|
|
||||||
|
@ -250,7 +246,7 @@ class CertificateAccounts(AuthenticatedResource):
|
||||||
|
|
||||||
.. sourcecode:: http
|
.. sourcecode:: http
|
||||||
|
|
||||||
GET /certificates/1/accounts HTTP/1.1
|
GET /certificates/1/destinations HTTP/1.1
|
||||||
Host: example.com
|
Host: example.com
|
||||||
Accept: application/json, text/javascript
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
@ -262,24 +258,6 @@ class CertificateAccounts(AuthenticatedResource):
|
||||||
Vary: Accept
|
Vary: Accept
|
||||||
Content-Type: text/javascript
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
:query sortBy: field to sort on
|
:query sortBy: field to sort on
|
||||||
:query sortDir: acs or desc
|
:query sortDir: acs or desc
|
||||||
:query page: int. default is 1
|
:query page: int. default is 1
|
||||||
|
@ -294,7 +272,7 @@ class CertificateAccounts(AuthenticatedResource):
|
||||||
return service.render(args)
|
return service.render(args)
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(AccountsList, '/accounts', endpoint='accounts')
|
api.add_resource(DestinationsList, '/destinations', endpoint='destinations')
|
||||||
api.add_resource(Accounts, '/accounts/<int:account_id>', endpoint='account')
|
api.add_resource(Destinations, '/destinations/<int:destination_id>', endpoint='account')
|
||||||
api.add_resource(CertificateAccounts, '/certificates/<int:certificate_id>/accounts', endpoint='certificateAccounts')
|
api.add_resource(CertificateDestinations, '/certificates/<int:certificate_id>/destinations', endpoint='certificateDestinations')
|
||||||
|
|
|
@ -16,7 +16,7 @@ from lemur.listeners.models import Listener
|
||||||
class ELB(db.Model):
|
class ELB(db.Model):
|
||||||
__tablename__ = 'elbs'
|
__tablename__ = 'elbs'
|
||||||
id = Column(BigInteger, primary_key=True)
|
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))
|
region = Column(String(32))
|
||||||
name = Column(String(128))
|
name = Column(String(128))
|
||||||
vpc_id = Column(String(128))
|
vpc_id = Column(String(128))
|
||||||
|
|
|
@ -12,9 +12,9 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask import current_app
|
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.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):
|
def create_new(known, aws, account):
|
||||||
|
|
|
@ -18,7 +18,7 @@ from lemur.listeners.models import Listener
|
||||||
from lemur.elbs import service as elb_service
|
from lemur.elbs import service as elb_service
|
||||||
from lemur.certificates import service as certificate_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):
|
def verify_attachment(certificate_id, elb_account_number):
|
||||||
|
|
|
@ -13,9 +13,11 @@ from flask_script.commands import ShowUrls, Clean, Server
|
||||||
from lemur import database
|
from lemur import database
|
||||||
from lemur.users import service as user_service
|
from lemur.users import service as user_service
|
||||||
from lemur.roles import service as role_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.certificates import service as cert_service
|
||||||
|
|
||||||
|
from lemur.plugins.base import plugins
|
||||||
|
|
||||||
from lemur.certificates.verify import verify_string
|
from lemur.certificates.verify import verify_string
|
||||||
from lemur.certificates import sync
|
from lemur.certificates import sync
|
||||||
from lemur.elbs.sync import sync_all_elbs
|
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.roles.models import Role
|
||||||
from lemur.authorities.models import Authority
|
from lemur.authorities.models import Authority
|
||||||
from lemur.certificates.models import Certificate
|
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.domains.models import Domain
|
||||||
from lemur.elbs.models import ELB
|
from lemur.elbs.models import ELB
|
||||||
from lemur.listeners.models import Listener
|
from lemur.listeners.models import Listener
|
||||||
|
@ -96,12 +98,12 @@ SQLALCHEMY_DATABASE_URI = ''
|
||||||
## AWS ##
|
## 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 = {{
|
#AWS_ACCOUNT_MAPPINGS = {{
|
||||||
# '1111111111': 'myawsacount'
|
# '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']
|
#AWS_REGIONS = ['us-east-1']
|
||||||
|
|
||||||
#LEMUR_INSTANCE_PROFILE = 'Lemur'
|
#LEMUR_INSTANCE_PROFILE = 'Lemur'
|
||||||
|
@ -133,6 +135,11 @@ def create():
|
||||||
stamp(revision='head')
|
stamp(revision='head')
|
||||||
|
|
||||||
|
|
||||||
|
@MigrateCommand.command
|
||||||
|
def drop_all():
|
||||||
|
database.db.drop_all()
|
||||||
|
|
||||||
|
|
||||||
@manager.command
|
@manager.command
|
||||||
def check_revoked():
|
def check_revoked():
|
||||||
"""
|
"""
|
||||||
|
@ -227,7 +234,7 @@ class Sync(Command):
|
||||||
|
|
||||||
class InitializeApp(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.
|
specified by our config.
|
||||||
|
|
||||||
Additionally a Lemur user will be created as a default user
|
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")
|
sys.stdout.write("[-] Default user has already been created, skipping...!\n")
|
||||||
|
|
||||||
if current_app.config.get('AWS_ACCOUNT_MAPPINGS'):
|
if current_app.config.get('AWS_ACCOUNT_MAPPINGS'):
|
||||||
|
if plugins.get('aws-destination'):
|
||||||
for account_name, account_number in current_app.config.get('AWS_ACCOUNT_MAPPINGS').items():
|
for account_name, account_number in current_app.config.get('AWS_ACCOUNT_MAPPINGS').items():
|
||||||
account = account_service.get_by_account_number(account_number)
|
|
||||||
|
|
||||||
if not account:
|
destination = destination_service.get_by_label(account_name)
|
||||||
account_service.create(account_number, label=account_name)
|
|
||||||
sys.stdout.write("[+] Added new account {0}:{1}!\n".format(account_number, 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:
|
else:
|
||||||
sys.stdout.write("[-] Account already exists, skipping...!\n")
|
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")
|
sys.stdout.write("[/] Done!\n")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,8 @@ certificate_associations = db.Table('certificate_associations',
|
||||||
Column('certificate_id', Integer, ForeignKey('certificates.id'))
|
Column('certificate_id', Integer, ForeignKey('certificates.id'))
|
||||||
)
|
)
|
||||||
|
|
||||||
certificate_account_associations = db.Table('certificate_account_associations',
|
certificate_destination_associations = db.Table('certificate_destination_associations',
|
||||||
Column('account_id', Integer, ForeignKey('accounts.id', ondelete='cascade')),
|
Column('destination_id', Integer, ForeignKey('destinations.id', ondelete='cascade')),
|
||||||
Column('certificate_id', Integer, ForeignKey('certificates.id', ondelete='cascade'))
|
Column('certificate_id', Integer, ForeignKey('certificates.id', ondelete='cascade'))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,8 @@
|
||||||
from lemur.plugins.base import Plugin
|
from lemur.plugins.base import Plugin
|
||||||
|
|
||||||
class DestinationPlugin(Plugin):
|
class DestinationPlugin(Plugin):
|
||||||
pass
|
type = 'destination'
|
||||||
|
|
||||||
|
def upload(self):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
try:
|
||||||
|
VERSION = __import__('pkg_resources') \
|
||||||
|
.get_distribution(__name__).version
|
||||||
|
except Exception, e:
|
||||||
|
VERSION = 'unknown'
|
|
@ -10,7 +10,7 @@ import boto.ec2
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
from lemur.exceptions import InvalidListener
|
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):
|
def is_valid(listener_tuple):
|
|
@ -6,8 +6,17 @@
|
||||||
:license: Apache, see LICENSE for more details.
|
:license: Apache, see LICENSE for more details.
|
||||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||||
"""
|
"""
|
||||||
from flask import current_app
|
from lemur.plugins.lemur_aws.sts import assume_service
|
||||||
from lemur.common.services.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):
|
def ssl_split(param_string):
|
|
@ -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 <kglisson@netflix.com>
|
||||||
|
"""
|
||||||
|
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
|
|
@ -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;
|
|
||||||
});
|
|
|
@ -1,45 +0,0 @@
|
||||||
<h2 class="featurette-heading"><span ng-show="!account.fromServer">Create</span><span ng-show="account.fromServer">Edit</span> Account <span class="text-muted"><small>next in line please
|
|
||||||
</small></span></h2>
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<a href="/#/accounts/" class="btn btn-danger pull-right">Cancel</a>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<form name="createForm" class="form-horizontal" role="form" novalidate>
|
|
||||||
<div class="form-group"
|
|
||||||
ng-class="{'has-error': createForm.name.$invalid, 'has-success': !createForm.name.$invalid&&createForm.name.$dirty}">
|
|
||||||
<label class="control-label col-sm-2">
|
|
||||||
Name
|
|
||||||
</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input name="name" ng-model="account.label" placeholder="Name" class="form-control" required/>
|
|
||||||
<p ng-show="createForm.name.$invalid && !createForm.name.$pristine" class="help-block">You must enter an account name</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group"
|
|
||||||
ng-class="{'has-error': createForm.accountNumber.$invalid, 'has-success': !createForm.accountNumber.$invalid&&createForm.accountNumber.$dirty}">
|
|
||||||
<label class="control-label col-sm-2">
|
|
||||||
Account Number
|
|
||||||
</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="number" name="accountNumber" ng-model="account.accountNumber" placeholder="111111111111" class="form-control" ng-minlength="12" ng-maxlength="12" required/>
|
|
||||||
<p ng-show="createForm.accountNumber.$invalid && !createForm.accountNumber.$pristine" class="help-block">You must enter an account number</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label col-sm-2">
|
|
||||||
Comments
|
|
||||||
</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<textarea name="comments" ng-model="account.comments" placeholder="Something elegant" class="form-control" ></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="panel-footer">
|
|
||||||
<button ng-click="save(account)" type="submit" ng-disabled="createForm.$invalid" class="btn btn-primary pull-right">Save</button>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -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;
|
|
||||||
});
|
|
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
|
@ -1,44 +0,0 @@
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<h2 class="featurette-heading">Accounts
|
|
||||||
<span class="text-muted"><small>next in line please</small></span></h2>
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<div class="btn-group pull-right">
|
|
||||||
<a href="#/accounts/create" class="btn btn-primary">Create</a>
|
|
||||||
</div>
|
|
||||||
<div class="btn-group">
|
|
||||||
<button ng-click="toggleFilter(accountsTable)" class="btn btn-default">Filter</button>
|
|
||||||
</div>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
</div>
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table ng-table="accountsTable" class="table table-striped" show-filter="false" template-pagination="angular/pager.html" >
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="account in $data track by $index">
|
|
||||||
<td data-title="'Name'" sortable="'label'" filter="{ 'label': 'text' }">
|
|
||||||
<ul class="list-unstyled">
|
|
||||||
<li>{{ account.label }}</li>
|
|
||||||
<li><span class="text-muted">{{ account.comments }}</span></li>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td data-title="'Account Number'" sortable="'account_number'" filter="{ 'account_number': 'text' }">
|
|
||||||
{{ account.accountNumber }}
|
|
||||||
</td>
|
|
||||||
<td data-title="''">
|
|
||||||
<div class="btn-group-vertical pull-right">
|
|
||||||
<a tooltip="Edit Account" href="#/accounts/{{ account.id }}/edit" class="btn btn-sm btn-info">
|
|
||||||
Edit
|
|
||||||
</a>
|
|
||||||
<button tooltip="Delete Account" ng-click="remove(account)" type="button" class="btn btn-sm btn-danger pull-left">
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,15 +1,15 @@
|
||||||
from lemur.accounts.service import *
|
from lemur.destinations.service import *
|
||||||
from lemur.accounts.views import *
|
from lemur.destinations.views import *
|
||||||
|
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
|
||||||
|
|
||||||
def test_crud(session):
|
def test_crud(session):
|
||||||
account = create('111111', 'account1')
|
destination = create('111111', 'destination1')
|
||||||
assert account.id > 0
|
assert destination.id > 0
|
||||||
|
|
||||||
account = update(account.id, 11111, 'account2')
|
destination = update(destination.id, 11111, 'destination2')
|
||||||
assert account.label == 'account2'
|
assert destination.label == 'destination2'
|
||||||
|
|
||||||
assert len(get_all()) == 1
|
assert len(get_all()) == 1
|
||||||
|
|
||||||
|
@ -17,116 +17,116 @@ def test_crud(session):
|
||||||
assert len(get_all()) == 0
|
assert len(get_all()) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_account_get(client):
|
def test_destination_get(client):
|
||||||
assert client.get(api.url_for(Accounts, account_id=1)).status_code == 401
|
assert client.get(api.url_for(Destinations, destination_id=1)).status_code == 401
|
||||||
|
|
||||||
|
|
||||||
def test_account_post(client):
|
def test_destination_post(client):
|
||||||
assert client.post(api.url_for(Accounts, account_id=1), data={}).status_code == 405
|
assert client.post(api.url_for(Destinations, destination_id=1), data={}).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
def test_account_put(client):
|
def test_destination_put(client):
|
||||||
assert client.put(api.url_for(Accounts, account_id=1), data={}).status_code == 401
|
assert client.put(api.url_for(Destinations, destination_id=1), data={}).status_code == 401
|
||||||
|
|
||||||
|
|
||||||
def test_account_delete(client):
|
def test_destination_delete(client):
|
||||||
assert client.delete(api.url_for(Accounts, account_id=1)).status_code == 401
|
assert client.delete(api.url_for(Destinations, destination_id=1)).status_code == 401
|
||||||
|
|
||||||
|
|
||||||
def test_account_patch(client):
|
def test_destination_patch(client):
|
||||||
assert client.patch(api.url_for(Accounts, account_id=1), data={}).status_code == 405
|
assert client.patch(api.url_for(Destinations, destination_id=1), data={}).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
VALID_USER_HEADER_TOKEN = {
|
VALID_USER_HEADER_TOKEN = {
|
||||||
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'}
|
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'}
|
||||||
|
|
||||||
def test_auth_account_get(client):
|
def test_auth_destination_get(client):
|
||||||
assert client.get(api.url_for(Accounts, account_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200
|
assert client.get(api.url_for(Destinations, destination_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_auth_account_post_(client):
|
def test_auth_destination_post_(client):
|
||||||
assert client.post(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
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):
|
def test_auth_destination_put(client):
|
||||||
assert client.put(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403
|
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):
|
def test_auth_destination_delete(client):
|
||||||
assert client.delete(api.url_for(Accounts, account_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 403
|
assert client.delete(api.url_for(Destinations, destination_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 403
|
||||||
|
|
||||||
|
|
||||||
def test_auth_account_patch(client):
|
def test_auth_destination_patch(client):
|
||||||
assert client.patch(api.url_for(Accounts, account_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
assert client.patch(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
VALID_ADMIN_HEADER_TOKEN = {
|
VALID_ADMIN_HEADER_TOKEN = {
|
||||||
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'}
|
'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'}
|
||||||
|
|
||||||
def test_admin_account_get(client):
|
def test_admin_destination_get(client):
|
||||||
assert client.get(api.url_for(Accounts, account_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
|
assert client.get(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_admin_account_post(client):
|
def test_admin_destination_post(client):
|
||||||
assert client.post(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
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):
|
def test_admin_destination_put(client):
|
||||||
assert client.put(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400
|
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):
|
def test_admin_destination_delete(client):
|
||||||
assert client.delete(api.url_for(Accounts, account_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 500
|
assert client.delete(api.url_for(Destinations, destination_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 500
|
||||||
|
|
||||||
|
|
||||||
def test_admin_account_patch(client):
|
def test_admin_destination_patch(client):
|
||||||
assert client.patch(api.url_for(Accounts, account_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
assert client.patch(api.url_for(Destinations, destination_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
def test_accounts_get(client):
|
def test_destinations_get(client):
|
||||||
assert client.get(api.url_for(AccountsList)).status_code == 401
|
assert client.get(api.url_for(DestinationsList)).status_code == 401
|
||||||
|
|
||||||
|
|
||||||
def test_accounts_post(client):
|
def test_destinations_post(client):
|
||||||
assert client.post(api.url_for(AccountsList), data={}).status_code == 401
|
assert client.post(api.url_for(DestinationsList), data={}).status_code == 401
|
||||||
|
|
||||||
|
|
||||||
def test_accounts_put(client):
|
def test_destinations_put(client):
|
||||||
assert client.put(api.url_for(AccountsList), data={}).status_code == 405
|
assert client.put(api.url_for(DestinationsList), data={}).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
def test_accounts_delete(client):
|
def test_destinations_delete(client):
|
||||||
assert client.delete(api.url_for(AccountsList)).status_code == 405
|
assert client.delete(api.url_for(DestinationsList)).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
def test_accounts_patch(client):
|
def test_destinations_patch(client):
|
||||||
assert client.patch(api.url_for(AccountsList), data={}).status_code == 405
|
assert client.patch(api.url_for(DestinationsList), data={}).status_code == 405
|
||||||
|
|
||||||
|
|
||||||
def test_auth_accounts_get(client):
|
def test_auth_destinations_get(client):
|
||||||
assert client.get(api.url_for(AccountsList), headers=VALID_USER_HEADER_TOKEN).status_code == 200
|
assert client.get(api.url_for(DestinationsList), headers=VALID_USER_HEADER_TOKEN).status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_auth_accounts_post(client):
|
def test_auth_destinations_post(client):
|
||||||
assert client.post(api.url_for(AccountsList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403
|
assert client.post(api.url_for(DestinationsList), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403
|
||||||
|
|
||||||
|
|
||||||
def test_admin_accounts_get(client):
|
def test_admin_destinations_get(client):
|
||||||
resp = client.get(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN)
|
resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert resp.json == {'items': [], 'total': 0}
|
assert resp.json == {'items': [], 'total': 0}
|
||||||
|
|
||||||
|
|
||||||
def test_admin_accounts_crud(client):
|
def test_admin_destinations_crud(client):
|
||||||
assert client.post(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400
|
assert client.post(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400
|
||||||
data = {'accountNumber': 111, 'label': 'test', 'comments': 'test'}
|
data = {'destinationNumber': 111, 'label': 'test', 'comments': 'test'}
|
||||||
resp = client.post(api.url_for(AccountsList), data=dumps(data), content_type='application/json', headers=VALID_ADMIN_HEADER_TOKEN)
|
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 resp.status_code == 200
|
||||||
assert client.get(api.url_for(Accounts, account_id=resp.json['id']), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
|
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(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN)
|
resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert resp.json == {'items': [{'accountNumber': 111, 'label': 'test', 'comments': 'test', 'id': 2}], 'total': 1}
|
assert resp.json == {'items': [{'destinationNumber': 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
|
assert client.delete(api.url_for(Destinations, destination_id=2), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200
|
||||||
resp = client.get(api.url_for(AccountsList), headers=VALID_ADMIN_HEADER_TOKEN)
|
resp = client.get(api.url_for(DestinationsList), headers=VALID_ADMIN_HEADER_TOKEN)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert resp.json == {'items': [], 'total': 0}
|
assert resp.json == {'items': [], 'total': 0}
|
||||||
|
|
Loading…
Reference in New Issue