diff --git a/lib/ansible/modules/notification/mqtt.py b/lib/ansible/modules/notification/mqtt.py index 57fb74199f3..6961be385e0 100644 --- a/lib/ansible/modules/notification/mqtt.py +++ b/lib/ansible/modules/notification/mqtt.py @@ -87,9 +87,16 @@ options: authentication. Support for this feature is broker dependent. version_added: 2.3 aliases: [ keyfile ] - - -# informational: requirements for nodes + tls_version: + description: + - Specifies the version of the SSL/TLS protocol to be used. + - By default (if the python version supports it) the highest TLS version is + detected. If unavailable, TLS v1 is used. + type: str + choices: + - tlsv1.1 + - tlsv1.2 + version_added: 2.9 requirements: [ mosquitto ] notes: - This module requires a connection to an MQTT broker such as Mosquitto @@ -112,7 +119,10 @@ EXAMPLES = ''' # import os +import ssl import traceback +import platform +from distutils.version import LooseVersion HAS_PAHOMQTT = True PAHOMQTT_IMP_ERR = None @@ -132,6 +142,10 @@ from ansible.module_utils._text import to_native # def main(): + tls_map = { + 'tlsv1.2': ssl.PROTOCOL_TLSv1_2, + 'tlsv1.1': ssl.PROTOCOL_TLSv1_1, + } module = AnsibleModule( argument_spec=dict( @@ -147,6 +161,7 @@ def main(): ca_cert=dict(default=None, type='path', aliases=['ca_certs']), client_cert=dict(default=None, type='path', aliases=['certfile']), client_key=dict(default=None, type='path', aliases=['keyfile']), + tls_version=dict(default=None, choices=['tlsv1.1', 'tlsv1.2']) ), supports_check_mode=True ) @@ -166,6 +181,7 @@ def main(): ca_certs = module.params.get("ca_cert", None) certfile = module.params.get("client_cert", None) keyfile = module.params.get("client_key", None) + tls_version = module.params.get("tls_version", None) if client_id is None: client_id = "%s_%s" % (socket.getfqdn(), os.getpid()) @@ -179,21 +195,42 @@ def main(): tls = None if ca_certs is not None: - tls = {'ca_certs': ca_certs, 'certfile': certfile, - 'keyfile': keyfile} + if tls_version: + tls_version = tls_map.get(tls_version, ssl.PROTOCOL_SSLv23) + else: + if LooseVersion(platform.python_version()) <= "3.5.2": + # Specifying `None` on later versions of python seems sufficient to + # instruct python to autonegotiate the SSL/TLS connection. On versions + # 3.5.2 and lower though we need to specify the version. + # + # Note that this is an alias for PROTOCOL_TLS, but PROTOCOL_TLS was + # not available until 3.5.3. + tls_version = ssl.PROTOCOL_SSLv23 + + tls = { + 'ca_certs': ca_certs, + 'certfile': certfile, + 'keyfile': keyfile, + 'tls_version': tls_version, + } try: - mqtt.single(topic, payload, - qos=qos, - retain=retain, - client_id=client_id, - hostname=server, - port=port, - auth=auth, - tls=tls) + mqtt.single( + topic, + payload, + qos=qos, + retain=retain, + client_id=client_id, + hostname=server, + port=port, + auth=auth, + tls=tls + ) except Exception as e: - module.fail_json(msg="unable to publish to MQTT broker %s" % to_native(e), - exception=traceback.format_exc()) + module.fail_json( + msg="unable to publish to MQTT broker %s" % to_native(e), + exception=traceback.format_exc() + ) module.exit_json(changed=False, topic=topic) diff --git a/test/integration/targets/mqtt/aliases b/test/integration/targets/mqtt/aliases new file mode 100644 index 00000000000..6d6f14e59d8 --- /dev/null +++ b/test/integration/targets/mqtt/aliases @@ -0,0 +1,5 @@ +notification/mqtt +shippable/posix/group1 +skip/osx +skip/freebsd +skip/rhel diff --git a/test/integration/targets/mqtt/meta/main.yml b/test/integration/targets/mqtt/meta/main.yml new file mode 100644 index 00000000000..86f3d043639 --- /dev/null +++ b/test/integration/targets/mqtt/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_mosquitto diff --git a/test/integration/targets/mqtt/tasks/main.yml b/test/integration/targets/mqtt/tasks/main.yml new file mode 100644 index 00000000000..37442abfeca --- /dev/null +++ b/test/integration/targets/mqtt/tasks/main.yml @@ -0,0 +1,4 @@ +- include: ubuntu.yml + when: + - ansible_distribution == 'Ubuntu' + - ansible_distribution_release != 'trusty' diff --git a/test/integration/targets/mqtt/tasks/ubuntu.yml b/test/integration/targets/mqtt/tasks/ubuntu.yml new file mode 100644 index 00000000000..71ff3e90bca --- /dev/null +++ b/test/integration/targets/mqtt/tasks/ubuntu.yml @@ -0,0 +1,142 @@ +- name: Install pip packages + pip: + name: paho-mqtt>=1.4.0 + state: present + +- name: MQTT non-TLS endpoint + mqtt: + topic: /node/s/bar/blurb + payload: foo + qos: 1 + client_id: me001 + register: result + +- assert: + that: + - result is success + +- name: Send a test message to TLS1.1 endpoint, no client version specified + mqtt: + topic: /node/s/bar/blurb + payload: foo-tls + qos: 1 + client_id: me001 + ca_certs: /tls/ca_certificate.pem + certfile: /tls/client_certificate.pem + keyfile: /tls/client_key.pem + port: 8883 + register: result + +- assert: + that: + - result is success + +- name: Send a test message to TLS1.2 endpoint, no client version specified + mqtt: + topic: /node/s/bar/blurb + payload: foo-tls + qos: 1 + client_id: me001 + ca_certs: /tls/ca_certificate.pem + certfile: /tls/client_certificate.pem + keyfile: /tls/client_key.pem + port: 8884 + register: result + +- assert: + that: + - result is success + +# TODO(Uncomment when TLS1.3 is supported in moquitto and ubuntu version) +# +# - name: Send a test message to TLS1.3 endpoint +# mqtt: +# topic: /node/s/bar/blurb +# payload: foo-tls +# qos: 1 +# client_id: me001 +# ca_certs: /tls/ca_certificate.pem +# certfile: /tls/client_certificate.pem +# keyfile: /tls/client_key.pem +# port: 8885 +# register: result + +#- assert: +# that: +# - result is success + +- name: Send a message, client TLS1.1, server (required) TLS1.2 - Expected failure + mqtt: + topic: /node/s/bar/blurb + payload: foo-tls + qos: 1 + client_id: me001 + ca_certs: /tls/ca_certificate.pem + certfile: /tls/client_certificate.pem + keyfile: /tls/client_key.pem + tls_version: tlsv1.1 + port: 8884 + register: result + failed_when: result is success + +- assert: + that: + - result is success + +# TODO(Uncomment when TLS1.3 is supported in moquitto and ubuntu version) +# +# - name: Send a message, client TLS1.1, server (required) TLS1.3 - Expected failure +# mqtt: +# topic: /node/s/bar/blurb +# payload: foo-tls +# qos: 1 +# client_id: me001 +# ca_certs: /tls/ca_certificate.pem +# certfile: /tls/client_certificate.pem +# keyfile: /tls/client_key.pem +# tls_version: tlsv1.1 +# port: 8885 +# register: result +# failed_when: result is success + +# - assert: +# that: +# - result is success + +- name: Send a message, client TLS1.2, server (required) TLS1.1 - Expected failure + mqtt: + topic: /node/s/bar/blurb + payload: foo-tls + qos: 1 + client_id: me001 + ca_certs: /tls/ca_certificate.pem + certfile: /tls/client_certificate.pem + keyfile: /tls/client_key.pem + tls_version: tlsv1.2 + port: 8883 + register: result + failed_when: result is success + +- assert: + that: + - result is success + +# TODO(Uncomment when TLS1.3 is supported in moquitto and ubuntu version) +# +# - name: Send a message, client TLS1.2, server (required) TLS1.3 - Expected failure +# mqtt: +# topic: /node/s/bar/blurb +# payload: foo-tls +# qos: 1 +# client_id: me001 +# ca_certs: /tls/ca_certificate.pem +# certfile: /tls/client_certificate.pem +# keyfile: /tls/client_key.pem +# tls_version: tlsv1.2 +# port: 8885 +# register: result +# failed_when: result is success + +# - assert: +# that: +# - result is success diff --git a/test/integration/targets/setup_mosquitto/files/mosquitto.conf b/test/integration/targets/setup_mosquitto/files/mosquitto.conf new file mode 100644 index 00000000000..84a80b71c3b --- /dev/null +++ b/test/integration/targets/setup_mosquitto/files/mosquitto.conf @@ -0,0 +1,35 @@ +# Plain MQTT protocol +listener 1883 + +# MQTT over TLS 1.1 +listener 8883 +tls_version tlsv1.1 +cafile /tls/ca_certificate.pem +certfile /tls/server_certificate.pem +keyfile /tls/server_key.pem + +# MQTT over TLS 1.2 +listener 8884 +tls_version tlsv1.2 +cafile /tls/ca_certificate.pem +certfile /tls/server_certificate.pem +keyfile /tls/server_key.pem + +# TODO(This does not appear to be supported on Ubuntu 18.04. Re-try on 20.04 or next LTS release) +# MQTT over TLS 1.3 +# +# listener 8885 +# tls_version tlsv1.3 +# cafile /tls/ca_certificate.pem +# certfile /tls/server_certificate.pem +# keyfile /tls/server_key.pem + +log_dest syslog + +log_type error +log_type warning +log_type notice +log_type information +log_type debug + +connection_messages true diff --git a/test/integration/targets/setup_mosquitto/meta/main.yml b/test/integration/targets/setup_mosquitto/meta/main.yml new file mode 100644 index 00000000000..af05db79d41 --- /dev/null +++ b/test/integration/targets/setup_mosquitto/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - setup_tls diff --git a/test/integration/targets/setup_mosquitto/tasks/main.yml b/test/integration/targets/setup_mosquitto/tasks/main.yml new file mode 100644 index 00000000000..4f35f16f623 --- /dev/null +++ b/test/integration/targets/setup_mosquitto/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- include: ubuntu.yml + when: ansible_distribution == 'Ubuntu' diff --git a/test/integration/targets/setup_mosquitto/tasks/ubuntu.yml b/test/integration/targets/setup_mosquitto/tasks/ubuntu.yml new file mode 100644 index 00000000000..5675cb89235 --- /dev/null +++ b/test/integration/targets/setup_mosquitto/tasks/ubuntu.yml @@ -0,0 +1,24 @@ +- name: Install https transport for apt + apt: + name: apt-transport-https + state: latest + force: yes + +- name: Install Mosquitto Server + apt: + name: mosquitto + state: latest + register: result + until: result is success + delay: 3 + retries: 10 + +- name: Ensure TLS config + copy: + src: mosquitto.conf + dest: /etc/mosquitto/mosquitto.conf + +- name: Start Mosquitto service + service: + name: mosquitto + state: restarted