From e7ddff19281937c97b90ced90f595e6cbbedcd78 Mon Sep 17 00:00:00 2001 From: "Jorge Rodriguez (A.K.A. Tiriel)" Date: Wed, 10 Oct 2018 06:25:08 +0200 Subject: [PATCH] Rabbitmq: Enable communication to management API over HTTPS (#18437) * Enable communication to management API over HTTPS. * Specify version added tags to new parameters * Set proper parameter type. * Corrected version_added numbers. * Extracted commons to ansible utils. * Fix PEP8 error * Fix documentation extension syntax. Fixes #22953 --- lib/ansible/module_utils/rabbitmq.py | 19 ++++++ .../modules/messaging/rabbitmq_binding.py | 52 ++++++--------- .../modules/messaging/rabbitmq_exchange.py | 66 +++++++------------ .../modules/messaging/rabbitmq_queue.py | 58 +++++++--------- .../utils/module_docs_fragments/rabbitmq.py | 57 ++++++++++++++++ 5 files changed, 142 insertions(+), 110 deletions(-) create mode 100644 lib/ansible/module_utils/rabbitmq.py create mode 100644 lib/ansible/utils/module_docs_fragments/rabbitmq.py diff --git a/lib/ansible/module_utils/rabbitmq.py b/lib/ansible/module_utils/rabbitmq.py new file mode 100644 index 0000000000..9d4f6a5869 --- /dev/null +++ b/lib/ansible/module_utils/rabbitmq.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2016, Jorge Rodriguez +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +def rabbitmq_argument_spec(): + return dict( + login_user=dict(default='guest', type='str'), + login_password=dict(default='guest', type='str', no_log=True), + login_host=dict(default='localhost', type='str'), + login_port=dict(default='15672', type='str'), + login_protocol=dict(default='http', choices=['http', 'https'], type='str'), + cacert=dict(required=False, type='path', default=None), + cert=dict(required=False, type='path', default=None), + key=dict(required=False, type='path', default=None), + vhost=dict(default='/', type='str'), + ) diff --git a/lib/ansible/modules/messaging/rabbitmq_binding.py b/lib/ansible/modules/messaging/rabbitmq_binding.py index 5db4ede0f2..3cbf3515b4 100644 --- a/lib/ansible/modules/messaging/rabbitmq_binding.py +++ b/lib/ansible/modules/messaging/rabbitmq_binding.py @@ -36,26 +36,6 @@ options: - source exchange to create binding on. required: true aliases: [ "src", "source" ] - login_user: - description: - - rabbitMQ user for the connection. - default: guest - login_password: - description: - - rabbitMQ password for the connection. - default: false - login_host: - description: - - rabbitMQ host for the connection. - default: localhost - login_port: - description: - - rabbitMQ management API port. - default: 15672 - vhost: - description: - - rabbitMQ virtual host. - default: "/" destination: description: - destination exchange or queue for the binding. @@ -73,8 +53,11 @@ options: default: "#" arguments: description: - - extra arguments for exchange. If defined this argument is a key/value dictionary. + - extra arguments for exchange. If defined this argument is a key/value dictionary + required: false default: {} +extends_documentation_fragment: + - rabbitmq ''' EXAMPLES = ''' @@ -104,6 +87,7 @@ except ImportError: from ansible.module_utils.six.moves.urllib import parse as urllib_parse from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.rabbitmq import rabbitmq_argument_spec class RabbitMqBinding(object): @@ -117,13 +101,18 @@ class RabbitMqBinding(object): self.login_password = self.module.params['login_password'] self.login_host = self.module.params['login_host'] self.login_port = self.module.params['login_port'] + self.login_protocol = self.module.params['login_protocol'] self.vhost = self.module.params['vhost'] self.destination = self.module.params['destination'] self.destination_type = 'q' if self.module.params['destination_type'] == 'queue' else 'e' self.routing_key = self.module.params['routing_key'] self.arguments = self.module.params['arguments'] - self.base_url = 'http://{0}:{1}/api/bindings'.format(self.login_host, - self.login_port) + self.verify = self.module.params['cacert'] + self.cert = self.module.params['cert'] + self.key = self.module.params['key'] + self.base_url = '{0}://{1}:{2}/api/bindings'.format(self.login_protocol, + self.login_host, + self.login_port) self.url = '{0}/{1}/e/{2}/{3}/{4}/{5}'.format(self.base_url, urllib_parse.quote(self.vhost, safe=''), urllib_parse.quote(self.name, safe=''), @@ -253,6 +242,8 @@ class RabbitMqBinding(object): urllib_parse.quote(self.destination, safe='')) self.api_result = self.request.post(self.url, auth=self.authentication, + verify=self.cacert, + cert=(self.cert, self.key), headers={"content-type": "application/json"}, data=json.dumps({ 'routing_key': self.routing_key, @@ -277,23 +268,20 @@ class RabbitMqBinding(object): def main(): - module = AnsibleModule( - argument_spec=dict( + + argument_spec = rabbitmq_argument_spec() + argument_spec.update( + dict( state=dict(default='present', choices=['present', 'absent'], type='str'), name=dict(required=True, aliases=["src", "source"], type='str'), - login_user=dict(default='guest', type='str'), - login_password=dict(default='guest', type='str', no_log=True), - login_host=dict(default='localhost', type='str'), - login_port=dict(default='15672', type='str'), - vhost=dict(default='/', type='str'), destination=dict(required=True, aliases=["dst", "dest"], type='str'), destination_type=dict(required=True, aliases=["type", "dest_type"], choices=["queue", "exchange"], type='str'), routing_key=dict(default='#', type='str'), arguments=dict(default=dict(), type='dict') - ), - supports_check_mode=True + ) ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) if not HAS_REQUESTS: module.fail_json(msg="requests library is required for this module. To install, use `pip install requests`") diff --git a/lib/ansible/modules/messaging/rabbitmq_exchange.py b/lib/ansible/modules/messaging/rabbitmq_exchange.py index c65c846edd..164bb78204 100644 --- a/lib/ansible/modules/messaging/rabbitmq_exchange.py +++ b/lib/ansible/modules/messaging/rabbitmq_exchange.py @@ -35,31 +35,6 @@ options: choices: [ "present", "absent" ] required: false default: present - login_user: - description: - - rabbitMQ user for connection - required: false - default: guest - login_password: - description: - - rabbitMQ password for connection - required: false - default: false - login_host: - description: - - rabbitMQ host for connection - required: false - default: localhost - login_port: - description: - - rabbitMQ management api port - required: false - default: 15672 - vhost: - description: - - rabbitMQ virtual host - required: false - default: "/" durable: description: - whether exchange is durable or not @@ -90,6 +65,8 @@ options: - extra arguments for exchange. If defined this argument is a key/value dictionary required: false default: {} +extends_documentation_fragment: + - rabbitmq ''' EXAMPLES = ''' @@ -114,30 +91,27 @@ except ImportError: from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.six.moves.urllib import parse as urllib_parse +from ansible.module_utils.rabbitmq import rabbitmq_argument_spec def main(): - module = AnsibleModule( - argument_spec=dict( + + argument_spec = rabbitmq_argument_spec() + argument_spec.update( + dict( state=dict(default='present', choices=['present', 'absent'], type='str'), name=dict(required=True, type='str'), - login_user=dict(default='guest', type='str'), - login_password=dict(default='guest', type='str', no_log=True), - login_host=dict(default='localhost', type='str'), - login_port=dict(default='15672', type='str'), - vhost=dict(default='/', type='str'), durable=dict(default=True, type='bool'), auto_delete=dict(default=False, type='bool'), internal=dict(default=False, type='bool'), exchange_type=dict(default='direct', aliases=['type'], type='str'), arguments=dict(default=dict(), type='dict') - ), - supports_check_mode=True + ) ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - result = dict(changed=False, name=module.params['name']) - - url = "http://%s:%s/api/exchanges/%s/%s" % ( + url = "%s://%s:%s/api/exchanges/%s/%s" % ( + module.params['login_protocol'], module.params['login_host'], module.params['login_port'], urllib_parse.quote(module.params['vhost'], ''), @@ -147,8 +121,11 @@ def main(): if not HAS_REQUESTS: module.fail_json(msg="requests library is required for this module. To install, use `pip install requests`") + result = dict(changed=False, name=module.params['name']) + # Check if exchange already exists - r = requests.get(url, auth=(module.params['login_user'], module.params['login_password'])) + r = requests.get(url, auth=(module.params['login_user'], module.params['login_password']), + verify=module.params['cacert'], cert=(module.params['cert'], module.params['key'])) if r.status_code == 200: exchange_exists = True @@ -199,10 +176,13 @@ def main(): "internal": module.params['internal'], "type": module.params['exchange_type'], "arguments": module.params['arguments'] - }) + }), + verify=module.params['cacert'], + cert=(module.params['cert'], module.params['key']) ) elif module.params['state'] == 'absent': - r = requests.delete(url, auth=(module.params['login_user'], module.params['login_password'])) + r = requests.delete(url, auth=(module.params['login_user'], module.params['login_password']), + verify=module.params['cacert'], cert=(module.params['cert'], module.params['key'])) # RabbitMQ 3.6.7 changed this response code from 204 to 201 if r.status_code == 204 or r.status_code == 201: @@ -216,8 +196,10 @@ def main(): ) else: - result['changed'] = False - module.exit_json(**result) + module.exit_json( + changed=False, + name=module.params['name'] + ) if __name__ == '__main__': diff --git a/lib/ansible/modules/messaging/rabbitmq_queue.py b/lib/ansible/modules/messaging/rabbitmq_queue.py index 0261e05f43..08b778437a 100644 --- a/lib/ansible/modules/messaging/rabbitmq_queue.py +++ b/lib/ansible/modules/messaging/rabbitmq_queue.py @@ -34,27 +34,6 @@ options: - Only present implemented atm choices: [ "present", "absent" ] default: present - login_user: - description: - - rabbitMQ user for connection - default: guest - login_password: - description: - - rabbitMQ password for connection - type: bool - default: 'no' - login_host: - description: - - rabbitMQ host for connection - default: localhost - login_port: - description: - - rabbitMQ management api port - default: 15672 - vhost: - description: - - rabbitMQ virtual host - default: "/" durable: description: - whether queue is durable or not @@ -95,6 +74,8 @@ options: description: - extra arguments for queue. If defined this argument is a key/value dictionary default: {} +extends_documentation_fragment: + - rabbitmq ''' EXAMPLES = ''' @@ -120,18 +101,16 @@ except ImportError: from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.six.moves.urllib import parse as urllib_parse +from ansible.module_utils.rabbitmq import rabbitmq_argument_spec def main(): - module = AnsibleModule( - argument_spec=dict( + + argument_spec = rabbitmq_argument_spec() + argument_spec.update( + dict( state=dict(default='present', choices=['present', 'absent'], type='str'), name=dict(required=True, type='str'), - login_user=dict(default='guest', type='str'), - login_password=dict(default='guest', type='str', no_log=True), - login_host=dict(default='localhost', type='str'), - login_port=dict(default='15672', type='str'), - vhost=dict(default='/', type='str'), durable=dict(default=True, type='bool'), auto_delete=dict(default=False, type='bool'), message_ttl=dict(default=None, type='int'), @@ -141,11 +120,12 @@ def main(): dead_letter_routing_key=dict(default=None, type='str'), arguments=dict(default=dict(), type='dict'), max_priority=dict(default=None, type='int') - ), - supports_check_mode=True + ) ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - url = "http://%s:%s/api/queues/%s/%s" % ( + url = "%s://%s:%s/api/queues/%s/%s" % ( + module.params['login_protocol'], module.params['login_host'], module.params['login_port'], urllib_parse.quote(module.params['vhost'], ''), @@ -158,7 +138,8 @@ def main(): result = dict(changed=False, name=module.params['name']) # Check if queue already exists - r = requests.get(url, auth=(module.params['login_user'], module.params['login_password'])) + r = requests.get(url, auth=(module.params['login_user'], module.params['login_password']), + verify=module.params['cacert'], cert=(module.params['cert'], module.params['key'])) if r.status_code == 200: queue_exists = True @@ -244,10 +225,13 @@ def main(): "durable": module.params['durable'], "auto_delete": module.params['auto_delete'], "arguments": module.params['arguments'] - }) + }), + verify=module.params['cacert'], + cert=(module.params['cert'], module.params['key']) ) elif module.params['state'] == 'absent': - r = requests.delete(url, auth=(module.params['login_user'], module.params['login_password'])) + r = requests.delete(url, auth=(module.params['login_user'], module.params['login_password']), + verify=module.params['cacert'], cert=(module.params['cert'], module.params['key'])) # RabbitMQ 3.6.7 changed this response code from 204 to 201 if r.status_code == 204 or r.status_code == 201: @@ -261,8 +245,10 @@ def main(): ) else: - result['changed'] = False - module.exit_json(**result) + module.exit_json( + changed=False, + name=module.params['name'] + ) if __name__ == '__main__': diff --git a/lib/ansible/utils/module_docs_fragments/rabbitmq.py b/lib/ansible/utils/module_docs_fragments/rabbitmq.py new file mode 100644 index 0000000000..87c52346d6 --- /dev/null +++ b/lib/ansible/utils/module_docs_fragments/rabbitmq.py @@ -0,0 +1,57 @@ +# Copyright: (c) 2016, Jorge Rodriguez + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +class ModuleDocFragment(object): + # Parameters for RabbitMQ modules + DOCUMENTATION = ''' +options: + login_user: + description: + - rabbitMQ user for connection. + required: false + default: guest + login_password: + description: + - rabbitMQ password for connection. + required: false + default: false + login_host: + description: + - rabbitMQ host for connection. + required: false + default: localhost + login_port: + description: + - rabbitMQ management API port. + required: false + default: 15672 + login_protocol: + description: + - rabbitMQ management API protocol. + choices: [ http , https ] + required: false + default: http + version_added: "2.3" + cacert: + description: + - CA certificate to verify SSL connection to management API. + required: false + version_added: "2.3" + cert: + description: + - Client certificate to send on SSL connections to management API. + required: false + version_added: "2.3" + key: + description: + - Private key matching the client certificate. + required: false + version_added: "2.3" + vhost: + description: + - rabbitMQ virtual host. + required: false + default: "/" +'''