Merge pull request #142765 from pennae/mosquitto-fixes

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

authored by Jörg Thalheim and committed by GitHub 185aeb4a f089c5c7

+295 -16
+102
nixos/modules/services/networking/mosquitto.md
···
··· 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.
+18 -3
nixos/modules/services/networking/mosquitto.nix
··· 257 258 users = mkOption { 259 type = attrsOf userOptions; 260 - example = { john = { password = "123456"; acl = [ "topic readwrite john/#" ]; }; }; 261 description = '' 262 A set of users and their passwords and ACLs. 263 ''; 264 default = {}; 265 }; 266 267 acl = mkOption { 268 type = listOf str; 269 description = '' 270 Additional ACL items to prepend to the generated ACL file. 271 ''; 272 default = []; 273 }; 274 ··· 294 formatListener = idx: listener: 295 [ 296 "listener ${toString listener.port} ${toString listener.address}" 297 - "password_file ${cfg.dataDir}/passwd-${toString idx}" 298 "acl_file ${makeACLFile idx listener.users listener.acl}" 299 ] 300 ++ formatFreeform {} listener.settings 301 ++ concatMap formatAuthPlugin listener.authPlugins; 302 ··· 645 646 }; 647 648 - meta.maintainers = with lib.maintainers; [ pennae ]; 649 }
··· 257 258 users = mkOption { 259 type = attrsOf userOptions; 260 + example = { john = { password = "123456"; acl = [ "readwrite john/#" ]; }; }; 261 description = '' 262 A set of users and their passwords and ACLs. 263 ''; 264 default = {}; 265 }; 266 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 + 276 acl = mkOption { 277 type = listOf str; 278 description = '' 279 Additional ACL items to prepend to the generated ACL file. 280 ''; 281 + example = [ "pattern read #" "topic readwrite anon/report/#" ]; 282 default = []; 283 }; 284 ··· 304 formatListener = idx: listener: 305 [ 306 "listener ${toString listener.port} ${toString listener.address}" 307 "acl_file ${makeACLFile idx listener.users listener.acl}" 308 ] 309 + ++ optional (! listener.omitPasswordAuth) "password_file ${cfg.dataDir}/passwd-${toString idx}" 310 ++ formatFreeform {} listener.settings 311 ++ concatMap formatAuthPlugin listener.authPlugins; 312 ··· 655 656 }; 657 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 + }; 664 }
+147
nixos/modules/services/networking/mosquitto.xml
···
··· 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>
+28 -13
nixos/tests/mosquitto.nix
··· 3 let 4 port = 1888; 5 tlsPort = 1889; 6 password = "VERY_secret"; 7 hashedPassword = "$7$101$/WJc4Mp+I+uYE9sR$o7z9rD1EYXHPwEP5GqQj6A7k4W1yVbePlb8TqNcuOLV9WNCiDgwHOB0JHC1WCtdkssqTBduBNUnUGd6kmZvDSw=="; 8 topic = "test/foo"; ··· 63 }; 64 in { 65 server = { pkgs, ... }: { 66 - networking.firewall.allowedTCPPorts = [ port tlsPort ]; 67 services.mosquitto = { 68 enable = true; 69 settings = { ··· 112 use_identity_as_username = true; 113 }; 114 } 115 ]; 116 }; 117 }; ··· 136 def publish(args, user, topic="${topic}", port=${toString port}): 137 return "{} {}".format(mosquitto_cmd("pub", user, topic, port), args) 138 139 - 140 def subscribe(args, user, topic="${topic}", port=${toString port}): 141 - return "{} -C 1 {}".format(mosquitto_cmd("sub", user, topic, port), args) 142 143 def parallel(*fns): 144 from threading import Thread ··· 150 start_all() 151 server.wait_for_unit("mosquitto.service") 152 153 - def check_passwords(): 154 client1.succeed(publish("-m test", "password_store")) 155 client1.succeed(publish("-m test", "password_file")) 156 client1.succeed(publish("-m test", "hashed_store")) 157 client1.succeed(publish("-m test", "hashed_file")) 158 159 - check_passwords() 160 - 161 - def check_acl(): 162 client1.succeed(subscribe("", "reader", topic="$SYS/#")) 163 - client1.fail(subscribe("-W 5", "writer", topic="$SYS/#")) 164 165 parallel( 166 lambda: client1.succeed(subscribe("-i 3688cdd7-aa07-42a4-be22-cb9352917e40", "reader")), ··· 170 ]) 171 172 parallel( 173 - lambda: client1.fail(subscribe("-W 5 -i 24ff16a2-ae33-4a51-9098-1b417153c712", "reader")), 174 lambda: [ 175 server.wait_for_console_text("24ff16a2-ae33-4a51-9098-1b417153c712"), 176 client2.succeed(publish("-m test", "reader")) 177 ]) 178 179 - check_acl() 180 - 181 - def check_tls(): 182 client1.succeed( 183 subscribe( 184 "--cafile ${snakeOil}/ca.crt " ··· 188 port=${toString tlsPort}, 189 user="no_such_user")) 190 191 - check_tls() 192 ''; 193 })
··· 3 let 4 port = 1888; 5 tlsPort = 1889; 6 + anonPort = 1890; 7 password = "VERY_secret"; 8 hashedPassword = "$7$101$/WJc4Mp+I+uYE9sR$o7z9rD1EYXHPwEP5GqQj6A7k4W1yVbePlb8TqNcuOLV9WNCiDgwHOB0JHC1WCtdkssqTBduBNUnUGd6kmZvDSw=="; 9 topic = "test/foo"; ··· 64 }; 65 in { 66 server = { pkgs, ... }: { 67 + networking.firewall.allowedTCPPorts = [ port tlsPort anonPort ]; 68 services.mosquitto = { 69 enable = true; 70 settings = { ··· 113 use_identity_as_username = true; 114 }; 115 } 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 + } 128 ]; 129 }; 130 }; ··· 149 def publish(args, user, topic="${topic}", port=${toString port}): 150 return "{} {}".format(mosquitto_cmd("pub", user, topic, port), args) 151 152 def subscribe(args, user, topic="${topic}", port=${toString port}): 153 + return "{} -W 5 -C 1 {}".format(mosquitto_cmd("sub", user, topic, port), args) 154 155 def parallel(*fns): 156 from threading import Thread ··· 162 start_all() 163 server.wait_for_unit("mosquitto.service") 164 165 + with subtest("check passwords"): 166 client1.succeed(publish("-m test", "password_store")) 167 client1.succeed(publish("-m test", "password_file")) 168 client1.succeed(publish("-m test", "hashed_store")) 169 client1.succeed(publish("-m test", "hashed_file")) 170 171 + with subtest("check acl"): 172 client1.succeed(subscribe("", "reader", topic="$SYS/#")) 173 + client1.fail(subscribe("", "writer", topic="$SYS/#")) 174 175 parallel( 176 lambda: client1.succeed(subscribe("-i 3688cdd7-aa07-42a4-be22-cb9352917e40", "reader")), ··· 180 ]) 181 182 parallel( 183 + lambda: client1.fail(subscribe("-i 24ff16a2-ae33-4a51-9098-1b417153c712", "reader")), 184 lambda: [ 185 server.wait_for_console_text("24ff16a2-ae33-4a51-9098-1b417153c712"), 186 client2.succeed(publish("-m test", "reader")) 187 ]) 188 189 + with subtest("check tls"): 190 client1.succeed( 191 subscribe( 192 "--cafile ${snakeOil}/ca.crt " ··· 196 port=${toString tlsPort}, 197 user="no_such_user")) 198 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 + ]) 207 ''; 208 })