at 21.11-pre 8.7 kB view raw
1{ config, lib, pkgs, ...}: 2 3with lib; 4 5let 6 cfg = config.services.mosquitto; 7 8 listenerConf = optionalString cfg.ssl.enable '' 9 listener ${toString cfg.ssl.port} ${cfg.ssl.host} 10 cafile ${cfg.ssl.cafile} 11 certfile ${cfg.ssl.certfile} 12 keyfile ${cfg.ssl.keyfile} 13 ''; 14 15 passwordConf = optionalString cfg.checkPasswords '' 16 password_file ${cfg.dataDir}/passwd 17 ''; 18 19 mosquittoConf = pkgs.writeText "mosquitto.conf" '' 20 acl_file ${aclFile} 21 persistence true 22 allow_anonymous ${boolToString cfg.allowAnonymous} 23 listener ${toString cfg.port} ${cfg.host} 24 ${passwordConf} 25 ${listenerConf} 26 ${cfg.extraConf} 27 ''; 28 29 userAcl = (concatStringsSep "\n\n" (mapAttrsToList (n: c: 30 "user ${n}\n" + (concatStringsSep "\n" c.acl)) cfg.users 31 )); 32 33 aclFile = pkgs.writeText "mosquitto.acl" '' 34 ${cfg.aclExtraConf} 35 ${userAcl} 36 ''; 37 38in 39 40{ 41 42 ###### Interface 43 44 options = { 45 services.mosquitto = { 46 enable = mkEnableOption "the MQTT Mosquitto broker"; 47 48 host = mkOption { 49 default = "127.0.0.1"; 50 example = "0.0.0.0"; 51 type = types.str; 52 description = '' 53 Host to listen on without SSL. 54 ''; 55 }; 56 57 port = mkOption { 58 default = 1883; 59 example = 1883; 60 type = types.int; 61 description = '' 62 Port on which to listen without SSL. 63 ''; 64 }; 65 66 ssl = { 67 enable = mkEnableOption "SSL listener"; 68 69 cafile = mkOption { 70 type = types.nullOr types.path; 71 default = null; 72 description = "Path to PEM encoded CA certificates."; 73 }; 74 75 certfile = mkOption { 76 type = types.nullOr types.path; 77 default = null; 78 description = "Path to PEM encoded server certificate."; 79 }; 80 81 keyfile = mkOption { 82 type = types.nullOr types.path; 83 default = null; 84 description = "Path to PEM encoded server key."; 85 }; 86 87 host = mkOption { 88 default = "0.0.0.0"; 89 example = "localhost"; 90 type = types.str; 91 description = '' 92 Host to listen on with SSL. 93 ''; 94 }; 95 96 port = mkOption { 97 default = 8883; 98 example = 8883; 99 type = types.int; 100 description = '' 101 Port on which to listen with SSL. 102 ''; 103 }; 104 }; 105 106 dataDir = mkOption { 107 default = "/var/lib/mosquitto"; 108 type = types.path; 109 description = '' 110 The data directory. 111 ''; 112 }; 113 114 users = mkOption { 115 type = types.attrsOf (types.submodule { 116 options = { 117 password = mkOption { 118 type = with types; uniq (nullOr str); 119 default = null; 120 description = '' 121 Specifies the (clear text) password for the MQTT User. 122 ''; 123 }; 124 125 passwordFile = mkOption { 126 type = with types; uniq (nullOr str); 127 example = "/path/to/file"; 128 default = null; 129 description = '' 130 Specifies the path to a file containing the 131 clear text password for the MQTT user. 132 ''; 133 }; 134 135 hashedPassword = mkOption { 136 type = with types; uniq (nullOr str); 137 default = null; 138 description = '' 139 Specifies the hashed password for the MQTT User. 140 To generate hashed password install <literal>mosquitto</literal> 141 package and use <literal>mosquitto_passwd</literal>. 142 ''; 143 }; 144 145 hashedPasswordFile = mkOption { 146 type = with types; uniq (nullOr str); 147 example = "/path/to/file"; 148 default = null; 149 description = '' 150 Specifies the path to a file containing the 151 hashed password for the MQTT user. 152 To generate hashed password install <literal>mosquitto</literal> 153 package and use <literal>mosquitto_passwd</literal>. 154 ''; 155 }; 156 157 acl = mkOption { 158 type = types.listOf types.str; 159 example = [ "topic read A/B" "topic A/#" ]; 160 description = '' 161 Control client access to topics on the broker. 162 ''; 163 }; 164 }; 165 }); 166 example = { john = { password = "123456"; acl = [ "topic readwrite john/#" ]; }; }; 167 description = '' 168 A set of users and their passwords and ACLs. 169 ''; 170 }; 171 172 allowAnonymous = mkOption { 173 default = false; 174 type = types.bool; 175 description = '' 176 Allow clients to connect without authentication. 177 ''; 178 }; 179 180 checkPasswords = mkOption { 181 default = false; 182 example = true; 183 type = types.bool; 184 description = '' 185 Refuse connection when clients provide incorrect passwords. 186 ''; 187 }; 188 189 extraConf = mkOption { 190 default = ""; 191 type = types.lines; 192 description = '' 193 Extra config to append to `mosquitto.conf` file. 194 ''; 195 }; 196 197 aclExtraConf = mkOption { 198 default = ""; 199 type = types.lines; 200 description = '' 201 Extra config to prepend to the ACL file. 202 ''; 203 }; 204 205 }; 206 }; 207 208 209 ###### Implementation 210 211 config = mkIf cfg.enable { 212 213 assertions = mapAttrsToList (name: cfg: { 214 assertion = length (filter (s: s != null) (with cfg; [ 215 password passwordFile hashedPassword hashedPasswordFile 216 ])) <= 1; 217 message = "Cannot set more than one password option"; 218 }) cfg.users; 219 220 systemd.services.mosquitto = { 221 description = "Mosquitto MQTT Broker Daemon"; 222 wantedBy = [ "multi-user.target" ]; 223 after = [ "network.target" ]; 224 serviceConfig = { 225 Type = "notify"; 226 NotifyAccess = "main"; 227 User = "mosquitto"; 228 Group = "mosquitto"; 229 RuntimeDirectory = "mosquitto"; 230 WorkingDirectory = cfg.dataDir; 231 Restart = "on-failure"; 232 ExecStart = "${pkgs.mosquitto}/bin/mosquitto -c ${mosquittoConf}"; 233 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; 234 235 # Hardening 236 CapabilityBoundingSet = ""; 237 DevicePolicy = "closed"; 238 LockPersonality = true; 239 MemoryDenyWriteExecute = true; 240 NoNewPrivileges = true; 241 PrivateDevices = true; 242 PrivateTmp = true; 243 PrivateUsers = true; 244 ProtectClock = true; 245 ProtectControlGroups = true; 246 ProtectHome = true; 247 ProtectHostname = true; 248 ProtectKernelLogs = true; 249 ProtectKernelModules = true; 250 ProtectKernelTunables = true; 251 ProtectProc = "invisible"; 252 ProcSubset = "pid"; 253 ProtectSystem = "strict"; 254 ReadWritePaths = [ 255 cfg.dataDir 256 "/tmp" # mosquitto_passwd creates files in /tmp before moving them 257 ]; 258 ReadOnlyPaths = with cfg.ssl; lib.optionals (enable) [ 259 certfile 260 keyfile 261 cafile 262 ]; 263 RemoveIPC = true; 264 RestrictAddressFamilies = [ 265 "AF_UNIX" # for sd_notify() call 266 "AF_INET" 267 "AF_INET6" 268 ]; 269 RestrictNamespaces = true; 270 RestrictRealtime = true; 271 RestrictSUIDSGID = true; 272 SystemCallArchitectures = "native"; 273 SystemCallFilter = [ 274 "@system-service" 275 "~@privileged" 276 "~@resources" 277 ]; 278 UMask = "0077"; 279 }; 280 preStart = '' 281 rm -f ${cfg.dataDir}/passwd 282 touch ${cfg.dataDir}/passwd 283 '' + concatStringsSep "\n" ( 284 mapAttrsToList (n: c: 285 if c.hashedPasswordFile != null then 286 "echo '${n}:'$(cat '${c.hashedPasswordFile}') >> ${cfg.dataDir}/passwd" 287 else if c.passwordFile != null then 288 "${pkgs.mosquitto}/bin/mosquitto_passwd -b ${cfg.dataDir}/passwd ${n} $(cat '${c.passwordFile}')" 289 else if c.hashedPassword != null then 290 "echo '${n}:${c.hashedPassword}' >> ${cfg.dataDir}/passwd" 291 else optionalString (c.password != null) 292 "${pkgs.mosquitto}/bin/mosquitto_passwd -b ${cfg.dataDir}/passwd ${n} '${c.password}'" 293 ) cfg.users); 294 }; 295 296 users.users.mosquitto = { 297 description = "Mosquitto MQTT Broker Daemon owner"; 298 group = "mosquitto"; 299 uid = config.ids.uids.mosquitto; 300 home = cfg.dataDir; 301 createHome = true; 302 }; 303 304 users.groups.mosquitto.gid = config.ids.gids.mosquitto; 305 306 }; 307}