23
lemur/plugins/lemur_aws/ec2.py
Normal file
23
lemur/plugins/lemur_aws/ec2.py
Normal 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()
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
1
lemur/plugins/lemur_aws/tests/conftest.py
Normal file
1
lemur/plugins/lemur_aws/tests/conftest.py
Normal file
@ -0,0 +1 @@
|
||||
from lemur.tests.conftest import * # noqa
|
14
lemur/plugins/lemur_aws/tests/test_elb.py
Normal file
14
lemur/plugins/lemur_aws/tests/test_elb.py
Normal 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']
|
0
lemur/plugins/lemur_aws/tests/test_plugin.py
Normal file
0
lemur/plugins/lemur_aws/tests/test_plugin.py
Normal 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 %}
|
||||
|
Reference in New Issue
Block a user