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.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):
|
||||
|
|
|
@ -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.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')
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 <kglisson@netflix.com>
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
if not exists:
|
||||
cert_service.import_certificate(**certificate)
|
||||
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)
|
||||
if len(exists) == 1:
|
||||
updated += 1
|
||||
|
||||
else:
|
||||
current_app.logger.error(
|
||||
"Multiple certificates with the same body found, unable to correctly determine which entry to update"
|
||||
)
|
||||
# TODO associated cert with source
|
||||
# TODO update cert if found from different source
|
||||
# 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('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()
|
||||
|
|
|
@ -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
|
||||
: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/<int:certificate_id/accounts'' endpoint """
|
||||
class CertificateDestinations(AuthenticatedResource):
|
||||
""" Defines the 'certificate/<int:certificate_id/destinations'' endpoint """
|
||||
def __init__(self):
|
||||
super(CertificateAccounts, self).__init__()
|
||||
super(CertificateDestinations, self).__init__()
|
||||
|
||||
@marshal_items(FIELDS)
|
||||
def get(self, certificate_id):
|
||||
"""
|
||||
.. http:get:: /certificates/1/accounts
|
||||
.. http:get:: /certificates/1/destinations
|
||||
|
||||
The current account list for a given certificates
|
||||
|
||||
|
@ -250,7 +246,7 @@ class CertificateAccounts(AuthenticatedResource):
|
|||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /certificates/1/accounts HTTP/1.1
|
||||
GET /certificates/1/destinations HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
|
@ -262,24 +258,6 @@ class CertificateAccounts(AuthenticatedResource):
|
|||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 2,
|
||||
"accountNumber": 222222222,
|
||||
"label": "account2",
|
||||
"comments": "this is a thing"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"accountNumber": 11111111111,
|
||||
"label": "account1",
|
||||
"comments": "this is a thing"
|
||||
},
|
||||
]
|
||||
"total": 2
|
||||
}
|
||||
|
||||
:query sortBy: field to sort on
|
||||
:query sortDir: acs or desc
|
||||
:query page: int. default is 1
|
||||
|
@ -294,7 +272,7 @@ class CertificateAccounts(AuthenticatedResource):
|
|||
return service.render(args)
|
||||
|
||||
|
||||
api.add_resource(AccountsList, '/accounts', endpoint='accounts')
|
||||
api.add_resource(Accounts, '/accounts/<int:account_id>', endpoint='account')
|
||||
api.add_resource(CertificateAccounts, '/certificates/<int:certificate_id>/accounts', endpoint='certificateAccounts')
|
||||
api.add_resource(DestinationsList, '/destinations', endpoint='destinations')
|
||||
api.add_resource(Destinations, '/destinations/<int:destination_id>', endpoint='account')
|
||||
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):
|
||||
__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))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'):
|
||||
if plugins.get('aws-destination'):
|
||||
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:
|
||||
account_service.create(account_number, label=account_name)
|
||||
sys.stdout.write("[+] Added new account {0}:{1}!\n".format(account_number, account_name))
|
||||
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")
|
||||
|
||||
|
||||
|
|
|
@ -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'))
|
||||
)
|
||||
|
||||
|
|
|
@ -9,5 +9,8 @@
|
|||
from lemur.plugins.base import 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 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):
|
|
@ -6,8 +6,17 @@
|
|||
:license: Apache, see LICENSE for more details.
|
||||
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
|
||||
"""
|
||||
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):
|
|
@ -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.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}
|
||||
|
|
Loading…
Reference in New Issue