Merge pull request #51 from jeremy-h/elb-ssl-automation

Elb ssl automation
This commit is contained in:
kevgliss 2015-08-29 09:52:35 -07:00
commit 6ec5d26f0c
3 changed files with 211 additions and 2 deletions

View File

@ -1,3 +1,5 @@
from __future__ import unicode_literals # at top of module
import os import os
import sys import sys
import base64 import base64
@ -478,6 +480,197 @@ def unlock(path=None):
sys.stdout.write("[+] Keys have been unencrypted!\n") sys.stdout.write("[+] Keys have been unencrypted!\n")
def unicode_(data):
import sys
if sys.version_info.major < 3:
return data.decode('UTF-8')
return data
class ProvisionELB(Command):
"""
Creates and provisions a certificate on an ELB based on command line arguments
"""
option_list = (
Option('-d', '--dns', dest='dns', action='append', required=True, type=unicode_),
Option('-e', '--elb', dest='elb_name', required=True, type=unicode_),
Option('-o', '--owner', dest='owner', type=unicode_),
Option('-a', '--authority', dest='authority', required=True, type=unicode_),
Option('-s', '--description', dest='description', default=u'Command line provisioned keypair', type=unicode_),
Option('-t', '--destination', dest='destinations', action='append', type=unicode_, required=True),
Option('-n', '--notification', dest='notifications', action='append', type=unicode_, default=[]),
Option('-r', '--region', dest='region', default=u'us-east-1', type=unicode_),
Option('-p', '--dport', '--port', dest='dport', default=7002),
Option('--src-port', '--source-port', '--sport', dest='sport', default=443),
Option('--dry-run', dest='dryrun', action='store_true')
)
def configure_user(self, owner):
from flask import g
import lemur.users.service
# grab the user
g.user = lemur.users.service.get_by_username(owner)
# get the first user by default
if not g.user:
g.user = lemur.users.service.get_all()[0]
return g.user.username
def build_cert_options(self, destinations, notifications, description, owner, dns, authority):
from sqlalchemy.orm.exc import NoResultFound
from lemur.certificates.views import valid_authority
import sys
# convert argument lists to arrays, or empty sets
destinations = self.get_destinations(destinations)
if not destinations:
sys.stderr.write("Valid destinations provided\n")
sys.exit(1)
# get the primary CN
common_name = dns[0]
# If there are more than one fqdn, add them as alternate names
extensions = {}
if len(dns) > 1:
extensions['subAltNames'] = {'names': map(lambda x: {'nameType': 'DNSName', 'value': x}, dns)}
try:
authority = valid_authority({"name": authority})
except NoResultFound:
sys.stderr.write("Invalid authority specified: '{}'\naborting\n".format(authority))
sys.exit(1)
options = {
# Convert from the Destination model to the JSON input expected further in the code
'destinations': map(lambda x: {'id': x.id, 'label': x.label}, destinations),
'description': description,
'notifications': notifications,
'commonName': common_name,
'extensions': extensions,
'authority': authority,
'owner': owner,
# defaults:
'organization': u'Netflix, Inc.',
'organizationalUnit': u'Operations',
'country': u'US',
'state': u'California',
'location': u'Los Gatos'
}
return options
def get_destinations(self, destination_names):
from lemur.destinations import service
destinations = []
for destination_name in destination_names:
destination = service.get_by_label(destination_name)
if not destination:
sys.stderr.write("Invalid destination specified: '{}'\nAborting...\n".format(destination_name))
sys.exit(1)
destinations.append(service.get_by_label(destination_name))
return destinations
def check_duplicate_listener(self, elb_name, region, account, sport, dport):
from lemur.plugins.lemur_aws import elb
listeners = elb.get_listeners(account, region, elb_name)
for listener in listeners:
if listener[0] == sport and listener[1] == dport:
return True
return False
def get_destination_account(self, destinations):
for destination in self.get_destinations(destinations):
if destination.plugin_name == 'aws-destination':
account_number = destination.plugin.get_option('accountNumber', destination.options)
return account_number
sys.stderr.write("No destination AWS account provided, failing\n")
sys.exit(1)
def run(self, dns, elb_name, owner, authority, description, notifications, destinations, region, dport, sport,
dryrun):
from lemur.certificates import service
from lemur.plugins.lemur_aws import elb
from boto.exception import BotoServerError
# configure the owner if we can find it, or go for default, and put it in the global
owner = self.configure_user(owner)
# make a config blob from the command line arguments
cert_options = self.build_cert_options(
destinations=destinations,
notifications=notifications,
description=description,
owner=owner,
dns=dns,
authority=authority)
aws_account = self.get_destination_account(destinations)
if dryrun:
import json
cert_options['authority'] = cert_options['authority'].name
sys.stdout.write('Will create certificate using options: {}\n'
.format(json.dumps(cert_options, sort_keys=True, indent=2)))
sys.stdout.write('Will create listener {}->{} HTTPS using the new certificate to elb {}\n'
.format(sport, dport, elb_name))
sys.exit(0)
if self.check_duplicate_listener(elb_name, region, aws_account, sport, dport):
sys.stderr.write("ELB {} already has a listener {}->{}\nAborting...\n".format(elb_name, sport, dport))
sys.exit(1)
# create the certificate
try:
sys.stdout.write('Creating certificate for {}\n'.format(cert_options['commonName']))
cert = service.create(**cert_options)
except Exception as e:
if e.message == 'Duplicate certificate: a certificate with the same common name exists already':
sys.stderr.write("Certificate already exists named: {}\n".format(dns[0]))
sys.exit(1)
raise e
cert_arn = cert.get_arn(aws_account)
sys.stderr.write('cert arn: {}\n'.format(cert_arn))
sys.stderr.write('Configuring elb {} from port {} to port {} in region {} with cert {}\n'
.format(elb_name, sport, dport, region, cert_arn))
delay = 1
done = False
retries = 5
while not done and retries > 0:
try:
elb.create_new_listeners(aws_account, region, elb_name, [(sport, dport, 'HTTPS', cert_arn)])
except BotoServerError as bse:
# if the server returns ad error, the certificate
if bse.error_code == 'CertificateNotFound':
sys.stderr.write('Certificate not available yet in the AWS account, waiting {}, {} retries left\n'
.format(delay, retries))
time.sleep(delay)
delay *= 2
retries -= 1
elif bse.error_code == 'DuplicateListener':
sys.stderr.write('ELB {} already has a listener {}->{}'.format(elb_name, sport, dport))
sys.exit(1)
else:
raise bse
else:
done = True
def main(): def main():
manager.add_command("start", LemurServer()) manager.add_command("start", LemurServer())
manager.add_command("runserver", Server(host='127.0.0.1')) manager.add_command("runserver", Server(host='127.0.0.1'))
@ -487,8 +680,8 @@ def main():
manager.add_command("init", InitializeApp()) manager.add_command("init", InitializeApp())
manager.add_command("create_user", CreateUser()) manager.add_command("create_user", CreateUser())
manager.add_command("create_role", CreateRole()) manager.add_command("create_role", CreateRole())
manager.add_command("provision_elb", ProvisionELB())
manager.run() manager.run()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -110,7 +110,7 @@ class IPlugin(local):
def get_option(self, name, options): def get_option(self, name, options):
for o in options: for o in options:
if o.get(name): if o.get('name') == name:
return o['value'] return o['value']

View File

@ -138,3 +138,19 @@ def delete_listeners(account_number, region, name, ports):
:return: :return:
""" """
return assume_service(account_number, 'elb', region).delete_load_balancer_listeners(name, ports) 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