git.iko.soy/typeable/nixpkgs
on master help sign in
Merge pull request #142765 from pennae/mosquitto-fixes

nixos/mosquitto: restore checkPasswords=false from old module, add module doc chapter

commit185aeb4ab8b0dffcf0092bc8d6440d91cadc8f0c authorJörg Thalheim <Mic92@users.noreply.github.com> dateSat 6 Nov 2021, 10:08 (5 years ago) parentf089c5c0343045
4 files changed+295−16
nixos/modules/services/networking/mosquitto.md+102 nixos/modules/services/networking/mosquitto.nix+18−3 nixos/modules/services/networking/mosquitto.xml+147 nixos/tests/mosquitto.nix+28−13
nixos/modules/services/networking/mosquitto.mdnew file+102
@@ -0,0 +1,102 @@
1+# Mosquitto {#module-services-mosquitto}
2+
3+Mosquitto is a MQTT broker often used for IoT or home automation data transport.
4+
5+## Quickstart {#module-services-mosquitto-quickstart}
6+
7+A minimal configuration for Mosquitto is
8+
9+```nix
10+services.mosquitto = {
11+ enable = true;
12+ listeners = [ {
13+ acl = [ "pattern readwrite #" ];
14+ omitPasswordAuth = true;
15+ settings.allow_anonymous = true;
16+ } ];
17+};
18+```
19+
20+This will start a broker on port 1883, listening on all interfaces of the machine, allowing
21+read/write access to all topics to any user without password requirements.
22+
23+User authentication can be configured with the `users` key of listeners. A config that gives
24+full read access to a user `monitor` and restricted write access to a user `service` could look
25+like
26+
27+```nix
28+services.mosquitto = {
29+ enable = true;
30+ listeners = [ {
31+ users = {
32+ monitor = {
33+ acl = [ "read #" ];
34+ password = "monitor";
35+ };
36+ service = {
37+ acl = [ "write service/#" ];
38+ password = "service";
39+ };
40+ };
41+ } ];
42+};
43+```
44+
45+TLS authentication is configured by setting TLS-related options of the listener:
46+
47+```nix
48+services.mosquitto = {
49+ enable = true;
50+ listeners = [ {
51+ port = 8883; # port change is not required, but helpful to avoid mistakes
52+ # ...
53+ settings = {
54+ cafile = "/path/to/mqtt.ca.pem";
55+ certfile = "/path/to/mqtt.pem";
56+ keyfile = "/path/to/mqtt.key";
57+ };
58+ } ];
59+```
60+
61+## Configuration {#module-services-mosquitto-config}
62+
63+The Mosquitto configuration has four distinct types of settings:
64+the global settings of the daemon, listeners, plugins, and bridges.
65+Bridges and listeners are part of the global configuration, plugins are part of listeners.
66+Users of the broker are configured as parts of listeners rather than globally, allowing
67+configurations in which a given user is only allowed to log in to the broker using specific
68+listeners (eg to configure an admin user with full access to all topics, but restricted to
69+localhost).
70+
71+Almost all options of Mosquitto are available for configuration at their appropriate levels, some
72+as NixOS options written in camel case, the remainders under `settings` with their exact names in
73+the Mosquitto config file. The exceptions are `acl_file` (which is always set according to the
74+`acl` attributes of a listener and its users) and `per_listener_settings` (which is always set to
75+`true`).
76+
77+### Password authentication {#module-services-mosquitto-config-passwords}
78+
79+Mosquitto can be run in two modes, with a password file or without. Each listener has its own
80+password file, and different listeners may use different password files. Password file generation
81+can be disabled by setting `omitPasswordAuth = true` for a listener; in this case it is necessary
82+to either set `settings.allow_anonymous = true` to allow all logins, or to configure other
83+authentication methods like TLS client certificates with `settings.use_identity_as_username = true`.
84+
85+The default is to generate a password file for each listener from the users configured to that
86+listener. Users with no configured password will not be added to the password file and thus
87+will not be able to use the broker.
88+
89+### ACL format {#module-services-mosquitto-config-acl}
90+
91+Every listener has a Mosquitto `acl_file` attached to it. This ACL is configured via two
92+attributes of the config:
93+
94+ * the `acl` attribute of the listener configures pattern ACL entries and topic ACL entries
95+ for anonymous users. Each entry must be prefixed with `pattern` or `topic` to distinguish
96+ between these two cases.
97+ * the `acl` attribute of every user configures in the listener configured the ACL for that
98+ given user. Only topic ACLs are supported by Mosquitto in this setting, so no prefix is
99+ required or allowed.
100+
101+The default ACL for a listener is empty, disallowing all accesses from all clients. To configure
102+a completely open ACL, set `acl = [ "pattern readwrite #" ]` in the listener.
nixos/modules/services/networking/mosquitto.nix+18−3
@@ -257,18 +257,28 @@ let
257257
258258 users = mkOption {
259259 type = attrsOf userOptions;
260− example = { john = { password = "123456"; acl = [ "topic readwrite john/#" ]; }; };
260+ example = { john = { password = "123456"; acl = [ "readwrite john/#" ]; }; };
261261 description = ''
262262 A set of users and their passwords and ACLs.
263263 '';
264264 default = {};
265265 };
266266
267+ omitPasswordAuth = mkOption {
268+ type = bool;
269+ description = ''
270+ Omits password checking, allowing anyone to log in with any user name unless
271+ other mandatory authentication methods (eg TLS client certificates) are configured.
272+ '';
273+ default = false;
274+ };
275+
267276 acl = mkOption {
268277 type = listOf str;
269278 description = ''
270279 Additional ACL items to prepend to the generated ACL file.
271280 '';
281+ example = [ "pattern read #" "topic readwrite anon/report/#" ];
272282 default = [];
273283 };
274284
@@ -294,9 +304,9 @@ let
294304 formatListener = idx: listener:
295305 [
296306 "listener ${toString listener.port} ${toString listener.address}"
297− "password_file ${cfg.dataDir}/passwd-${toString idx}"
298307 "acl_file ${makeACLFile idx listener.users listener.acl}"
299308 ]
309+ ++ optional (! listener.omitPasswordAuth) "password_file ${cfg.dataDir}/passwd-${toString idx}"
300310 ++ formatFreeform {} listener.settings
301311 ++ concatMap formatAuthPlugin listener.authPlugins;
302312
@@ -645,5 +655,10 @@ in
645655
646656 };
647657
648− meta.maintainers = with lib.maintainers; [ pennae ];
658+ meta = {
659+ maintainers = with lib.maintainers; [ pennae ];
660+ # Don't edit the docbook xml directly, edit the md and generate it:
661+ # `pandoc mosquitto.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > mosquitto.xml`
662+ doc = ./mosquitto.xml;
663+ };
649664}
nixos/modules/services/networking/mosquitto.xmlnew file+147
@@ -0,0 +1,147 @@
1+<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-mosquitto">
2+ <title>Mosquitto</title>
3+ <para>
4+ Mosquitto is a MQTT broker often used for IoT or home automation
5+ data transport.
6+ </para>
7+ <section xml:id="module-services-mosquitto-quickstart">
8+ <title>Quickstart</title>
9+ <para>
10+ A minimal configuration for Mosquitto is
11+ </para>
12+ <programlisting language="bash">
13+services.mosquitto = {
14+ enable = true;
15+ listeners = [ {
16+ acl = [ &quot;pattern readwrite #&quot; ];
17+ omitPasswordAuth = true;
18+ settings.allow_anonymous = true;
19+ } ];
20+};
21+</programlisting>
22+ <para>
23+ This will start a broker on port 1883, listening on all interfaces
24+ of the machine, allowing read/write access to all topics to any
25+ user without password requirements.
26+ </para>
27+ <para>
28+ User authentication can be configured with the
29+ <literal>users</literal> key of listeners. A config that gives
30+ full read access to a user <literal>monitor</literal> and
31+ restricted write access to a user <literal>service</literal> could
32+ look like
33+ </para>
34+ <programlisting language="bash">
35+services.mosquitto = {
36+ enable = true;
37+ listeners = [ {
38+ users = {
39+ monitor = {
40+ acl = [ &quot;read #&quot; ];
41+ password = &quot;monitor&quot;;
42+ };
43+ service = {
44+ acl = [ &quot;write service/#&quot; ];
45+ password = &quot;service&quot;;
46+ };
47+ };
48+ } ];
49+};
50+</programlisting>
51+ <para>
52+ TLS authentication is configured by setting TLS-related options of
53+ the listener:
54+ </para>
55+ <programlisting language="bash">
56+services.mosquitto = {
57+ enable = true;
58+ listeners = [ {
59+ port = 8883; # port change is not required, but helpful to avoid mistakes
60+ # ...
61+ settings = {
62+ cafile = &quot;/path/to/mqtt.ca.pem&quot;;
63+ certfile = &quot;/path/to/mqtt.pem&quot;;
64+ keyfile = &quot;/path/to/mqtt.key&quot;;
65+ };
66+ } ];
67+</programlisting>
68+ </section>
69+ <section xml:id="module-services-mosquitto-config">
70+ <title>Configuration</title>
71+ <para>
72+ The Mosquitto configuration has four distinct types of settings:
73+ the global settings of the daemon, listeners, plugins, and
74+ bridges. Bridges and listeners are part of the global
75+ configuration, plugins are part of listeners. Users of the broker
76+ are configured as parts of listeners rather than globally,
77+ allowing configurations in which a given user is only allowed to
78+ log in to the broker using specific listeners (eg to configure an
79+ admin user with full access to all topics, but restricted to
80+ localhost).
81+ </para>
82+ <para>
83+ Almost all options of Mosquitto are available for configuration at
84+ their appropriate levels, some as NixOS options written in camel
85+ case, the remainders under <literal>settings</literal> with their
86+ exact names in the Mosquitto config file. The exceptions are
87+ <literal>acl_file</literal> (which is always set according to the
88+ <literal>acl</literal> attributes of a listener and its users) and
89+ <literal>per_listener_settings</literal> (which is always set to
90+ <literal>true</literal>).
91+ </para>
92+ <section xml:id="module-services-mosquitto-config-passwords">
93+ <title>Password authentication</title>
94+ <para>
95+ Mosquitto can be run in two modes, with a password file or
96+ without. Each listener has its own password file, and different
97+ listeners may use different password files. Password file
98+ generation can be disabled by setting
99+ <literal>omitPasswordAuth = true</literal> for a listener; in
100+ this case it is necessary to either set
101+ <literal>settings.allow_anonymous = true</literal> to allow all
102+ logins, or to configure other authentication methods like TLS
103+ client certificates with
104+ <literal>settings.use_identity_as_username = true</literal>.
105+ </para>
106+ <para>
107+ The default is to generate a password file for each listener
108+ from the users configured to that listener. Users with no
109+ configured password will not be added to the password file and
110+ thus will not be able to use the broker.
111+ </para>
112+ </section>
113+ <section xml:id="module-services-mosquitto-config-acl">
114+ <title>ACL format</title>
115+ <para>
116+ Every listener has a Mosquitto <literal>acl_file</literal>
117+ attached to it. This ACL is configured via two attributes of the
118+ config:
119+ </para>
120+ <itemizedlist spacing="compact">
121+ <listitem>
122+ <para>
123+ the <literal>acl</literal> attribute of the listener
124+ configures pattern ACL entries and topic ACL entries for
125+ anonymous users. Each entry must be prefixed with
126+ <literal>pattern</literal> or <literal>topic</literal> to
127+ distinguish between these two cases.
128+ </para>
129+ </listitem>
130+ <listitem>
131+ <para>
132+ the <literal>acl</literal> attribute of every user
133+ configures in the listener configured the ACL for that given
134+ user. Only topic ACLs are supported by Mosquitto in this
135+ setting, so no prefix is required or allowed.
136+ </para>
137+ </listitem>
138+ </itemizedlist>
139+ <para>
140+ The default ACL for a listener is empty, disallowing all
141+ accesses from all clients. To configure a completely open ACL,
142+ set <literal>acl = [ &quot;pattern readwrite #&quot; ]</literal>
143+ in the listener.
144+ </para>
145+ </section>
146+ </section>
147+</chapter>
nixos/tests/mosquitto.nix+28−13
@@ -3,6 +3,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
33let
44 port = 1888;
55 tlsPort = 1889;
6+ anonPort = 1890;
67 password = "VERY_secret";
78 hashedPassword = "$7$101$/WJc4Mp+I+uYE9sR$o7z9rD1EYXHPwEP5GqQj6A7k4W1yVbePlb8TqNcuOLV9WNCiDgwHOB0JHC1WCtdkssqTBduBNUnUGd6kmZvDSw==";
89 topic = "test/foo";
@@ -63,7 +64,7 @@ in {
6364 };
6465 in {
6566 server = { pkgs, ... }: {
66− networking.firewall.allowedTCPPorts = [ port tlsPort ];
67+ networking.firewall.allowedTCPPorts = [ port tlsPort anonPort ];
6768 services.mosquitto = {
6869 enable = true;
6970 settings = {
@@ -112,6 +113,18 @@ in {
112113 use_identity_as_username = true;
113114 };
114115 }
116+ {
117+ port = anonPort;
118+ omitPasswordAuth = true;
119+ settings.allow_anonymous = true;
120+ acl = [ "pattern read #" ];
121+ users = {
122+ anonWriter = {
123+ password = "<ignored>" + password;
124+ acl = [ "write ${topic}" ];
125+ };
126+ };
127+ }
115128 ];
116129 };
117130 };
@@ -136,9 +149,8 @@ in {
136149 def publish(args, user, topic="${topic}", port=${toString port}):
137150 return "{} {}".format(mosquitto_cmd("pub", user, topic, port), args)
138151
139−
140152 def subscribe(args, user, topic="${topic}", port=${toString port}):
141− return "{} -C 1 {}".format(mosquitto_cmd("sub", user, topic, port), args)
153+ return "{} -W 5 -C 1 {}".format(mosquitto_cmd("sub", user, topic, port), args)
142154
143155 def parallel(*fns):
144156 from threading import Thread
@@ -150,17 +162,15 @@ in {
150162 start_all()
151163 server.wait_for_unit("mosquitto.service")
152164
153− def check_passwords():
165+ with subtest("check passwords"):
154166 client1.succeed(publish("-m test", "password_store"))
155167 client1.succeed(publish("-m test", "password_file"))
156168 client1.succeed(publish("-m test", "hashed_store"))
157169 client1.succeed(publish("-m test", "hashed_file"))
158170
159− check_passwords()
160−
161− def check_acl():
171+ with subtest("check acl"):
162172 client1.succeed(subscribe("", "reader", topic="$SYS/#"))
163− client1.fail(subscribe("-W 5", "writer", topic="$SYS/#"))
173+ client1.fail(subscribe("", "writer", topic="$SYS/#"))
164174
165175 parallel(
166176 lambda: client1.succeed(subscribe("-i 3688cdd7-aa07-42a4-be22-cb9352917e40", "reader")),
@@ -170,15 +180,13 @@ in {
170180 ])
171181
172182 parallel(
173− lambda: client1.fail(subscribe("-W 5 -i 24ff16a2-ae33-4a51-9098-1b417153c712", "reader")),
183+ lambda: client1.fail(subscribe("-i 24ff16a2-ae33-4a51-9098-1b417153c712", "reader")),
174184 lambda: [
175185 server.wait_for_console_text("24ff16a2-ae33-4a51-9098-1b417153c712"),
176186 client2.succeed(publish("-m test", "reader"))
177187 ])
178188
179− check_acl()
180−
181− def check_tls():
189+ with subtest("check tls"):
182190 client1.succeed(
183191 subscribe(
184192 "--cafile ${snakeOil}/ca.crt "
@@ -188,6 +196,13 @@ in {
188196 port=${toString tlsPort},
189197 user="no_such_user"))
190198
191− check_tls()
199+ with subtest("check omitPasswordAuth"):
200+ parallel(
201+ lambda: client1.succeed(subscribe("-i fd56032c-d9cb-4813-a3b4-6be0e04c8fc3",
202+ "anonReader", port=${toString anonPort})),
203+ lambda: [
204+ server.wait_for_console_text("fd56032c-d9cb-4813-a3b4-6be0e04c8fc3"),
205+ client2.succeed(publish("-m test", "anonWriter", port=${toString anonPort}))
206+ ])
192207 '';
193208})