Refactored 'accounts' to be more general with 'destinations'
This commit is contained in:
@ -0,0 +1,5 @@
|
||||
try:
|
||||
VERSION = __import__('pkg_resources') \
|
||||
.get_distribution(__name__).version
|
||||
except Exception, e:
|
||||
VERSION = 'unknown'
|
140
lemur/plugins/lemur_aws/elb.py
Normal file
140
lemur/plugins/lemur_aws/elb.py
Normal file
@ -0,0 +1,140 @@
|
||||
"""
|
||||
.. module:: elb
|
||||
:synopsis: Module contains some often used and helpful classes that
|
||||
are used to deal with ELBs
|
||||
|
||||
.. 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
|
||||
|
||||
|
||||
def is_valid(listener_tuple):
|
||||
"""
|
||||
There are a few rules that aws has when creating listeners,
|
||||
this function ensures those rules are met before we try and create
|
||||
or update a listener.
|
||||
|
||||
While these could be caught with boto exception handling, I would
|
||||
rather be nice and catch these early before we sent them out to aws.
|
||||
It also gives us an opportunity to create nice user warnings.
|
||||
|
||||
This validity check should also be checked in the frontend
|
||||
but must also be enforced by server.
|
||||
|
||||
:param listener_tuple:
|
||||
"""
|
||||
|
||||
current_app.logger.debug(listener_tuple)
|
||||
lb_port, i_port, lb_protocol, arn = listener_tuple
|
||||
current_app.logger.debug(lb_protocol)
|
||||
if lb_protocol.lower() in ['ssl', 'https']:
|
||||
if not arn:
|
||||
raise InvalidListener
|
||||
|
||||
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):
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
|
||||
def attach_certificate(account_number, region, name, port, certificate_id):
|
||||
"""
|
||||
Attaches a certificate to a listener, throws exception
|
||||
if certificate specified does not exist in a particular account.
|
||||
|
||||
:param account_number:
|
||||
:param region:
|
||||
:param name:
|
||||
:param port:
|
||||
:param 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)
|
||||
|
113
lemur/plugins/lemur_aws/iam.py
Normal file
113
lemur/plugins/lemur_aws/iam.py
Normal file
@ -0,0 +1,113 @@
|
||||
"""
|
||||
.. module: lemur.common.services.aws.iam
|
||||
:platform: Unix
|
||||
:synopsis: Contains helper functions for interactive with AWS IAM Apis.
|
||||
: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.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):
|
||||
"""
|
||||
|
||||
:param param_string:
|
||||
:return:
|
||||
"""
|
||||
output = {}
|
||||
parts = str(param_string).split("/")
|
||||
for part in parts:
|
||||
if "=" in part:
|
||||
key, value = part.split("=", 1)
|
||||
output[key] = value
|
||||
return output
|
||||
|
||||
|
||||
def upload_cert(account_number, cert, private_key, cert_chain=None):
|
||||
"""
|
||||
Upload a certificate to AWS
|
||||
|
||||
:param account_number:
|
||||
:param cert:
|
||||
:param private_key:
|
||||
:param cert_chain:
|
||||
:return:
|
||||
"""
|
||||
return assume_service(account_number, 'iam').upload_server_cert(cert.name, str(cert.body), str(private_key), cert_chain=str(cert_chain))
|
||||
|
||||
|
||||
def delete_cert(account_number, cert):
|
||||
"""
|
||||
Delete a certificate from AWS
|
||||
|
||||
:param account_number:
|
||||
:param cert:
|
||||
:return:
|
||||
"""
|
||||
return assume_service(account_number, 'iam').delete_server_cert(cert.name)
|
||||
|
||||
|
||||
def get_all_server_certs(account_number):
|
||||
"""
|
||||
Use STS to fetch all of the SSL certificates from a given account
|
||||
|
||||
:param account_number:
|
||||
"""
|
||||
marker = None
|
||||
certs = []
|
||||
while True:
|
||||
response = assume_service(account_number, 'iam').get_all_server_certs(marker=marker)
|
||||
result = response['list_server_certificates_response']['list_server_certificates_result']
|
||||
|
||||
for cert in result['server_certificate_metadata_list']:
|
||||
certs.append(cert)
|
||||
|
||||
if result['is_truncated'] == 'true':
|
||||
marker = result['marker']
|
||||
else:
|
||||
return certs
|
||||
|
||||
|
||||
def get_cert_from_arn(arn):
|
||||
"""
|
||||
Retrieves an SSL certificate from a given ARN.
|
||||
|
||||
:param arn:
|
||||
:return:
|
||||
"""
|
||||
name = arn.split("/", 1)[1]
|
||||
account_number = arn.split(":")[4]
|
||||
name = name.split("/")[-1]
|
||||
|
||||
response = assume_service(account_number, 'iam').get_server_certificate(name.strip())
|
||||
return digest_aws_cert_response(response)
|
||||
|
||||
|
||||
def digest_aws_cert_response(response):
|
||||
"""
|
||||
Processes an AWS certifcate response and retrieves the certificate body and chain.
|
||||
|
||||
:param response:
|
||||
:return:
|
||||
"""
|
||||
chain = None
|
||||
cert = response['get_server_certificate_response']['get_server_certificate_result']['server_certificate']
|
||||
body = cert['certificate_body']
|
||||
|
||||
if 'certificate_chain' in cert:
|
||||
chain = cert['certificate_chain']
|
||||
|
||||
return str(body), str(chain),
|
||||
|
||||
|
77
lemur/plugins/lemur_aws/plugin.py
Normal file
77
lemur/plugins/lemur_aws/plugin.py
Normal file
@ -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
|
41
lemur/plugins/lemur_aws/sts.py
Normal file
41
lemur/plugins/lemur_aws/sts.py
Normal file
@ -0,0 +1,41 @@
|
||||
"""
|
||||
.. module: lemur.common.services.aws.sts
|
||||
: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 boto
|
||||
import boto.ec2.elb
|
||||
|
||||
from flask import current_app
|
||||
|
||||
|
||||
def assume_service(account_number, service, region=None):
|
||||
conn = boto.connect_sts()
|
||||
|
||||
role = conn.assume_role('arn:aws:iam::{0}:role/{1}'.format(
|
||||
account_number, current_app.config.get('LEMUR_INSTANCE_PROFILE', 'Lemur')), 'blah')
|
||||
|
||||
if service in 'iam':
|
||||
return boto.connect_iam(
|
||||
aws_access_key_id=role.credentials.access_key,
|
||||
aws_secret_access_key=role.credentials.secret_key,
|
||||
security_token=role.credentials.session_token)
|
||||
|
||||
elif service in 'elb':
|
||||
return boto.ec2.elb.connect_to_region(
|
||||
region,
|
||||
aws_access_key_id=role.credentials.access_key,
|
||||
aws_secret_access_key=role.credentials.secret_key,
|
||||
security_token=role.credentials.session_token)
|
||||
|
||||
elif service in 'vpc':
|
||||
return boto.connect_vpc(
|
||||
aws_access_key_id=role.credentials.access_key,
|
||||
aws_secret_access_key=role.credentials.secret_key,
|
||||
security_token=role.credentials.session_token)
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user