diff --git a/lemur/manage.py b/lemur/manage.py index 1b53c591..ced44429 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals # at top of module + import os import sys import base64 @@ -478,6 +480,197 @@ def unlock(path=None): 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(): manager.add_command("start", LemurServer()) manager.add_command("runserver", Server(host='127.0.0.1')) @@ -487,8 +680,8 @@ def main(): manager.add_command("init", InitializeApp()) manager.add_command("create_user", CreateUser()) manager.add_command("create_role", CreateRole()) + manager.add_command("provision_elb", ProvisionELB()) manager.run() - if __name__ == "__main__": main() diff --git a/lemur/plugins/base/v1.py b/lemur/plugins/base/v1.py index 7026c99f..ec1e74ed 100644 --- a/lemur/plugins/base/v1.py +++ b/lemur/plugins/base/v1.py @@ -110,7 +110,7 @@ class IPlugin(local): def get_option(self, name, options): for o in options: - if o.get(name): + if o.get('name') == name: return o['value'] diff --git a/lemur/plugins/lemur_aws/elb.py b/lemur/plugins/lemur_aws/elb.py index b263d473..1b9226fb 100644 --- a/lemur/plugins/lemur_aws/elb.py +++ b/lemur/plugins/lemur_aws/elb.py @@ -138,3 +138,19 @@ def delete_listeners(account_number, region, name, 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