This commit is contained in:
kevgliss
2016-06-27 14:40:46 -07:00
committed by GitHub
parent b44a7c73d8
commit fe9703dd94
36 changed files with 1140 additions and 187 deletions

View File

@ -0,0 +1,23 @@
"""
.. module: lemur.plugins.lemur_aws.elb
:synopsis: Module contains some often used and helpful classes that
are used to deal with ELBs
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from lemur.plugins.lemur_aws.sts import sts_client
@sts_client('ec2')
def get_regions(**kwargs):
regions = kwargs['client'].describe_regions()
return [x['RegionName'] for x in regions['Regions']]
@sts_client('ec2')
def get_all_instances(**kwargs):
"""
Fetches all instance objects for a given account and region.
"""
paginator = kwargs['client'].get_paginator('describe_instances')
return paginator.paginate()

View File

@ -5,12 +5,10 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
import boto.ec2
from flask import current_app
from lemur.exceptions import InvalidListener
from lemur.plugins.lemur_aws.sts import assume_service
from lemur.plugins.lemur_aws.sts import sts_client, assume_service
def is_valid(listener_tuple):
@ -38,41 +36,34 @@ def is_valid(listener_tuple):
return listener_tuple
def get_all_regions():
"""
Retrieves all current EC2 regions.
:return:
"""
regions = []
for r in boto.ec2.regions():
regions.append(r.name)
return regions
def get_all_elbs(account_number, region):
@sts_client('elb')
def get_all_elbs(**kwargs):
"""
Fetches all elb objects for a given account and region.
:param account_number:
:param region:
"""
marker = None
elbs = []
return assume_service(account_number, 'elb', region).get_all_load_balancers()
# TODO create pull request for boto to include elb marker support
# while True:
# app.logger.debug(response.__dict__)
# raise Exception
# result = response['list_server_certificates_response']['list_server_certificates_result']
#
# for elb in result['server_certificate_metadata_list']:
# elbs.append(elb)
#
# if result['is_truncated'] == 'true':
# marker = result['marker']
# else:
# return elbs
return kwargs['client'].describe_load_balancers()
@sts_client('elb')
def describe_load_balancer_policies(load_balancer_name, policy_names, **kwargs):
"""
Fetching all policies currently associated with an ELB.
:param load_balancer_name:
:return:
"""
return kwargs['client'].describe_load_balancer_policies(LoadBalancerName=load_balancer_name, PolicyNames=policy_names)
@sts_client('elb')
def describe_load_balancer_types(policies, **kwargs):
"""
Describe the policies with policy details.
:param policies:
:return:
"""
return kwargs['client'].describe_load_balancer_policy_types(PolicyTypeNames=policies)
def attach_certificate(account_number, region, name, port, certificate_id):
@ -89,67 +80,67 @@ def attach_certificate(account_number, region, name, port, certificate_id):
return assume_service(account_number, 'elb', region).set_lb_listener_SSL_certificate(name, port, certificate_id)
def create_new_listeners(account_number, region, name, listeners=None):
"""
Creates a new listener and attaches it to the ELB.
:param account_number:
:param region:
:param name:
:param listeners:
:return:
"""
listeners = [is_valid(x) for x in listeners]
return assume_service(account_number, 'elb', region).create_load_balancer_listeners(name, listeners=listeners)
def update_listeners(account_number, region, name, listeners, ports):
"""
We assume that a listener with a specified port already exists. We can then
delete the old listener on the port and create a new one in it's place.
If however we are replacing a listener e.g. changing a port from 80 to 443 we need
to make sure we kept track of which ports we needed to delete so that we don't create
two listeners (one 80 and one 443)
:param account_number:
:param region:
:param name:
:param listeners:
:param ports:
"""
# you cannot update a listeners port/protocol instead we remove the only one and
# create a new one in it's place
listeners = [is_valid(x) for x in listeners]
assume_service(account_number, 'elb', region).delete_load_balancer_listeners(name, ports)
return create_new_listeners(account_number, region, name, listeners=listeners)
def delete_listeners(account_number, region, name, ports):
"""
Deletes a listener from an ELB.
:param account_number:
:param region:
:param name:
:param ports:
:return:
"""
return assume_service(account_number, 'elb', region).delete_load_balancer_listeners(name, ports)
def get_listeners(account_number, region, name):
"""
Gets the listeners configured on an elb and returns a array of tuples
:param account_number:
:param region:
:param name:
:return: list of tuples
"""
conn = assume_service(account_number, 'elb', region)
elbs = conn.get_all_load_balancers(load_balancer_names=[name])
if elbs:
return elbs[0].listeners
# def create_new_listeners(account_number, region, name, listeners=None):
# """
# Creates a new listener and attaches it to the ELB.
#
# :param account_number:
# :param region:
# :param name:
# :param listeners:
# :return:
# """
# listeners = [is_valid(x) for x in listeners]
# return assume_service(account_number, 'elb', region).create_load_balancer_listeners(name, listeners=listeners)
#
#
# def update_listeners(account_number, region, name, listeners, ports):
# """
# We assume that a listener with a specified port already exists. We can then
# delete the old listener on the port and create a new one in it's place.
#
# If however we are replacing a listener e.g. changing a port from 80 to 443 we need
# to make sure we kept track of which ports we needed to delete so that we don't create
# two listeners (one 80 and one 443)
#
# :param account_number:
# :param region:
# :param name:
# :param listeners:
# :param ports:
# """
# # you cannot update a listeners port/protocol instead we remove the only one and
# # create a new one in it's place
# listeners = [is_valid(x) for x in listeners]
#
# assume_service(account_number, 'elb', region).delete_load_balancer_listeners(name, ports)
# return create_new_listeners(account_number, region, name, listeners=listeners)
#
#
# def delete_listeners(account_number, region, name, ports):
# """
# Deletes a listener from an ELB.
#
# :param account_number:
# :param region:
# :param name:
# :param ports:
# :return:
# """
# return assume_service(account_number, 'elb', region).delete_load_balancer_listeners(name, ports)
#
#
# def get_listeners(account_number, region, name):
# """
# Gets the listeners configured on an elb and returns a array of tuples
#
# :param account_number:
# :param region:
# :param name:
# :return: list of tuples
# """
#
# conn = assume_service(account_number, 'elb', region)
# elbs = conn.get_all_load_balancers(load_balancer_names=[name])
# if elbs:
# return elbs[0].listeners

View File

@ -6,18 +6,16 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from flask import current_app
from boto.exception import BotoServerError
from lemur.plugins.bases import DestinationPlugin, SourcePlugin
from lemur.plugins.lemur_aws import iam, elb
from lemur.plugins.lemur_aws import iam
from lemur.plugins.lemur_aws.ec2 import get_regions
from lemur.plugins.lemur_aws.elb import get_all_elbs, describe_load_balancer_policies, attach_certificate
from lemur.plugins import lemur_aws as aws
def find_value(name, options):
for o in options:
if o['name'] == name:
return o['value']
class AWSDestinationPlugin(DestinationPlugin):
title = 'AWS'
slug = 'aws-destination'
@ -45,14 +43,14 @@ class AWSDestinationPlugin(DestinationPlugin):
def upload(self, name, body, private_key, cert_chain, options, **kwargs):
if private_key:
try:
iam.upload_cert(find_value('accountNumber', options), name, body, private_key, cert_chain=cert_chain)
iam.upload_cert(self.get_option('accountNumber', options), name, body, private_key, cert_chain=cert_chain)
except BotoServerError as e:
if e.error_code != 'EntityAlreadyExists':
raise Exception(e)
e = find_value('elb', options)
e = self.get_option('elb', options)
if e:
elb.attach_certificate(kwargs['accountNumber'], ['region'], e['name'], e['port'], e['certificateId'])
attach_certificate(kwargs['accountNumber'], ['region'], e['name'], e['port'], e['certificateId'])
else:
raise Exception("Unable to upload to AWS, private key is required")
@ -60,7 +58,7 @@ class AWSDestinationPlugin(DestinationPlugin):
class AWSSourcePlugin(SourcePlugin):
title = 'AWS'
slug = 'aws-source'
description = 'Discovers all SSL certificates in an AWS account'
description = 'Discovers all SSL certificates and ELB endpoints in an AWS account'
version = aws.VERSION
author = 'Kevin Glisson'
@ -74,11 +72,16 @@ class AWSSourcePlugin(SourcePlugin):
'validation': '/^[0-9]{12,12}$/',
'helpMessage': 'Must be a valid AWS account number!',
},
{
'name': 'regions',
'type': 'str',
'helpMessage': 'Comma separated list of regions to search in, if no region is specified we look in all regions.'
},
]
def get_certificates(self, options, **kwargs):
certs = []
arns = iam.get_all_server_certs(find_value('accountNumber', options))
arns = iam.get_all_server_certs(self.get_option('accountNumber', options))
for arn in arns:
cert_body, cert_chain = iam.get_cert_from_arn(arn)
cert_name = iam.get_name_from_arn(arn)
@ -89,3 +92,57 @@ class AWSSourcePlugin(SourcePlugin):
)
certs.append(cert)
return certs
def get_endpoints(self, options, **kwargs):
endpoints = []
account_number = self.get_option('accountNumber', options)
regions = self.get_option('regions', options)
if not regions:
regions = get_regions(account_number=account_number)
else:
regions = regions.split(',')
for region in regions:
elbs = get_all_elbs(account_number=account_number, region=region)
current_app.logger.info("Describing load balancers in {0}-{1}".format(account_number, region))
for elb in elbs['LoadBalancerDescriptions']:
for listener in elb['ListenerDescriptions']:
if not listener['Listener'].get('SSLCertificateId'):
continue
endpoint = dict(
name=elb['LoadBalancerName'],
dnsname=elb['DNSName'],
type='elb',
port=listener['Listener']['LoadBalancerPort'],
certificate_name=iam.get_name_from_arn(listener['Listener']['SSLCertificateId'])
)
if listener['PolicyNames']:
policy = describe_load_balancer_policies(elb['LoadBalancerName'], listener['PolicyNames'], account_number=account_number, region=region)
endpoint['policy'] = format_elb_cipher_policy(policy)
endpoints.append(endpoint)
return endpoints
def format_elb_cipher_policy(policy):
"""
Attempts to format cipher policy information into a common format.
:param policy:
:return:
"""
ciphers = []
name = None
for descr in policy['PolicyDescriptions']:
for attr in descr['PolicyAttributeDescriptions']:
if attr['AttributeName'] == 'Reference-Security-Policy':
name = attr['AttributeValue']
continue
if attr['AttributeValue'] == 'true':
ciphers.append(attr['AttributeName'])
return dict(name=name, ciphers=ciphers)

View File

@ -5,13 +5,16 @@
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from functools import wraps
import boto
import boto.ec2.elb
import boto3
from flask import current_app
def assume_service(account_number, service, region=None):
def assume_service(account_number, service, region='us-east-1'):
conn = boto.connect_sts()
role = conn.assume_role('arn:aws:iam::{0}:role/{1}'.format(
@ -35,3 +38,40 @@ def assume_service(account_number, service, region=None):
aws_access_key_id=role.credentials.access_key,
aws_secret_access_key=role.credentials.secret_key,
security_token=role.credentials.session_token)
def sts_client(service, service_type='client'):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
sts = boto3.client('sts')
arn = 'arn:aws:iam::{0}:role/{1}'.format(
kwargs.pop('account_number'),
current_app.config.get('LEMUR_INSTANCE_PROFILE', 'Lemur')
)
# TODO add user specific information to RoleSessionName
role = sts.assume_role(RoleArn=arn, RoleSessionName='lemur')
if service_type == 'client':
client = boto3.client(
service,
region_name=kwargs.pop('region', 'us-east-1'),
aws_access_key_id=role['Credentials']['AccessKeyId'],
aws_secret_access_key=role['Credentials']['SecretAccessKey'],
aws_session_token=role['Credentials']['SessionToken']
)
kwargs['client'] = client
elif service_type == 'resource':
resource = boto3.resource(
service,
region_name=kwargs.pop('region', 'us-east-1'),
aws_access_key_id=role['Credentials']['AccessKeyId'],
aws_secret_access_key=role['Credentials']['SecretAccessKey'],
aws_session_token=role['Credentials']['SessionToken']
)
kwargs['resource'] = resource
return f(*args, **kwargs)
return decorated_function
return decorator

View File

@ -0,0 +1 @@
from lemur.tests.conftest import * # noqa

View File

@ -0,0 +1,14 @@
import boto
from moto import mock_sts, mock_elb
@mock_sts()
@mock_elb()
def test_get_all_elbs(app):
from lemur.plugins.lemur_aws.elb import get_all_elbs
conn = boto.ec2.elb.connect_to_region('us-east-1')
elbs = get_all_elbs(account_number='123456789012', region='us-east-1')
assert not elbs['LoadBalancerDescriptions']
conn.create_load_balancer('example-lb', ['us-east-1a', 'us-east-1b'], [(443, 5443, 'tcp')])
elbs = get_all_elbs(account_number='123456789012', region='us-east-1')
assert elbs['LoadBalancerDescriptions']

View File

@ -23,7 +23,7 @@
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td align="left" style="font-family:Roboto-Regular,Helvetica,Arial,sans-serif;font-size:35px;color:#727272;" line-height:1.5">
<td align="left" style="font-family:Roboto-Regular,Helvetica,Arial,sans-serif;font-size:35px;color:#727272; line-height:1.5">
Lemur
</td>
</tr>
@ -83,12 +83,15 @@
<tr valign="middle">
<td width="32px"></td>
<td width="16px"></td>
<td style="line-height:1.2"><span
style="font-family:Roboto-Regular,Helvetica,Arial,sans-serif;font-size:20px;color:#202020">{{ message.name }}</span><br><span
style="font-family:Roboto-Regular,Helvetica,Arial,sans-serif;font-size:13px;color:#727272">{{ message.owner }}
<td style="line-height:1.2">
<span style="font-family:Roboto-Regular,Helvetica,Arial,sans-serif;font-size:20px;color:#202020">{{ message.name }}</span>
<br>
<span style="font-family:Roboto-Regular,Helvetica,Arial,sans-serif;font-size:13px;color:#727272">
{{ message.endpoints | length }} Endpoints
<br>{{ message.owner }}
<br>{{ message.not_after | time }}
<a href="https://{{ hostname }}/#/certificates/{{ message.name }}" target="_blank">Details</a>
</span>
</span>
</td>
</tr>
{% if not loop.last %}