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 5a573cbf4ac9..2d498d4dbbcf 100644
--- a/nixos/modules/services/networking/mosquitto.nix
+++ b/nixos/modules/services/networking/mosquitto.nix
@@ -257,18 +257,28 @@ 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.
'';
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 = ''
Additional ACL items to prepend to the generated ACL file.
'';
+ example = [ "pattern read #" "topic readwrite anon/report/#" ];
default = [];
};
@@ -294,9 +304,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;
@@ -645,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.
+
+
+
+
diff --git a/nixos/tests/mosquitto.nix b/nixos/tests/mosquitto.nix
index eb47e97ba04b..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}" ];
+ };
+ };
+ }
];
};
};
@@ -136,9 +149,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 +162,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 +180,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 "
@@ -188,6 +196,13 @@ in {
port=${toString tlsPort},
user="no_such_user"))
- check_tls()
+ 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}))
+ ])
'';
})