From ab7b0c442ca34d91b182b7be63cf2f8a4cd54d65 Mon Sep 17 00:00:00 2001 From: Jeremy Heffner Date: Wed, 19 Aug 2015 16:10:45 -0700 Subject: [PATCH 01/11] provisionelb creates certs. needs some cleanup and the rest of the glue --- lemur/manage.py | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/lemur/manage.py b/lemur/manage.py index 8cee39e0..9092f48b 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -480,6 +480,72 @@ def unlock(path=None): sys.stdout.write("[+] Keys have been unencrypted!\n") +class ProvisionELB(Command): + """ + Creates and provisions a certificate on an ELB based on a json blob + """ + + option_list = ( + Option('-d', '--dns', dest='dns', required=True), + Option('-e', '--elb', dest='elb', required=True), + Option('-o', '--owner', dest='owner'), + Option('-a', '--authority', dest='authority', required=True), + Option('-s','--description', dest='description'), + Option('-t','--destinations', dest='destinations'), + Option('-n','--notifications', dest='notifications') + ) + + def run(self, dns, elb, owner, authority, description, destinations, notifications): + from lemur.certificates import service + from lemur.certificates.views import valid_authority + from flask import g + import lemur.users.service + + # convert argument lists to arrays, or empty sets + destinations = [] if not destinations else destinations.split(',') + notifications = [] if not notifications else notifications.split(',') + + # set a default description + description = u'Command line provisioned keypair' if not description else unicode(description) + + #grab the user + g.user = lemur.users.service.get_by_username(owner) + if not g.user: + g.user = lemur.users.service.get_all()[0] ## get the first user + + dns = dns.split(',') + + # get the primary CN + cn = dns[0] + + # IF there are more, add them as alternate name + extensions = {} + if len(dns) > 1: + sub_alt_names = [] + + for alt_name in dns[1:]: + sub_alt_names.append({'nameType': 'DNSName', 'value': unicode(alt_name)}) + + extensions['subAltNames'] = {'names': sub_alt_names} + + sys.stdout.write("subNames: {}\n".format(extensions)) + sys.stdout.write("cn: {} is a {}\n".format(cn, cn.__class__)) + + cert = service.create(authority=valid_authority({ "name": authority}), commonName=unicode(cn), extensions=extensions, + organization=u'Netflix', + organizationalUnit=u'Operations', + country=u'US', + state=u'California', + location=u'Los Gatos', + owner=unicode(owner), + description=description, + destinations=destinations, + notifications=notifications + ) + sys.stdout.write("cert {}".format(cert)) + + + def main(): manager.add_command("start", LemurServer()) manager.add_command("runserver", Server(host='127.0.0.1')) @@ -489,6 +555,7 @@ 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() From fcfaa21a2490b0666adb406b7ad298539219bd44 Mon Sep 17 00:00:00 2001 From: Jeremy Heffner Date: Thu, 20 Aug 2015 15:45:42 -0700 Subject: [PATCH 02/11] Refactoring --- lemur/manage.py | 77 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/lemur/manage.py b/lemur/manage.py index 9092f48b..f30fd4af 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -490,17 +490,24 @@ class ProvisionELB(Command): Option('-e', '--elb', dest='elb', required=True), Option('-o', '--owner', dest='owner'), Option('-a', '--authority', dest='authority', required=True), - Option('-s','--description', dest='description'), - Option('-t','--destinations', dest='destinations'), - Option('-n','--notifications', dest='notifications') + Option('-s', '--description', dest='description'), + Option('-t', '--destinations', dest='destinations'), + Option('-n', '--notifications', dest='notifications') ) - def run(self, dns, elb, owner, authority, description, destinations, notifications): - from lemur.certificates import service - from lemur.certificates.views import valid_authority + 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 unicode(g.user.username) + + def _build_cert_options(self, destinations, notifications, description, owner, dns, authority): # convert argument lists to arrays, or empty sets destinations = [] if not destinations else destinations.split(',') notifications = [] if not notifications else notifications.split(',') @@ -508,15 +515,12 @@ class ProvisionELB(Command): # set a default description description = u'Command line provisioned keypair' if not description else unicode(description) - #grab the user - g.user = lemur.users.service.get_by_username(owner) - if not g.user: - g.user = lemur.users.service.get_all()[0] ## get the first user + owner = unicode(owner) dns = dns.split(',') # get the primary CN - cn = dns[0] + cn = unicode(dns[0]) # IF there are more, add them as alternate name extensions = {} @@ -531,19 +535,45 @@ class ProvisionELB(Command): sys.stdout.write("subNames: {}\n".format(extensions)) sys.stdout.write("cn: {} is a {}\n".format(cn, cn.__class__)) - cert = service.create(authority=valid_authority({ "name": authority}), commonName=unicode(cn), extensions=extensions, - organization=u'Netflix', - organizationalUnit=u'Operations', - country=u'US', - state=u'California', - location=u'Los Gatos', - owner=unicode(owner), - description=description, - destinations=destinations, - notifications=notifications - ) - sys.stdout.write("cert {}".format(cert)) + from lemur.certificates.views import valid_authority + authority=valid_authority({"name": authority}) + + options = { + 'destinations': destinations, + 'description': description, + 'notifications': notifications, + 'commonName': cn, + 'extensions': extensions, + 'authority': authority, + 'owner': owner, + # defaults: + 'organization': u'Netflix', + 'organizationalUnit': u'Operations', + 'country': u'US', + 'state': u'California', + 'location': u'Los Gatos' + } + + return options + + + def run(self, dns, elb, owner, authority, description, notifications): + from lemur.certificates import service + + # 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) + + sys.stdout.write("cert options: {}\n".format(cert_options)) + + # create the certificate + cert = service.create( **cert_options ) + sys.stdout.write("cert {}".format(cert)) def main(): @@ -558,6 +588,5 @@ def main(): manager.add_command("provision_elb", ProvisionELB()) manager.run() - if __name__ == "__main__": main() From 38ebeab163d7cc3c09748376534a6e6d4f820433 Mon Sep 17 00:00:00 2001 From: Jeremy Heffner Date: Thu, 20 Aug 2015 15:45:53 -0700 Subject: [PATCH 03/11] Refactoring.. with pep8 fixes --- lemur/manage.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lemur/manage.py b/lemur/manage.py index f30fd4af..6e50e606 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -537,7 +537,7 @@ class ProvisionELB(Command): from lemur.certificates.views import valid_authority - authority=valid_authority({"name": authority}) + authority = valid_authority({"name": authority}) options = { 'destinations': destinations, @@ -557,8 +557,7 @@ class ProvisionELB(Command): return options - - def run(self, dns, elb, owner, authority, description, notifications): + def run(self, dns, elb, owner, authority, description, notifications, destinations): from lemur.certificates import service # configure the owner if we can find it, or go for default, and put it in the global @@ -572,7 +571,7 @@ class ProvisionELB(Command): sys.stdout.write("cert options: {}\n".format(cert_options)) # create the certificate - cert = service.create( **cert_options ) + cert = service.create(**cert_options) sys.stdout.write("cert {}".format(cert)) From dbfd6b1e17772f1043b1d9c73120d643ed3565cc Mon Sep 17 00:00:00 2001 From: Jeremy Heffner Date: Fri, 21 Aug 2015 13:09:29 -0700 Subject: [PATCH 04/11] Fixing this so it pulls the named option --- lemur/plugins/base/v1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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'] From 6e39a1e666ee0d96c0b9139c845dea59de256ea4 Mon Sep 17 00:00:00 2001 From: Jeremy Heffner Date: Mon, 24 Aug 2015 12:18:15 -0700 Subject: [PATCH 05/11] Finished glue code to push ELBs. --- lemur/manage.py | 84 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/lemur/manage.py b/lemur/manage.py index d50e959d..5223aa65 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -488,12 +488,15 @@ class ProvisionELB(Command): Option('-e', '--elb', dest='elb', required=True), Option('-o', '--owner', dest='owner'), Option('-a', '--authority', dest='authority', required=True), - Option('-s', '--description', dest='description'), + Option('-s', '--description', dest='description', default=u'Command line provisioned keypair'), Option('-t', '--destinations', dest='destinations'), - Option('-n', '--notifications', dest='notifications') + Option('-n', '--notifications', dest='notifications'), + Option('-r', '--region', dest='region', default=u'us-east-1'), + Option('-p', '--dport', '--port', dest='dport', default=7002), + Option('--src-port', '--source-port', '--sport', dest='sport', default=443) ) - def _configure_user(self, owner): + def configure_user(self, owner): from flask import g import lemur.users.service @@ -505,24 +508,19 @@ class ProvisionELB(Command): return unicode(g.user.username) - def _build_cert_options(self, destinations, notifications, description, owner, dns, authority): + def build_cert_options(self, destinations, notifications, description, owner, dns, authority): # convert argument lists to arrays, or empty sets - destinations = [] if not destinations else destinations.split(',') + destinations = [] if not destinations else self._get_destinations(destinations.split(',')) notifications = [] if not notifications else notifications.split(',') - # set a default description - description = u'Command line provisioned keypair' if not description else unicode(description) - - owner = unicode(owner) - dns = dns.split(',') # get the primary CN - cn = unicode(dns[0]) + commonName = unicode(dns.pop(0)) - # IF there are more, add them as alternate name + # If there are more than one fqdn, add them as alternate names extensions = {} - if len(dns) > 1: + if dns: sub_alt_names = [] for alt_name in dns[1:]: @@ -530,18 +528,15 @@ class ProvisionELB(Command): extensions['subAltNames'] = {'names': sub_alt_names} - sys.stdout.write("subNames: {}\n".format(extensions)) - sys.stdout.write("cn: {} is a {}\n".format(cn, cn.__class__)) - from lemur.certificates.views import valid_authority authority = valid_authority({"name": authority}) options = { - 'destinations': destinations, - 'description': description, + 'destinations': map(lambda x: {'id': x.id, 'label': x.label}, destinations), + 'description': unicode(description), 'notifications': notifications, - 'commonName': cn, + 'commonName': commonName, 'extensions': extensions, 'authority': authority, 'owner': owner, @@ -555,22 +550,59 @@ class ProvisionELB(Command): return options - def run(self, dns, elb, owner, authority, description, notifications, destinations): + def get_destinations(self, destination_names): + from lemur.destinations import service + + destinations = [] + + for destination_name in destination_names: + sys.stderr.write("looking up dest: {}\n".format(destination_name)) + destinations.append(service.get_by_label(destination_name)) + + return destinations + + def _get_destination_account(self, destinations): + for destination in self._get_destinations(destinations.split(',')): + if destination.plugin_name == 'aws-destination': + + account_number = destination.plugin.get_option('accountNumber', destination.options) + sys.stdout.write('found aws account: {}\n'.format(account_number)) + 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): from lemur.certificates import service + from lemur.plugins.lemur_aws import elb # configure the owner if we can find it, or go for default, and put it in the global - owner = self._configure_user(owner) + 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) + cert_options = self.build_cert_options( + destinations=destinations, + notifications=notifications, + description=description, + owner=owner, + dns=dns, + authority=authority) - sys.stdout.write("cert options: {}\n".format(cert_options)) + aws_account = self.get_destination_account(destinations) # create the certificate + sys.stdout.write('Creating certificate for {}'.format(cert_options['commonName'])) + cert = service.create(**cert_options) - sys.stdout.write("cert {}".format(cert)) + + cert_arn = cert.get_arn(aws_account) + sys.stderr.write('cert arn: {}\n'.format(cert_arn)) + + time.sleep(2) + # adding the listener + sys.stderr.write('Configuring elb {} from port {} to port {} in region {} with cert {}\n'.format(elb, sport, dport, region, cert_arn)) + + elb.create_new_listeners(aws_account, region, elb_name, [(sport, dport, 'HTTPS', cert_arn)]) def main(): From d599aaa4106cdf6dc5cebe9a44a0c98e8259674d Mon Sep 17 00:00:00 2001 From: Jeremy Heffner Date: Mon, 24 Aug 2015 16:17:04 -0700 Subject: [PATCH 06/11] Updating to handle unicode in python 2 and 3$ added retry with backoff for the SSL cert to show up after it is added (CAP, ftw)$ --- lemur/manage.py | 81 +++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/lemur/manage.py b/lemur/manage.py index 5223aa65..5298ffc8 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 @@ -480,18 +482,18 @@ def unlock(path=None): class ProvisionELB(Command): """ - Creates and provisions a certificate on an ELB based on a json blob + Creates and provisions a certificate on an ELB based on command line arguments """ option_list = ( - Option('-d', '--dns', dest='dns', required=True), - Option('-e', '--elb', dest='elb', required=True), - Option('-o', '--owner', dest='owner'), - Option('-a', '--authority', dest='authority', required=True), - Option('-s', '--description', dest='description', default=u'Command line provisioned keypair'), - Option('-t', '--destinations', dest='destinations'), - Option('-n', '--notifications', dest='notifications'), - Option('-r', '--region', dest='region', default=u'us-east-1'), + 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', '--destinations', dest='destinations', action='append', type=unicode), + Option('-n', '--notifications', 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) ) @@ -506,42 +508,35 @@ class ProvisionELB(Command): if not g.user: g.user = lemur.users.service.get_all()[0] - return unicode(g.user.username) + return g.user.username def build_cert_options(self, destinations, notifications, description, owner, dns, authority): - # convert argument lists to arrays, or empty sets - destinations = [] if not destinations else self._get_destinations(destinations.split(',')) - notifications = [] if not notifications else notifications.split(',') + from lemur.certificates.views import valid_authority - dns = dns.split(',') + # convert argument lists to arrays, or empty sets + destinations = self.get_destinations(destinations) # get the primary CN - commonName = unicode(dns.pop(0)) + common_name = dns[0] # If there are more than one fqdn, add them as alternate names extensions = {} - if dns: - sub_alt_names = [] - - for alt_name in dns[1:]: - sub_alt_names.append({'nameType': 'DNSName', 'value': unicode(alt_name)}) - - extensions['subAltNames'] = {'names': sub_alt_names} - - from lemur.certificates.views import valid_authority + if len(dns) > 1: + extensions['subAltNames'] = {'names': map(lambda x: {'nameType': 'DNSName', 'value': x}, dns)} authority = valid_authority({"name": authority}) 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': unicode(description), + 'description': description, 'notifications': notifications, - 'commonName': commonName, + 'commonName': common_name, 'extensions': extensions, 'authority': authority, 'owner': owner, # defaults: - 'organization': u'Netflix', + 'organization': u'Netflix, Inc.', 'organizationalUnit': u'Operations', 'country': u'US', 'state': u'California', @@ -556,17 +551,15 @@ class ProvisionELB(Command): destinations = [] for destination_name in destination_names: - sys.stderr.write("looking up dest: {}\n".format(destination_name)) destinations.append(service.get_by_label(destination_name)) return destinations - def _get_destination_account(self, destinations): - for destination in self._get_destinations(destinations.split(',')): + 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) - sys.stdout.write('found aws account: {}\n'.format(account_number)) return account_number sys.stderr.write("No destination AWS account provided, failing\n") @@ -575,6 +568,7 @@ class ProvisionELB(Command): def run(self, dns, elb_name, owner, authority, description, notifications, destinations, region, dport, sport): 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) @@ -591,18 +585,31 @@ class ProvisionELB(Command): aws_account = self.get_destination_account(destinations) # create the certificate - sys.stdout.write('Creating certificate for {}'.format(cert_options['commonName'])) - + sys.stdout.write('Creating certificate for {}\n'.format(cert_options['commonName'])) cert = service.create(**cert_options) cert_arn = cert.get_arn(aws_account) sys.stderr.write('cert arn: {}\n'.format(cert_arn)) - time.sleep(2) - # adding the listener - sys.stderr.write('Configuring elb {} from port {} to port {} in region {} with cert {}\n'.format(elb, sport, dport, region, cert_arn)) + sys.stderr.write('Configuring elb {} from port {} to port {} in region {} with cert {}\n'.format(elb_name, sport, dport, region, cert_arn)) - elb.create_new_listeners(aws_account, region, elb_name, [(sport, dport, 'HTTPS', 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 retuirns 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 + else: + raise bse + else: + done = True def main(): From 6db1d0b031558927cbdb9b00303737efb899d27b Mon Sep 17 00:00:00 2001 From: Jeremy Heffner Date: Mon, 24 Aug 2015 16:37:24 -0700 Subject: [PATCH 07/11] fixing unicode support --- lemur/manage.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/lemur/manage.py b/lemur/manage.py index 5298ffc8..cea8fbb3 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -480,22 +480,31 @@ def unlock(path=None): sys.stdout.write("[+] Keys have been unencrypted!\n") +def unicode_(data): + import sys + + if sys.version_info.major < 3: + return unicode(data) + 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', '--destinations', dest='destinations', action='append', type=unicode), - Option('-n', '--notifications', dest='notifications', action='append', type=unicode, default=[]), - Option('-r', '--region', dest='region', default=u'us-east-1', type=unicode), + 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', '--destinations', dest='destinations', action='append', type=unicode_), + Option('-n', '--notifications', 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('--src-port', '--source-port', '--sport', dest='sport', default=443), + Option('--dry-run', dest='dryrun', action='store_true') ) def configure_user(self, owner): @@ -565,7 +574,7 @@ class ProvisionELB(Command): 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): + 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 @@ -584,6 +593,12 @@ class ProvisionELB(Command): aws_account = self.get_destination_account(destinations) + if dryrun: + import json + + sys.stdout('Creating certificate for using options: {}\n'.format(json.dumps(cert_options, sort_keys=True, indent=2))) + sys.exit(0) + # create the certificate sys.stdout.write('Creating certificate for {}\n'.format(cert_options['commonName'])) cert = service.create(**cert_options) From f799ff3af19d2af46961bb14733ade51ee8cbdc0 Mon Sep 17 00:00:00 2001 From: kevgliss Date: Mon, 24 Aug 2015 20:10:03 -0700 Subject: [PATCH 08/11] Seeing if using decode explicity this helps py3 problem --- lemur/manage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/manage.py b/lemur/manage.py index cea8fbb3..5d6a4c90 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -484,7 +484,7 @@ def unicode_(data): import sys if sys.version_info.major < 3: - return unicode(data) + return data.decode('UTF-8') return data From 627b36d2a53bff8189d616863352177b8001ab80 Mon Sep 17 00:00:00 2001 From: Jeremy Heffner Date: Thu, 27 Aug 2015 15:45:00 -0700 Subject: [PATCH 09/11] Adding method to get existing listeners --- lemur/plugins/lemur_aws/elb.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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 From 51800d5e4b5e316cb5d863c72650a4a6f96eac77 Mon Sep 17 00:00:00 2001 From: Jeremy Heffner Date: Thu, 27 Aug 2015 15:48:49 -0700 Subject: [PATCH 10/11] Added better error handling Added a "dry run" option --- lemur/manage.py | 64 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/lemur/manage.py b/lemur/manage.py index 5d6a4c90..cbeac82c 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -499,8 +499,8 @@ class ProvisionELB(Command): 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', '--destinations', dest='destinations', action='append', type=unicode_), - Option('-n', '--notifications', dest='notifications', action='append', type=unicode_, default=[]), + 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), @@ -520,10 +520,15 @@ class ProvisionELB(Command): 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] @@ -533,7 +538,11 @@ class ProvisionELB(Command): if len(dns) > 1: extensions['subAltNames'] = {'names': map(lambda x: {'nameType': 'DNSName', 'value': x}, dns)} - authority = valid_authority({"name": authority}) + 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 @@ -560,10 +569,25 @@ class ProvisionELB(Command): 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': @@ -574,7 +598,8 @@ class ProvisionELB(Command): 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): + 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 @@ -596,17 +621,32 @@ class ProvisionELB(Command): if dryrun: import json - sys.stdout('Creating certificate for using options: {}\n'.format(json.dumps(cert_options, sort_keys=True, indent=2))) + 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 - sys.stdout.write('Creating certificate for {}\n'.format(cert_options['commonName'])) - cert = service.create(**cert_options) + 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)) + 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 @@ -615,12 +655,16 @@ class ProvisionELB(Command): try: elb.create_new_listeners(aws_account, region, elb_name, [(sport, dport, 'HTTPS', cert_arn)]) except BotoServerError as bse: - # if the server retuirns ad error, the certificate + # 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)) + 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: From 53ce9cac4c440af33d29c2abeb5374c96c0e41c0 Mon Sep 17 00:00:00 2001 From: Jeremy Heffner Date: Thu, 27 Aug 2015 15:55:39 -0700 Subject: [PATCH 11/11] Fix a typo, add a typo --- lemur/manage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/manage.py b/lemur/manage.py index cbeac82c..ced44429 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -499,7 +499,7 @@ class ProvisionELB(Command): 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('-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),