From 42538dc0190ffea5e74bd5adbec739bb1b7953f1 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Wed, 2 Oct 2024 21:09:02 +0200 Subject: [PATCH] mosquitto: Add role to install and configure mosquitto --- roles/mosquitto/README.md | 4 ++ roles/mosquitto/defaults/main.yml | 46 +++++++++++++++++++ .../filter_plugins/mosquitto_passwd.py | 34 ++++++++++++++ roles/mosquitto/handlers/main.yml | 6 +++ roles/mosquitto/meta/main.yml | 11 +++++ roles/mosquitto/tasks/main.yml | 35 ++++++++++++++ roles/mosquitto/templates/acl.j2 | 28 +++++++++++ roles/mosquitto/templates/mosquitto.conf.j2 | 36 +++++++++++++++ roles/mosquitto/templates/users.j2 | 5 ++ 9 files changed, 205 insertions(+) create mode 100644 roles/mosquitto/README.md create mode 100644 roles/mosquitto/defaults/main.yml create mode 100644 roles/mosquitto/filter_plugins/mosquitto_passwd.py create mode 100644 roles/mosquitto/handlers/main.yml create mode 100644 roles/mosquitto/meta/main.yml create mode 100644 roles/mosquitto/tasks/main.yml create mode 100644 roles/mosquitto/templates/acl.j2 create mode 100644 roles/mosquitto/templates/mosquitto.conf.j2 create mode 100644 roles/mosquitto/templates/users.j2 diff --git a/roles/mosquitto/README.md b/roles/mosquitto/README.md new file mode 100644 index 0000000..ce8e5a4 --- /dev/null +++ b/roles/mosquitto/README.md @@ -0,0 +1,4 @@ +Ansible Role: Mosquitto +========= + +Install and configure [Mosquitto](https://mosquitto.org/) MQTT message broker. diff --git a/roles/mosquitto/defaults/main.yml b/roles/mosquitto/defaults/main.yml new file mode 100644 index 0000000..c833435 --- /dev/null +++ b/roles/mosquitto/defaults/main.yml @@ -0,0 +1,46 @@ +--- + +mosquitto_packages: + - mosquitto + - mosquitto-clients + +mosquitto_listeners: + # Listeners for Mosquitto MQTT Broker + - name: "default" + listener: "1883 localhost" + protocol: "mqtt" + use_username_as_clientid: "true" + allow_zero_length_clientid: "true" + allow_anonymous: "false" + users: [] + # Users for Mosquitto MQTT Broker + # Type: Arrays of Objects with following parameters defined: + # - username: + # password: + # acl: of Objects as follows: + # - permissions: Acceptable Value: either `read`, `readwrite`, `write`, `deny` + # - topic: Acceptable Value: your/mqtt/topic (wildcards `+`, and `*` allowed) + auth_anonymous: [] + # Topics which are accessable with anonymous access + # Example + # - "topic read topic_name" + auth_patterns: [] + # %c to match the client id of the client + # %u to match the username of the client + # Example + # - "pattern write $SYS/broker/connection/%c/state" + +mosquitto_bridges: [] + # Bridges for Mosquitto MQTT Broker + # Type: Arrays of Objects with following parameters defined: + # - connection: + # address: + # bridge_insecure: + # bridge_capath: + # remote_password: + # remote_username: + # remote_clientid: + # try_private: + # topics: + # - topic: # in 0 down/ to-level/02/line/ + # - topic: # out 0 up/ from-level/02/line/ diff --git a/roles/mosquitto/filter_plugins/mosquitto_passwd.py b/roles/mosquitto/filter_plugins/mosquitto_passwd.py new file mode 100644 index 0000000..3b2b8aa --- /dev/null +++ b/roles/mosquitto/filter_plugins/mosquitto_passwd.py @@ -0,0 +1,34 @@ +# mosquitto_passwd.py: Custom Jinja2 filter plugin to generate valid PBKDF2_SHA512 +# hash digests for plain-text passwords in `users` file for +# Eclipse Mosquitto Broker + + +from ansible.errors import AnsibleError + + +def mosquitto_passwd(passwd): + try: + import passlib.hash + except Exception as e: + raise AnsibleError( + 'mosquitto_passlib custom filter requires the passlib pip package installed') + + SALT_SIZE = 12 + ITERATIONS = 101 + salt = passwd[:SALT_SIZE] + salt = bytes(salt, 'utf-8') + salt += b"0" * (SALT_SIZE - len(salt)) + + digest = passlib.hash.pbkdf2_sha512.using(salt_size=SALT_SIZE, rounds=ITERATIONS, salt=salt) \ + .hash(passwd) \ + .replace("pbkdf2-sha512", "7") \ + .replace(".", "+") + + return digest + "==" + + +class FilterModule(object): + def filters(self): + return { + 'mosquitto_passwd': mosquitto_passwd, + } diff --git a/roles/mosquitto/handlers/main.yml b/roles/mosquitto/handlers/main.yml new file mode 100644 index 0000000..03a1edb --- /dev/null +++ b/roles/mosquitto/handlers/main.yml @@ -0,0 +1,6 @@ +--- + +- name: Restart Mosquitto + ansible.builtin.service: + name: mosquitto + state: restarted diff --git a/roles/mosquitto/meta/main.yml b/roles/mosquitto/meta/main.yml new file mode 100644 index 0000000..90e4265 --- /dev/null +++ b/roles/mosquitto/meta/main.yml @@ -0,0 +1,11 @@ +--- + +galaxy_info: + author: Thomas Basler + description: Install Mosquitto + license: None + platforms: + - name: Debian + min_ansible_version: "2.4" + +dependencies: [] diff --git a/roles/mosquitto/tasks/main.yml b/roles/mosquitto/tasks/main.yml new file mode 100644 index 0000000..822e560 --- /dev/null +++ b/roles/mosquitto/tasks/main.yml @@ -0,0 +1,35 @@ +--- + +- name: Mosquitto | Install Mosquitto packages + ansible.builtin.apt: + name: "{{ item }}" + state: present + with_items: "{{ mosquitto_packages }}" + notify: Restart Mosquitto + +- name: Mosquitto | Generating Configuration File + ansible.builtin.template: + src: mosquitto.conf.j2 + dest: /etc/mosquitto/conf.d/mosquitto.conf + mode: "0755" + notify: Restart Mosquitto + +- name: Mosquitto | Generating Authentication Users File + ansible.builtin.template: + src: users.j2 + dest: "/etc/mosquitto/users_{{ item.name }}" + mode: "0755" + vars: + mosquitto_users: "{{ item.users }}" + with_items: "{{ mosquitto_listeners }}" + notify: Restart Mosquitto + +- name: Mosquitto | Generating Access Control List File + ansible.builtin.template: + src: acl.j2 + dest: "/etc/mosquitto/acl_{{ item.name }}" + mode: "0755" + vars: + listener: "{{ item }}" + with_items: "{{ mosquitto_listeners }}" + notify: Restart Mosquitto diff --git a/roles/mosquitto/templates/acl.j2 b/roles/mosquitto/templates/acl.j2 new file mode 100644 index 0000000..a6a9293 --- /dev/null +++ b/roles/mosquitto/templates/acl.j2 @@ -0,0 +1,28 @@ +{{ ansible_managed | comment }} + +{% for entry in listener.auth_anonymous | default([]) %} +{% if loop.first %} +# Anonymous access +{% endif %} +{{ entry }} +{% endfor %} + +{% for user in listener.users %} +{% if loop.first %} +# User access +{% endif %} +user {{ user.username }} +{% for access_list in user.acl | default([]) %} +topic {{ access_list.permissions }} {{ access_list.topic }} +{% if loop.last %} + +{% endif %} +{% endfor %} +{% endfor %} + +{% for entry in listener.auth_patterns | default([]) %} +{% if loop.first %} +# Global patterns +{% endif %} +{{ entry }} +{% endfor %} diff --git a/roles/mosquitto/templates/mosquitto.conf.j2 b/roles/mosquitto/templates/mosquitto.conf.j2 new file mode 100644 index 0000000..c96c10a --- /dev/null +++ b/roles/mosquitto/templates/mosquitto.conf.j2 @@ -0,0 +1,36 @@ +{{ ansible_managed | comment }} + +# Logging Configuration +log_timestamp true +log_type all + +# Listener +per_listener_settings true + +{% for elem in mosquitto_listeners %} +### Listener '{{ elem.name }}' +listener {{ elem.listener }} +{% for key, value in elem | dictsort %} +{% if key not in ["listener", "name", "users", "auth_anonymous", "auth_patterns"] %} +{{ key }} {{ value }} +{% endif %} +{% endfor %} +password_file /etc/mosquitto/users_{{ elem.name }} +acl_file /etc/mosquitto/acl_{{ elem.name }} + +{% endfor %} + +{% for elem in mosquitto_bridges %} +{% if loop.first %} +# Bridges +{% endif %} +connection {{ elem.connection }} +{% for key, value in elem | dictsort %} +{% if key not in ["connection", "topics"] %} +{{ key }} {{ value }} +{% endif %} +{% endfor %} +{% for topic in elem.topics %} +topic {{ topic.topic }} +{% endfor %} +{% endfor %} diff --git a/roles/mosquitto/templates/users.j2 b/roles/mosquitto/templates/users.j2 new file mode 100644 index 0000000..d1107d0 --- /dev/null +++ b/roles/mosquitto/templates/users.j2 @@ -0,0 +1,5 @@ +{{ ansible_managed | comment }} + +{% for user in mosquitto_users %} +{{ user.username }}:{{ user.password | mosquitto_passwd }} +{% endfor %}