2016-05-22 15:07:26 +01:00
|
|
|
{ config, lib, pkgs, ...}:
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
let
|
|
|
|
cfg = config.services.mosquitto;
|
|
|
|
|
|
|
|
listenerConf = optionalString cfg.ssl.enable ''
|
|
|
|
listener ${toString cfg.ssl.port} ${cfg.ssl.host}
|
|
|
|
cafile ${cfg.ssl.cafile}
|
|
|
|
certfile ${cfg.ssl.certfile}
|
|
|
|
keyfile ${cfg.ssl.keyfile}
|
|
|
|
'';
|
|
|
|
|
2017-08-06 23:21:01 +01:00
|
|
|
passwordConf = optionalString cfg.checkPasswords ''
|
|
|
|
password_file ${cfg.dataDir}/passwd
|
|
|
|
'';
|
|
|
|
|
2016-05-22 15:07:26 +01:00
|
|
|
mosquittoConf = pkgs.writeText "mosquitto.conf" ''
|
|
|
|
acl_file ${aclFile}
|
|
|
|
persistence true
|
2017-04-11 17:08:51 +01:00
|
|
|
allow_anonymous ${boolToString cfg.allowAnonymous}
|
2021-04-29 02:56:40 +01:00
|
|
|
listener ${toString cfg.port} ${cfg.host}
|
2017-08-06 23:21:01 +01:00
|
|
|
${passwordConf}
|
2016-05-22 15:07:26 +01:00
|
|
|
${listenerConf}
|
|
|
|
${cfg.extraConf}
|
|
|
|
'';
|
|
|
|
|
|
|
|
userAcl = (concatStringsSep "\n\n" (mapAttrsToList (n: c:
|
|
|
|
"user ${n}\n" + (concatStringsSep "\n" c.acl)) cfg.users
|
|
|
|
));
|
|
|
|
|
|
|
|
aclFile = pkgs.writeText "mosquitto.acl" ''
|
|
|
|
${cfg.aclExtraConf}
|
|
|
|
${userAcl}
|
|
|
|
'';
|
|
|
|
|
|
|
|
in
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
###### Interface
|
|
|
|
|
|
|
|
options = {
|
|
|
|
services.mosquitto = {
|
2019-04-20 02:41:48 +01:00
|
|
|
enable = mkEnableOption "the MQTT Mosquitto broker";
|
2016-05-22 15:07:26 +01:00
|
|
|
|
|
|
|
host = mkOption {
|
|
|
|
default = "127.0.0.1";
|
|
|
|
example = "0.0.0.0";
|
2019-08-08 21:48:27 +01:00
|
|
|
type = types.str;
|
2016-05-22 15:07:26 +01:00
|
|
|
description = ''
|
|
|
|
Host to listen on without SSL.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
port = mkOption {
|
|
|
|
default = 1883;
|
|
|
|
type = types.int;
|
|
|
|
description = ''
|
|
|
|
Port on which to listen without SSL.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
ssl = {
|
2019-04-20 02:41:48 +01:00
|
|
|
enable = mkEnableOption "SSL listener";
|
2016-05-22 15:07:26 +01:00
|
|
|
|
|
|
|
cafile = mkOption {
|
|
|
|
type = types.nullOr types.path;
|
|
|
|
default = null;
|
|
|
|
description = "Path to PEM encoded CA certificates.";
|
|
|
|
};
|
|
|
|
|
|
|
|
certfile = mkOption {
|
|
|
|
type = types.nullOr types.path;
|
|
|
|
default = null;
|
|
|
|
description = "Path to PEM encoded server certificate.";
|
|
|
|
};
|
|
|
|
|
|
|
|
keyfile = mkOption {
|
|
|
|
type = types.nullOr types.path;
|
|
|
|
default = null;
|
|
|
|
description = "Path to PEM encoded server key.";
|
|
|
|
};
|
|
|
|
|
|
|
|
host = mkOption {
|
|
|
|
default = "0.0.0.0";
|
|
|
|
example = "localhost";
|
2019-08-08 21:48:27 +01:00
|
|
|
type = types.str;
|
2016-05-22 15:07:26 +01:00
|
|
|
description = ''
|
|
|
|
Host to listen on with SSL.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
port = mkOption {
|
|
|
|
default = 8883;
|
|
|
|
type = types.int;
|
|
|
|
description = ''
|
|
|
|
Port on which to listen with SSL.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
dataDir = mkOption {
|
|
|
|
default = "/var/lib/mosquitto";
|
|
|
|
type = types.path;
|
|
|
|
description = ''
|
|
|
|
The data directory.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
users = mkOption {
|
|
|
|
type = types.attrsOf (types.submodule {
|
|
|
|
options = {
|
|
|
|
password = mkOption {
|
|
|
|
type = with types; uniq (nullOr str);
|
|
|
|
default = null;
|
|
|
|
description = ''
|
|
|
|
Specifies the (clear text) password for the MQTT User.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2020-10-21 16:30:12 +01:00
|
|
|
passwordFile = mkOption {
|
|
|
|
type = with types; uniq (nullOr str);
|
|
|
|
example = "/path/to/file";
|
|
|
|
default = null;
|
|
|
|
description = ''
|
|
|
|
Specifies the path to a file containing the
|
|
|
|
clear text password for the MQTT user.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2016-05-22 15:07:26 +01:00
|
|
|
hashedPassword = mkOption {
|
|
|
|
type = with types; uniq (nullOr str);
|
|
|
|
default = null;
|
|
|
|
description = ''
|
|
|
|
Specifies the hashed password for the MQTT User.
|
2020-10-21 16:30:12 +01:00
|
|
|
To generate hashed password install <literal>mosquitto</literal>
|
|
|
|
package and use <literal>mosquitto_passwd</literal>.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
hashedPasswordFile = mkOption {
|
|
|
|
type = with types; uniq (nullOr str);
|
|
|
|
example = "/path/to/file";
|
|
|
|
default = null;
|
|
|
|
description = ''
|
|
|
|
Specifies the path to a file containing the
|
|
|
|
hashed password for the MQTT user.
|
2017-08-06 23:54:36 +01:00
|
|
|
To generate hashed password install <literal>mosquitto</literal>
|
|
|
|
package and use <literal>mosquitto_passwd</literal>.
|
2016-05-22 15:07:26 +01:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
acl = mkOption {
|
2019-08-08 21:48:27 +01:00
|
|
|
type = types.listOf types.str;
|
2016-05-22 15:07:26 +01:00
|
|
|
example = [ "topic read A/B" "topic A/#" ];
|
|
|
|
description = ''
|
|
|
|
Control client access to topics on the broker.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
});
|
|
|
|
example = { john = { password = "123456"; acl = [ "topic readwrite john/#" ]; }; };
|
|
|
|
description = ''
|
|
|
|
A set of users and their passwords and ACLs.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
allowAnonymous = mkOption {
|
|
|
|
default = false;
|
|
|
|
type = types.bool;
|
|
|
|
description = ''
|
|
|
|
Allow clients to connect without authentication.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2017-08-06 23:21:01 +01:00
|
|
|
checkPasswords = mkOption {
|
|
|
|
default = false;
|
|
|
|
example = true;
|
|
|
|
type = types.bool;
|
|
|
|
description = ''
|
|
|
|
Refuse connection when clients provide incorrect passwords.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2016-05-22 15:07:26 +01:00
|
|
|
extraConf = mkOption {
|
|
|
|
default = "";
|
|
|
|
type = types.lines;
|
|
|
|
description = ''
|
|
|
|
Extra config to append to `mosquitto.conf` file.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
aclExtraConf = mkOption {
|
|
|
|
default = "";
|
|
|
|
type = types.lines;
|
|
|
|
description = ''
|
|
|
|
Extra config to prepend to the ACL file.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
###### Implementation
|
|
|
|
|
|
|
|
config = mkIf cfg.enable {
|
|
|
|
|
2020-10-21 16:30:12 +01:00
|
|
|
assertions = mapAttrsToList (name: cfg: {
|
|
|
|
assertion = length (filter (s: s != null) (with cfg; [
|
|
|
|
password passwordFile hashedPassword hashedPasswordFile
|
|
|
|
])) <= 1;
|
|
|
|
message = "Cannot set more than one password option";
|
|
|
|
}) cfg.users;
|
|
|
|
|
2016-05-22 15:07:26 +01:00
|
|
|
systemd.services.mosquitto = {
|
|
|
|
description = "Mosquitto MQTT Broker Daemon";
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
after = [ "network.target" ];
|
|
|
|
serviceConfig = {
|
2019-03-01 10:54:24 +00:00
|
|
|
Type = "notify";
|
|
|
|
NotifyAccess = "main";
|
2016-05-22 15:07:26 +01:00
|
|
|
User = "mosquitto";
|
|
|
|
Group = "mosquitto";
|
|
|
|
RuntimeDirectory = "mosquitto";
|
|
|
|
WorkingDirectory = cfg.dataDir;
|
|
|
|
Restart = "on-failure";
|
2019-03-01 10:54:24 +00:00
|
|
|
ExecStart = "${pkgs.mosquitto}/bin/mosquitto -c ${mosquittoConf}";
|
2016-05-22 15:07:26 +01:00
|
|
|
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
2020-04-13 09:43:32 +01:00
|
|
|
|
2021-04-24 16:22:54 +01:00
|
|
|
# Hardening
|
|
|
|
CapabilityBoundingSet = "";
|
|
|
|
DevicePolicy = "closed";
|
|
|
|
LockPersonality = true;
|
|
|
|
MemoryDenyWriteExecute = true;
|
|
|
|
NoNewPrivileges = true;
|
2020-04-13 09:43:32 +01:00
|
|
|
PrivateDevices = true;
|
|
|
|
PrivateTmp = true;
|
2021-04-24 16:22:54 +01:00
|
|
|
PrivateUsers = true;
|
|
|
|
ProtectClock = true;
|
2020-04-13 09:43:32 +01:00
|
|
|
ProtectControlGroups = true;
|
2021-04-24 16:22:54 +01:00
|
|
|
ProtectHome = true;
|
|
|
|
ProtectHostname = true;
|
|
|
|
ProtectKernelLogs = true;
|
2020-04-13 09:43:32 +01:00
|
|
|
ProtectKernelModules = true;
|
|
|
|
ProtectKernelTunables = true;
|
2021-04-24 16:22:54 +01:00
|
|
|
ProtectProc = "invisible";
|
|
|
|
ProcSubset = "pid";
|
|
|
|
ProtectSystem = "strict";
|
|
|
|
ReadWritePaths = [
|
|
|
|
cfg.dataDir
|
|
|
|
"/tmp" # mosquitto_passwd creates files in /tmp before moving them
|
|
|
|
];
|
|
|
|
ReadOnlyPaths = with cfg.ssl; lib.optionals (enable) [
|
|
|
|
certfile
|
|
|
|
keyfile
|
|
|
|
cafile
|
|
|
|
];
|
|
|
|
RemoveIPC = true;
|
|
|
|
RestrictAddressFamilies = [
|
|
|
|
"AF_UNIX" # for sd_notify() call
|
|
|
|
"AF_INET"
|
|
|
|
"AF_INET6"
|
|
|
|
];
|
|
|
|
RestrictNamespaces = true;
|
|
|
|
RestrictRealtime = true;
|
|
|
|
RestrictSUIDSGID = true;
|
|
|
|
SystemCallArchitectures = "native";
|
|
|
|
SystemCallFilter = [
|
|
|
|
"@system-service"
|
|
|
|
"~@privileged"
|
|
|
|
"~@resources"
|
|
|
|
];
|
|
|
|
UMask = "0077";
|
2016-05-22 15:07:26 +01:00
|
|
|
};
|
|
|
|
preStart = ''
|
|
|
|
rm -f ${cfg.dataDir}/passwd
|
|
|
|
touch ${cfg.dataDir}/passwd
|
|
|
|
'' + concatStringsSep "\n" (
|
|
|
|
mapAttrsToList (n: c:
|
2020-10-21 16:30:12 +01:00
|
|
|
if c.hashedPasswordFile != null then
|
|
|
|
"echo '${n}:'$(cat '${c.hashedPasswordFile}') >> ${cfg.dataDir}/passwd"
|
|
|
|
else if c.passwordFile != null then
|
|
|
|
"${pkgs.mosquitto}/bin/mosquitto_passwd -b ${cfg.dataDir}/passwd ${n} $(cat '${c.passwordFile}')"
|
|
|
|
else if c.hashedPassword != null then
|
2018-02-08 21:46:06 +00:00
|
|
|
"echo '${n}:${c.hashedPassword}' >> ${cfg.dataDir}/passwd"
|
2016-05-22 15:07:26 +01:00
|
|
|
else optionalString (c.password != null)
|
2019-03-01 10:54:24 +00:00
|
|
|
"${pkgs.mosquitto}/bin/mosquitto_passwd -b ${cfg.dataDir}/passwd ${n} '${c.password}'"
|
2016-05-22 15:07:26 +01:00
|
|
|
) cfg.users);
|
|
|
|
};
|
|
|
|
|
2018-06-30 00:58:35 +01:00
|
|
|
users.users.mosquitto = {
|
2016-05-22 15:07:26 +01:00
|
|
|
description = "Mosquitto MQTT Broker Daemon owner";
|
|
|
|
group = "mosquitto";
|
|
|
|
uid = config.ids.uids.mosquitto;
|
|
|
|
home = cfg.dataDir;
|
|
|
|
createHome = true;
|
|
|
|
};
|
|
|
|
|
2018-06-30 00:58:35 +01:00
|
|
|
users.groups.mosquitto.gid = config.ids.gids.mosquitto;
|
2016-05-22 15:07:26 +01:00
|
|
|
|
|
|
|
};
|
|
|
|
}
|