@@ -245,22 +245,85 @@ in {
245245 rm -f "${cfg.configDir}/ui-lovelace.yaml"
246246 ln -s ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
247247 '');
248− serviceConfig = {
249− ExecStart = "${package}/bin/hass --config '${cfg.configDir}'";
248+ serviceConfig = let
249+ # List of capabilities to equip home-assistant with, depending on configured components
250+ capabilities = [
251+ # Empty string first, so we will never accidentally have an empty capability bounding set
252+ # https://github.com/NixOS/nixpkgs/issues/120617#issuecomment-830685115
253+ ""
254+ ] ++ (unique (optionals (useComponent "bluetooth_tracker" || useComponent "bluetooth_le_tracker") [
255+ # Required for interaction with hci devices and bluetooth sockets
256+ # https://www.home-assistant.io/integrations/bluetooth_le_tracker/#rootless-setup-on-core-installs
257+ "CAP_NET_ADMIN"
258+ "CAP_NET_RAW"
259+ ] ++ lib.optionals (useComponent "emulated_hue") [
260+ # Alexa looks for the service on port 80
261+ # https://www.home-assistant.io/integrations/emulated_hue
262+ "CAP_NET_BIND_SERVICE"
263+ ] ++ lib.optionals (useComponent "nmap_tracker") [
264+ # https://www.home-assistant.io/integrations/nmap_tracker#linux-capabilities
265+ "CAP_NET_ADMIN"
266+ "CAP_NET_BIND_SERVICE"
267+ "CAP_NET_RAW"
268+ ]));
269+ in {
270+ ExecStart = "${package}/bin/hass --runner --config '${cfg.configDir}'";
250271 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
251272 User = "hass";
252273 Group = "hass";
253274 Restart = "on-failure";
275+ RestartForceExitStatus = "100";
276+ SuccessExitStatus = "100";
277+ KillSignal = "SIGINT";
278+
279+ # Hardening
280+ AmbientCapabilities = capabilities;
281+ CapabilityBoundingSet = capabilities;
282+ DeviceAllow = [
283+ "char-ttyACM rw"
284+ "char-ttyAMA rw"
285+ "char-ttyUSB rw"
286+ ];
287+ DevicePolicy = "closed";
288+ LockPersonality = true;
289+ MemoryDenyWriteExecute = true;
290+ NoNewPrivileges = true;
291+ PrivateTmp = true;
292+ PrivateUsers = false; # prevents gaining capabilities in the host namespace
293+ ProtectClock = true;
294+ ProtectControlGroups = true;
295+ ProtectHome = true;
296+ ProtectHostname = true;
297+ ProtectKernelLogs = true;
298+ ProtectKernelModules = true;
299+ ProtectKernelTunables = true;
300+ ProtectProc = "invisible";
301+ ProcSubset = "pid";
254302 ProtectSystem = "strict";
303+ RemoveIPC = true;
255304 ReadWritePaths = let
305+ # Allow rw access to explicitly configured paths
256306 cfgPath = [ "config" "homeassistant" "allowlist_external_dirs" ];
257307 value = attrByPath cfgPath [] cfg;
258308 allowPaths = if isList value then value else singleton value;
259309 in [ "${cfg.configDir}" ] ++ allowPaths;
260− KillSignal = "SIGINT";
261− PrivateTmp = true;
262− RemoveIPC = true;
263− AmbientCapabilities = "cap_net_raw,cap_net_admin+eip";
310+ RestrictAddressFamilies = [
311+ "AF_UNIX"
312+ "AF_INET"
313+ "AF_INET6"
314+ ] ++ optionals (useComponent "bluetooth_tracker" || useComponent "bluetooth_le_tracker") [
315+ "AF_BLUETOOTH"
316+ ];
317+ RestrictNamespaces = true;
318+ RestrictRealtime = true;
319+ RestrictSUIDSGID = true;
320+ SupplementaryGroups = [ "dialout" ];
321+ SystemCallArchitectures = "native";
322+ SystemCallFilter = [
323+ "@system-service"
324+ "~@privileged"
325+ ];
326+ UMask = "0077";
264327 };
265328 path = [
266329 "/run/wrappers" # needed for ping
@@ -278,7 +341,6 @@ in {
278341 home = cfg.configDir;
279342 createHome = true;
280343 group = "hass";
281− extraGroups = [ "dialout" ];
282344 uid = config.ids.uids.hass;
283345 };
284346