owntracks / quicksetup

A (mostly) automated installer for OwnTracks Recorder, Frontend with MQTT and Let's Encrypt
https://owntracks.org/booklet/guide/quicksetup/
9 stars 3 forks source link

User-specific MQTT ACLs? #25

Closed jpmens closed 4 months ago

jpmens commented 4 months ago

When Quicksetup configures a system, we create a mosquitto.conf which can, if necessary, be augmented by user-specific files dropped into conf.d/; these will be picked up during a Mosquitto restart.

Assuming a user needs specific ACLs, however, we have no provision for specifying these. Bootstrapping creates a mosquitto.acl which, if necessary, is overwritten at each run.

It's probably worth our while, long-term, to look at the dynamic security plugin which is auto-installed with the Debian Mosquitto package.

jpmens commented 4 months ago

I have changed my mind about the dynamic security plugin for Quicksetup: far too complex, not necessarily for us, but if we'd have to support the resulting configuration.

We might be able to simplify with some Ansible/Jinja templating which picks up specific user files and inserts them into the ACL where they belong.

jpmens commented 4 months ago

This looks okay, using only Jinja templating:

main acl template

----
{% for f in friends %}
# main: user={{ f.username }}
{% set fragment = "t/" + f.username + ".acl" %}
{% include fragment ignore missing %}
{% endfor %}
----

fragment t/jjolie.acl

# --begin special for jjolie
topic read owntracks/jpmens/5s
topic read $SYS/#
# --end  special for jjolie

output

----
# main: user=jane
# main: user=ck
# main: user=ck00
# main: user=bbb
# main: user=ccc
# main: user=jip
# main: user=jjolie
# --begin special for jjolie
topic read owntracks/jpmens/5s
topic read $SYS/#
# --end  special for jjolie# main: user=iris
# main: user=iris
# main: user=iris
# main: user=andie
# main: user=huawei
# main: user=simu
# main: user=simu
----
jpmens commented 4 months ago

output (no default for a user, which is good)

user jip
topic readwrite owntracks/jip/#
topic read owntracks/+/+
topic read owntracks/+/+/event
topic read owntracks/+/+/info

# --begin special for jjolie
topic read owntracks/jpmens/5s
topic read $SYS/#
# --end  special for jjolie

user iris
topic readwrite owntracks/iris/#
topic read owntracks/+/+
topic read owntracks/+/+/event
topic read owntracks/+/+/info

template

{% for f in friends %}
{{ f.username | mqtt_acl() }}
{% endfor %}

new filter function

This filter tests if a fragement file for the specified username exists; if so it is read in, otherwise a default ACL for the particular user is emitted.

from ansible.errors import AnsibleFilterError
from string import Template
import os

def mqtt_acl(username):

    acl = """
user $U
topic readwrite owntracks/$U/#
topic read owntracks/+/+
topic read owntracks/+/+/event
topic read owntracks/+/+/info
"""

    path = os.path.join("t", "%s.acl" % username)
    if os.path.exists(path):
        with open(path, "r") as f:
            acl = f.read()
            return acl

    return Template(acl).safe_substitute(U=username)

class FilterModule(object):
    def filters(self):
        return {
            'mqtt_acl': mqtt_acl,
        }