From 81175b442f4e4e3c9c8aa807b92047f73647458e Mon Sep 17 00:00:00 2001 From: pennae Date: Sun, 24 Oct 2021 19:11:45 +0200 Subject: [PATCH 1/3] nixos/mosquitto: refactor test a little --- nixos/tests/mosquitto.nix | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/nixos/tests/mosquitto.nix b/nixos/tests/mosquitto.nix index eb47e97ba04b..bcca5372eaef 100644 --- a/nixos/tests/mosquitto.nix +++ b/nixos/tests/mosquitto.nix @@ -136,9 +136,8 @@ in { def publish(args, user, topic="${topic}", port=${toString port}): return "{} {}".format(mosquitto_cmd("pub", user, topic, port), args) - def subscribe(args, user, topic="${topic}", port=${toString port}): - return "{} -C 1 {}".format(mosquitto_cmd("sub", user, topic, port), args) + return "{} -W 5 -C 1 {}".format(mosquitto_cmd("sub", user, topic, port), args) def parallel(*fns): from threading import Thread @@ -150,17 +149,15 @@ in { start_all() server.wait_for_unit("mosquitto.service") - def check_passwords(): + with subtest("check passwords"): client1.succeed(publish("-m test", "password_store")) client1.succeed(publish("-m test", "password_file")) client1.succeed(publish("-m test", "hashed_store")) client1.succeed(publish("-m test", "hashed_file")) - check_passwords() - - def check_acl(): + with subtest("check acl"): client1.succeed(subscribe("", "reader", topic="$SYS/#")) - client1.fail(subscribe("-W 5", "writer", topic="$SYS/#")) + client1.fail(subscribe("", "writer", topic="$SYS/#")) parallel( lambda: client1.succeed(subscribe("-i 3688cdd7-aa07-42a4-be22-cb9352917e40", "reader")), @@ -170,15 +167,13 @@ in { ]) parallel( - lambda: client1.fail(subscribe("-W 5 -i 24ff16a2-ae33-4a51-9098-1b417153c712", "reader")), + lambda: client1.fail(subscribe("-i 24ff16a2-ae33-4a51-9098-1b417153c712", "reader")), lambda: [ server.wait_for_console_text("24ff16a2-ae33-4a51-9098-1b417153c712"), client2.succeed(publish("-m test", "reader")) ]) - check_acl() - - def check_tls(): + with subtest("check tls"): client1.succeed( subscribe( "--cafile ${snakeOil}/ca.crt " @@ -187,7 +182,5 @@ in { topic="$SYS/#", port=${toString tlsPort}, user="no_such_user")) - - check_tls() ''; }) From d09952fea85538ff72fb25a9fe8e473f853a58ec Mon Sep 17 00:00:00 2001 From: pennae Date: Sun, 24 Oct 2021 19:34:25 +0200 Subject: [PATCH 2/3] nixos/mosquitto: restore passwordless system feature during the rewrite the checkPasswords=false feature of the old module was lost. restore it, and with it systems that allow any client to use any username. --- .../modules/services/networking/mosquitto.nix | 11 ++++++++- nixos/tests/mosquitto.nix | 24 ++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/nixos/modules/services/networking/mosquitto.nix b/nixos/modules/services/networking/mosquitto.nix index 5a573cbf4ac9..29c42afefffe 100644 --- a/nixos/modules/services/networking/mosquitto.nix +++ b/nixos/modules/services/networking/mosquitto.nix @@ -264,6 +264,15 @@ let default = {}; }; + omitPasswordAuth = mkOption { + type = bool; + description = '' + Omits password checking, allowing anyone to log in with any user name unless + other mandatory authentication methods (eg TLS client certificates) are configured. + ''; + default = false; + }; + acl = mkOption { type = listOf str; description = '' @@ -294,9 +303,9 @@ let formatListener = idx: listener: [ "listener ${toString listener.port} ${toString listener.address}" - "password_file ${cfg.dataDir}/passwd-${toString idx}" "acl_file ${makeACLFile idx listener.users listener.acl}" ] + ++ optional (! listener.omitPasswordAuth) "password_file ${cfg.dataDir}/passwd-${toString idx}" ++ formatFreeform {} listener.settings ++ concatMap formatAuthPlugin listener.authPlugins; diff --git a/nixos/tests/mosquitto.nix b/nixos/tests/mosquitto.nix index bcca5372eaef..36cc8e3e3d9b 100644 --- a/nixos/tests/mosquitto.nix +++ b/nixos/tests/mosquitto.nix @@ -3,6 +3,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: let port = 1888; tlsPort = 1889; + anonPort = 1890; password = "VERY_secret"; hashedPassword = "$7$101$/WJc4Mp+I+uYE9sR$o7z9rD1EYXHPwEP5GqQj6A7k4W1yVbePlb8TqNcuOLV9WNCiDgwHOB0JHC1WCtdkssqTBduBNUnUGd6kmZvDSw=="; topic = "test/foo"; @@ -63,7 +64,7 @@ in { }; in { server = { pkgs, ... }: { - networking.firewall.allowedTCPPorts = [ port tlsPort ]; + networking.firewall.allowedTCPPorts = [ port tlsPort anonPort ]; services.mosquitto = { enable = true; settings = { @@ -112,6 +113,18 @@ in { use_identity_as_username = true; }; } + { + port = anonPort; + omitPasswordAuth = true; + settings.allow_anonymous = true; + acl = [ "pattern read #" ]; + users = { + anonWriter = { + password = "" + password; + acl = [ "write ${topic}" ]; + }; + }; + } ]; }; }; @@ -182,5 +195,14 @@ in { topic="$SYS/#", port=${toString tlsPort}, user="no_such_user")) + + with subtest("check omitPasswordAuth"): + parallel( + lambda: client1.succeed(subscribe("-i fd56032c-d9cb-4813-a3b4-6be0e04c8fc3", + "anonReader", port=${toString anonPort})), + lambda: [ + server.wait_for_console_text("fd56032c-d9cb-4813-a3b4-6be0e04c8fc3"), + client2.succeed(publish("-m test", "anonWriter", port=${toString anonPort})) + ]) ''; }) From 0343045a92d2cff88ad861304f7a979f8e7dcd2d Mon Sep 17 00:00:00 2001 From: pennae Date: Thu, 28 Oct 2021 08:44:32 +0200 Subject: [PATCH 3/3] nixos/mosquitto: add module documentation --- .../modules/services/networking/mosquitto.md | 102 ++++++++++++ .../modules/services/networking/mosquitto.nix | 10 +- .../modules/services/networking/mosquitto.xml | 147 ++++++++++++++++++ 3 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 nixos/modules/services/networking/mosquitto.md create mode 100644 nixos/modules/services/networking/mosquitto.xml diff --git a/nixos/modules/services/networking/mosquitto.md b/nixos/modules/services/networking/mosquitto.md new file mode 100644 index 000000000000..5cdb598151e5 --- /dev/null +++ b/nixos/modules/services/networking/mosquitto.md @@ -0,0 +1,102 @@ +# Mosquitto {#module-services-mosquitto} + +Mosquitto is a MQTT broker often used for IoT or home automation data transport. + +## Quickstart {#module-services-mosquitto-quickstart} + +A minimal configuration for Mosquitto is + +```nix +services.mosquitto = { + enable = true; + listeners = [ { + acl = [ "pattern readwrite #" ]; + omitPasswordAuth = true; + settings.allow_anonymous = true; + } ]; +}; +``` + +This will start a broker on port 1883, listening on all interfaces of the machine, allowing +read/write access to all topics to any user without password requirements. + +User authentication can be configured with the `users` key of listeners. A config that gives +full read access to a user `monitor` and restricted write access to a user `service` could look +like + +```nix +services.mosquitto = { + enable = true; + listeners = [ { + users = { + monitor = { + acl = [ "read #" ]; + password = "monitor"; + }; + service = { + acl = [ "write service/#" ]; + password = "service"; + }; + }; + } ]; +}; +``` + +TLS authentication is configured by setting TLS-related options of the listener: + +```nix +services.mosquitto = { + enable = true; + listeners = [ { + port = 8883; # port change is not required, but helpful to avoid mistakes + # ... + settings = { + cafile = "/path/to/mqtt.ca.pem"; + certfile = "/path/to/mqtt.pem"; + keyfile = "/path/to/mqtt.key"; + }; + } ]; +``` + +## Configuration {#module-services-mosquitto-config} + +The Mosquitto configuration has four distinct types of settings: +the global settings of the daemon, listeners, plugins, and bridges. +Bridges and listeners are part of the global configuration, plugins are part of listeners. +Users of the broker are configured as parts of listeners rather than globally, allowing +configurations in which a given user is only allowed to log in to the broker using specific +listeners (eg to configure an admin user with full access to all topics, but restricted to +localhost). + +Almost all options of Mosquitto are available for configuration at their appropriate levels, some +as NixOS options written in camel case, the remainders under `settings` with their exact names in +the Mosquitto config file. The exceptions are `acl_file` (which is always set according to the +`acl` attributes of a listener and its users) and `per_listener_settings` (which is always set to +`true`). + +### Password authentication {#module-services-mosquitto-config-passwords} + +Mosquitto can be run in two modes, with a password file or without. Each listener has its own +password file, and different listeners may use different password files. Password file generation +can be disabled by setting `omitPasswordAuth = true` for a listener; in this case it is necessary +to either set `settings.allow_anonymous = true` to allow all logins, or to configure other +authentication methods like TLS client certificates with `settings.use_identity_as_username = true`. + +The default is to generate a password file for each listener from the users configured to that +listener. Users with no configured password will not be added to the password file and thus +will not be able to use the broker. + +### ACL format {#module-services-mosquitto-config-acl} + +Every listener has a Mosquitto `acl_file` attached to it. This ACL is configured via two +attributes of the config: + + * the `acl` attribute of the listener configures pattern ACL entries and topic ACL entries + for anonymous users. Each entry must be prefixed with `pattern` or `topic` to distinguish + between these two cases. + * the `acl` attribute of every user configures in the listener configured the ACL for that + given user. Only topic ACLs are supported by Mosquitto in this setting, so no prefix is + required or allowed. + +The default ACL for a listener is empty, disallowing all accesses from all clients. To configure +a completely open ACL, set `acl = [ "pattern readwrite #" ]` in the listener. diff --git a/nixos/modules/services/networking/mosquitto.nix b/nixos/modules/services/networking/mosquitto.nix index 29c42afefffe..2d498d4dbbcf 100644 --- a/nixos/modules/services/networking/mosquitto.nix +++ b/nixos/modules/services/networking/mosquitto.nix @@ -257,7 +257,7 @@ let users = mkOption { type = attrsOf userOptions; - example = { john = { password = "123456"; acl = [ "topic readwrite john/#" ]; }; }; + example = { john = { password = "123456"; acl = [ "readwrite john/#" ]; }; }; description = '' A set of users and their passwords and ACLs. ''; @@ -278,6 +278,7 @@ let description = '' Additional ACL items to prepend to the generated ACL file. ''; + example = [ "pattern read #" "topic readwrite anon/report/#" ]; default = []; }; @@ -654,5 +655,10 @@ in }; - meta.maintainers = with lib.maintainers; [ pennae ]; + meta = { + maintainers = with lib.maintainers; [ pennae ]; + # Don't edit the docbook xml directly, edit the md and generate it: + # `pandoc mosquitto.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > mosquitto.xml` + doc = ./mosquitto.xml; + }; } diff --git a/nixos/modules/services/networking/mosquitto.xml b/nixos/modules/services/networking/mosquitto.xml new file mode 100644 index 000000000000..d16ab28c0269 --- /dev/null +++ b/nixos/modules/services/networking/mosquitto.xml @@ -0,0 +1,147 @@ + + Mosquitto + + Mosquitto is a MQTT broker often used for IoT or home automation + data transport. + +
+ Quickstart + + A minimal configuration for Mosquitto is + + +services.mosquitto = { + enable = true; + listeners = [ { + acl = [ "pattern readwrite #" ]; + omitPasswordAuth = true; + settings.allow_anonymous = true; + } ]; +}; + + + This will start a broker on port 1883, listening on all interfaces + of the machine, allowing read/write access to all topics to any + user without password requirements. + + + User authentication can be configured with the + users key of listeners. A config that gives + full read access to a user monitor and + restricted write access to a user service could + look like + + +services.mosquitto = { + enable = true; + listeners = [ { + users = { + monitor = { + acl = [ "read #" ]; + password = "monitor"; + }; + service = { + acl = [ "write service/#" ]; + password = "service"; + }; + }; + } ]; +}; + + + TLS authentication is configured by setting TLS-related options of + the listener: + + +services.mosquitto = { + enable = true; + listeners = [ { + port = 8883; # port change is not required, but helpful to avoid mistakes + # ... + settings = { + cafile = "/path/to/mqtt.ca.pem"; + certfile = "/path/to/mqtt.pem"; + keyfile = "/path/to/mqtt.key"; + }; + } ]; + +
+
+ Configuration + + The Mosquitto configuration has four distinct types of settings: + the global settings of the daemon, listeners, plugins, and + bridges. Bridges and listeners are part of the global + configuration, plugins are part of listeners. Users of the broker + are configured as parts of listeners rather than globally, + allowing configurations in which a given user is only allowed to + log in to the broker using specific listeners (eg to configure an + admin user with full access to all topics, but restricted to + localhost). + + + Almost all options of Mosquitto are available for configuration at + their appropriate levels, some as NixOS options written in camel + case, the remainders under settings with their + exact names in the Mosquitto config file. The exceptions are + acl_file (which is always set according to the + acl attributes of a listener and its users) and + per_listener_settings (which is always set to + true). + +
+ Password authentication + + Mosquitto can be run in two modes, with a password file or + without. Each listener has its own password file, and different + listeners may use different password files. Password file + generation can be disabled by setting + omitPasswordAuth = true for a listener; in + this case it is necessary to either set + settings.allow_anonymous = true to allow all + logins, or to configure other authentication methods like TLS + client certificates with + settings.use_identity_as_username = true. + + + The default is to generate a password file for each listener + from the users configured to that listener. Users with no + configured password will not be added to the password file and + thus will not be able to use the broker. + +
+
+ ACL format + + Every listener has a Mosquitto acl_file + attached to it. This ACL is configured via two attributes of the + config: + + + + + the acl attribute of the listener + configures pattern ACL entries and topic ACL entries for + anonymous users. Each entry must be prefixed with + pattern or topic to + distinguish between these two cases. + + + + + the acl attribute of every user + configures in the listener configured the ACL for that given + user. Only topic ACLs are supported by Mosquitto in this + setting, so no prefix is required or allowed. + + + + + The default ACL for a listener is empty, disallowing all + accesses from all clients. To configure a completely open ACL, + set acl = [ "pattern readwrite #" ] + in the listener. + +
+
+