From 817f603b6be58d2f44a5c0713d03a5377181915e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 17 Feb 2015 13:33:43 -0500 Subject: [PATCH 01/11] Initial implementation of rax_clb_ssl. --- cloud/rackspace/rax_clb_ssl.py | 284 +++++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 cloud/rackspace/rax_clb_ssl.py diff --git a/cloud/rackspace/rax_clb_ssl.py b/cloud/rackspace/rax_clb_ssl.py new file mode 100644 index 00000000000..d93e2f594e7 --- /dev/null +++ b/cloud/rackspace/rax_clb_ssl.py @@ -0,0 +1,284 @@ +#!/usr/bin/python +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# This is a DOCUMENTATION stub specific to this module, it extends +# a documentation fragment located in ansible.utils.module_docs_fragments +DOCUMENTATION=''' +module: rax_clb_ssl +short_description: Manage SSL termination for a Rackspace Cloud Load Balancer. +description: +- Set up, reconfigure, or remove SSL termination for an existing load balancer. +version_added: "1.8.2" +options: + balancer_name: + description: + - Name or ID of the load balancer on which to manage SSL termination. + required: true + state: + description: + - If set to "present", SSL termination will be added to this load balancer. + - If "absent", SSL termination will be removed instead. + choices: + - present + - absent + default: present + enabled: + description: + - If set to "false", temporarily disable SSL termination without discarding + - existing credentials. + default: true + private_key: + description: + - The private SSL key as a string in PEM format. + certificate: + description: + - The public SSL certificates as a string in PEM format. + intermediate_certificate: + description: + - One or more intermediate certificate authorities as a string in PEM + - format, concatenated into a single string. + secure_port: + description: + - The port to listen for secure traffic. + default: 443 + secure_traffic_only: + description: + - If "true", the load balancer will *only* accept secure traffic. + default: false + https_redirect: + description: + - If "true", the load balancer will redirect HTTP traffic to HTTPS. + - Requires "secure_traffic_only" to be true. Incurs an implicit wait if SSL + - termination is also applied or removed. + wait: + description: + - Wait for the balancer to be in state "running" before turning. + default: false + wait_timeout: + description: + - How long before "wait" gives up, in seconds. + default: 300 +author: Ash Wilson +extends_documentation_fragment: rackspace +''' + +EXAMPLES = ''' +- name: Enable SSL termination on a load balancer + rax_clb_ssl: + balancer_name: the_loadbalancer + state: present + private_key: "{{ lookup('file', 'credentials/server.key' ) }}" + certificate: "{{ lookup('file', 'credentials/server.crt' ) }}" + intermediate_certificate: "{{ lookup('file', 'credentials/trust-chain.crt') }}" + secure_traffic_only: true + wait: true + +- name: Disable SSL termination + rax_clb_ssl: + balancer_name: "{{ registered_lb.balancer.id }}" + state: absent + wait: true +''' + +from ansible.module_utils.basic import * +from ansible.module_utils.rax import * + +try: + import pyrax + HAS_PYRAX = True +except ImportError: + HAS_PYRAX = False + +def cloud_load_balancer_ssl(module, balancer_name, state, enabled, private_key, + certificate, intermediate_certificate, secure_port, + secure_traffic_only, https_redirect, + wait, wait_timeout): + # Validate arguments. + + if not balancer_name: + module.fail_json(msg='balancer_name is required.') + + if state == 'present': + if not private_key: + module.fail_json(msg="private_key must be provided.") + else: + private_key = private_key.strip() + + if not certificate: + module.fail_json(msg="certificate must be provided.") + else: + certificate = certificate.strip() + + if state not in ('present', 'absent'): + module.fail_json(msg="State must be either 'present' or 'absent'.") + + attempts = wait_timeout / 5 + + # Locate the load balancer. + + clb = pyrax.cloud_loadbalancers + if not clb: + module.fail_json(msg='Failed to instantiate client. This ' + 'typically indicates an invalid region or an ' + 'incorrectly capitalized region name.') + + balancers = [] + for balancer in clb.list(): + if balancer_name == balancer.name or balancer_name == str(balancer.id): + balancers.append(balancer) + + if not balancers: + module.fail_json(msg='No load balancers matched your criteria. ' + 'Use rax_clb to create the balancer first.') + + if len(balancers) > 1: + module.fail_json(msg="%d load balancers were matched your criteria. Try" + "using the balancer's id instead." % len(balancers)) + + balancer = balancers[0] + existing_ssl = balancer.get_ssl_termination() + + changed = False + + if state == 'present': + # Apply or reconfigure SSL termination on the load balancer. + ssl_attrs = dict( + securePort=secure_port, + privatekey=private_key, + certificate=certificate, + intermediateCertificate=intermediate_certificate, + enabled=enabled, + secureTrafficOnly=secure_traffic_only + ) + + needs_change = False + + if existing_ssl: + for ssl_attr, value in ssl_attrs.iteritems(): + if ssl_attr == 'privatekey': + # The private key is not included in get_ssl_termination's + # output (as it shouldn't be). Also, if you're changing the + # private key, you'll also be changing the certificate, + # so we don't lose anything by not checking it. + continue + + if value is not None and existing_ssl.get(ssl_attr) != value: + # module.fail_json(msg='Unnecessary change', attr=ssl_attr, value=value, existing=existing_ssl.get(ssl_attr)) + needs_change = True + else: + needs_change = True + + if needs_change: + balancer.add_ssl_termination(**ssl_attrs) + changed = True + elif state == 'absent': + # Remove SSL termination if it's already configured. + if existing_ssl: + balancer.delete_ssl_termination() + changed = True + + if https_redirect is not None and balancer.httpsRedirect != https_redirect: + if changed: + # This wait is unavoidable because load balancers are immutable + # while the SSL termination changes above are being applied. + pyrax.utils.wait_for_build(balancer, interval=5, attempts=attempts) + + balancer.update(httpsRedirect=https_redirect) + changed = True + + if changed and wait: + pyrax.utils.wait_for_build(balancer, interval=5, attempts=attempts) + + balancer.get() + new_ssl_termination = balancer.get_ssl_termination() + + # Intentionally omit the private key from the module output, so you don't + # accidentally echo it with `ansible-playbook -v` or `debug`, and the + # certificate, which is just long. Convert other attributes to snake_case + # and include https_redirect at the top-level. + if new_ssl_termination: + new_ssl = dict( + enabled=new_ssl_termination['enabled'], + secure_port=new_ssl_termination['securePort'], + secure_traffic_only=new_ssl_termination['secureTrafficOnly'] + ) + else: + new_ssl = None + + result = dict( + changed=changed, + https_redirect=balancer.httpsRedirect, + ssl_termination=new_ssl + ) + success = True + + if balancer.status == 'ERROR': + result['msg'] = '%s failed to build' % balancer.id + success = False + elif wait and balancer.status not in ('ACTIVE', 'ERROR'): + result['msg'] = 'Timeout waiting on %s' % balancer.id + success = False + + if success: + module.exit_json(**result) + else: + module.fail_json(**result) + +def main(): + argument_spec = rax_argument_spec() + argument_spec.update(dict( + balancer_name=dict(type='str'), + state=dict(default='present', choices=['present', 'absent']), + enabled=dict(type='bool', default=True), + private_key=dict(), + certificate=dict(), + intermediate_certificate=dict(), + secure_port=dict(type='int', default=443), + secure_traffic_only=dict(type='bool', default=False), + https_redirect=dict(type='bool'), + wait=dict(type='bool', default=False), + wait_timeout=dict(type='int', default=300) + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=rax_required_together(), + ) + + if not HAS_PYRAX: + module.fail_json(msg='pyrax is required for this module.') + + balancer_name = module.params.get('balancer_name') + state = module.params.get('state') + enabled = module.boolean(module.params.get('enabled')) + private_key = module.params.get('private_key') + certificate = module.params.get('certificate') + intermediate_certificate = module.params.get('intermediate_certificate') + secure_port = module.params.get('secure_port') + secure_traffic_only = module.boolean(module.params.get('secure_traffic_only')) + https_redirect = module.boolean(module.params.get('https_redirect')) + wait = module.boolean(module.params.get('wait')) + wait_timeout = module.params.get('wait_timeout') + + setup_rax_module(module, pyrax) + + cloud_load_balancer_ssl( + module, balancer_name, state, enabled, private_key, certificate, + intermediate_certificate, secure_port, secure_traffic_only, + https_redirect, wait, wait_timeout + ) + +main() From 4c4c0bb11909b4219f8bbe3abc1fce5cfff50e20 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 19 Feb 2015 13:36:33 -0500 Subject: [PATCH 02/11] Use the correct version_added. --- cloud/rackspace/rax_clb_ssl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/rackspace/rax_clb_ssl.py b/cloud/rackspace/rax_clb_ssl.py index d93e2f594e7..7a27f93116f 100644 --- a/cloud/rackspace/rax_clb_ssl.py +++ b/cloud/rackspace/rax_clb_ssl.py @@ -21,7 +21,7 @@ module: rax_clb_ssl short_description: Manage SSL termination for a Rackspace Cloud Load Balancer. description: - Set up, reconfigure, or remove SSL termination for an existing load balancer. -version_added: "1.8.2" +version_added: "1.9" options: balancer_name: description: From 015ffbf9a90c776f8b222e61510b5b45c4fa6e9b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 19 Feb 2015 13:37:28 -0500 Subject: [PATCH 03/11] Move ansible imports to the module's bottom. --- cloud/rackspace/rax_clb_ssl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cloud/rackspace/rax_clb_ssl.py b/cloud/rackspace/rax_clb_ssl.py index 7a27f93116f..d011432e066 100644 --- a/cloud/rackspace/rax_clb_ssl.py +++ b/cloud/rackspace/rax_clb_ssl.py @@ -93,9 +93,6 @@ EXAMPLES = ''' wait: true ''' -from ansible.module_utils.basic import * -from ansible.module_utils.rax import * - try: import pyrax HAS_PYRAX = True @@ -281,4 +278,7 @@ def main(): https_redirect, wait, wait_timeout ) +from ansible.module_utils.basic import * +from ansible.module_utils.rax import * + main() From 0380490ae9561d414684602f8a0a5323b98949d8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 19 Feb 2015 13:41:03 -0500 Subject: [PATCH 04/11] Rename "balancer_name" to "loadbalancer." --- cloud/rackspace/rax_clb_ssl.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/cloud/rackspace/rax_clb_ssl.py b/cloud/rackspace/rax_clb_ssl.py index d011432e066..2195d08b938 100644 --- a/cloud/rackspace/rax_clb_ssl.py +++ b/cloud/rackspace/rax_clb_ssl.py @@ -23,7 +23,7 @@ description: - Set up, reconfigure, or remove SSL termination for an existing load balancer. version_added: "1.9" options: - balancer_name: + loadbalancer: description: - Name or ID of the load balancer on which to manage SSL termination. required: true @@ -99,15 +99,12 @@ try: except ImportError: HAS_PYRAX = False -def cloud_load_balancer_ssl(module, balancer_name, state, enabled, private_key, +def cloud_load_balancer_ssl(module, loadbalancer, state, enabled, private_key, certificate, intermediate_certificate, secure_port, secure_traffic_only, https_redirect, wait, wait_timeout): # Validate arguments. - if not balancer_name: - module.fail_json(msg='balancer_name is required.') - if state == 'present': if not private_key: module.fail_json(msg="private_key must be provided.") @@ -134,7 +131,7 @@ def cloud_load_balancer_ssl(module, balancer_name, state, enabled, private_key, balancers = [] for balancer in clb.list(): - if balancer_name == balancer.name or balancer_name == str(balancer.id): + if loadbalancer == balancer.name or loadbalancer == str(balancer.id): balancers.append(balancer) if not balancers: @@ -237,7 +234,7 @@ def cloud_load_balancer_ssl(module, balancer_name, state, enabled, private_key, def main(): argument_spec = rax_argument_spec() argument_spec.update(dict( - balancer_name=dict(type='str'), + loadbalancer=dict(required=True), state=dict(default='present', choices=['present', 'absent']), enabled=dict(type='bool', default=True), private_key=dict(), @@ -258,7 +255,7 @@ def main(): if not HAS_PYRAX: module.fail_json(msg='pyrax is required for this module.') - balancer_name = module.params.get('balancer_name') + loadbalancer = module.params.get('loadbalancer') state = module.params.get('state') enabled = module.boolean(module.params.get('enabled')) private_key = module.params.get('private_key') @@ -273,7 +270,7 @@ def main(): setup_rax_module(module, pyrax) cloud_load_balancer_ssl( - module, balancer_name, state, enabled, private_key, certificate, + module, loadbalancer, state, enabled, private_key, certificate, intermediate_certificate, secure_port, secure_traffic_only, https_redirect, wait, wait_timeout ) From 1a8ed52819f03a3a3ebde3e1f81e25f35e231fa3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 19 Feb 2015 13:41:29 -0500 Subject: [PATCH 05/11] Remove redundant "state" validity check. --- cloud/rackspace/rax_clb_ssl.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/cloud/rackspace/rax_clb_ssl.py b/cloud/rackspace/rax_clb_ssl.py index 2195d08b938..cff30d67b5e 100644 --- a/cloud/rackspace/rax_clb_ssl.py +++ b/cloud/rackspace/rax_clb_ssl.py @@ -116,9 +116,6 @@ def cloud_load_balancer_ssl(module, loadbalancer, state, enabled, private_key, else: certificate = certificate.strip() - if state not in ('present', 'absent'): - module.fail_json(msg="State must be either 'present' or 'absent'.") - attempts = wait_timeout / 5 # Locate the load balancer. From e1cdda56ff697a891541efd04acf39a9f4dcac64 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 19 Feb 2015 13:48:03 -0500 Subject: [PATCH 06/11] Use rax_find_loadbalancer utility method. --- cloud/rackspace/rax_clb_ssl.py | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/cloud/rackspace/rax_clb_ssl.py b/cloud/rackspace/rax_clb_ssl.py index cff30d67b5e..f16118c20f4 100644 --- a/cloud/rackspace/rax_clb_ssl.py +++ b/cloud/rackspace/rax_clb_ssl.py @@ -120,26 +120,7 @@ def cloud_load_balancer_ssl(module, loadbalancer, state, enabled, private_key, # Locate the load balancer. - clb = pyrax.cloud_loadbalancers - if not clb: - module.fail_json(msg='Failed to instantiate client. This ' - 'typically indicates an invalid region or an ' - 'incorrectly capitalized region name.') - - balancers = [] - for balancer in clb.list(): - if loadbalancer == balancer.name or loadbalancer == str(balancer.id): - balancers.append(balancer) - - if not balancers: - module.fail_json(msg='No load balancers matched your criteria. ' - 'Use rax_clb to create the balancer first.') - - if len(balancers) > 1: - module.fail_json(msg="%d load balancers were matched your criteria. Try" - "using the balancer's id instead." % len(balancers)) - - balancer = balancers[0] + balancer = rax_find_loadbalancer(module, pyrax, loadbalancer) existing_ssl = balancer.get_ssl_termination() changed = False From a706689a353f3c6906483f5fd105b2a93b5e8b4e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 2 Jul 2015 08:51:13 -0400 Subject: [PATCH 07/11] Bump version_added. --- cloud/rackspace/rax_clb_ssl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/rackspace/rax_clb_ssl.py b/cloud/rackspace/rax_clb_ssl.py index f16118c20f4..b467880400e 100644 --- a/cloud/rackspace/rax_clb_ssl.py +++ b/cloud/rackspace/rax_clb_ssl.py @@ -21,7 +21,7 @@ module: rax_clb_ssl short_description: Manage SSL termination for a Rackspace Cloud Load Balancer. description: - Set up, reconfigure, or remove SSL termination for an existing load balancer. -version_added: "1.9" +version_added: "2.0" options: loadbalancer: description: From d1a63d39a27f8b2e3f999b8013cb0d52093a0900 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 2 Jul 2015 08:53:19 -0400 Subject: [PATCH 08/11] Include the balancer acted upon in the result. --- cloud/rackspace/rax_clb_ssl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cloud/rackspace/rax_clb_ssl.py b/cloud/rackspace/rax_clb_ssl.py index b467880400e..bfd5f643020 100644 --- a/cloud/rackspace/rax_clb_ssl.py +++ b/cloud/rackspace/rax_clb_ssl.py @@ -193,7 +193,8 @@ def cloud_load_balancer_ssl(module, loadbalancer, state, enabled, private_key, result = dict( changed=changed, https_redirect=balancer.httpsRedirect, - ssl_termination=new_ssl + ssl_termination=new_ssl, + balancer=balancer ) success = True From 9462ad55e3c99163b673850711981ba1737273ed Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 2 Jul 2015 08:59:54 -0400 Subject: [PATCH 09/11] Guard calls that modify the CLB with try/except. --- cloud/rackspace/rax_clb_ssl.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/cloud/rackspace/rax_clb_ssl.py b/cloud/rackspace/rax_clb_ssl.py index bfd5f643020..eafa725d286 100644 --- a/cloud/rackspace/rax_clb_ssl.py +++ b/cloud/rackspace/rax_clb_ssl.py @@ -154,12 +154,18 @@ def cloud_load_balancer_ssl(module, loadbalancer, state, enabled, private_key, needs_change = True if needs_change: - balancer.add_ssl_termination(**ssl_attrs) + try: + balancer.add_ssl_termination(**ssl_attrs) + except pyrax.exceptions.PyraxException, e: + module.fail_json(msg='%s' % e.message) changed = True elif state == 'absent': # Remove SSL termination if it's already configured. if existing_ssl: - balancer.delete_ssl_termination() + try: + balancer.delete_ssl_termination() + except pyrax.exceptions.PyraxException, e: + module.fail_json(msg='%s' % e.message) changed = True if https_redirect is not None and balancer.httpsRedirect != https_redirect: @@ -168,7 +174,10 @@ def cloud_load_balancer_ssl(module, loadbalancer, state, enabled, private_key, # while the SSL termination changes above are being applied. pyrax.utils.wait_for_build(balancer, interval=5, attempts=attempts) - balancer.update(httpsRedirect=https_redirect) + try: + balancer.update(httpsRedirect=https_redirect) + except pyrax.exceptions.PyraxException, e: + module.fail_json(msg='%s' % e.message) changed = True if changed and wait: From bd4023fe8f177c457bf40fa9a3ae27af0d012c12 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 2 Jul 2015 09:09:28 -0400 Subject: [PATCH 10/11] Bring the examples up to date. --- cloud/rackspace/rax_clb_ssl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud/rackspace/rax_clb_ssl.py b/cloud/rackspace/rax_clb_ssl.py index eafa725d286..20ae9698457 100644 --- a/cloud/rackspace/rax_clb_ssl.py +++ b/cloud/rackspace/rax_clb_ssl.py @@ -78,7 +78,7 @@ extends_documentation_fragment: rackspace EXAMPLES = ''' - name: Enable SSL termination on a load balancer rax_clb_ssl: - balancer_name: the_loadbalancer + loadbalancer: the_loadbalancer state: present private_key: "{{ lookup('file', 'credentials/server.key' ) }}" certificate: "{{ lookup('file', 'credentials/server.crt' ) }}" @@ -88,7 +88,7 @@ EXAMPLES = ''' - name: Disable SSL termination rax_clb_ssl: - balancer_name: "{{ registered_lb.balancer.id }}" + loadbalancer: "{{ registered_lb.balancer.id }}" state: absent wait: true ''' From 84880c5e35a6dc8e2eeddda3a1377d617ee57368 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 2 Jul 2015 09:24:07 -0400 Subject: [PATCH 11/11] Use rax_to_dict(). --- cloud/rackspace/rax_clb_ssl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/rackspace/rax_clb_ssl.py b/cloud/rackspace/rax_clb_ssl.py index 20ae9698457..2013b8c4d81 100644 --- a/cloud/rackspace/rax_clb_ssl.py +++ b/cloud/rackspace/rax_clb_ssl.py @@ -203,7 +203,7 @@ def cloud_load_balancer_ssl(module, loadbalancer, state, enabled, private_key, changed=changed, https_redirect=balancer.httpsRedirect, ssl_termination=new_ssl, - balancer=balancer + balancer=rax_to_dict(balancer, 'clb') ) success = True