From 7d09d7f5713dac972ce9d72624d20635899c876d Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Sat, 24 Apr 2021 14:52:14 +0200 Subject: [PATCH] nixos/home-assistant: harden systemd service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is what is still exposed, and it should still allow things to work as usual. ✗ PrivateNetwork= Service has access to the host's … 0.5 ✗ RestrictAddressFamilies=~AF_(INET… Service may allocate Internet soc… 0.3 ✗ DeviceAllow= Service has a device ACL with som… 0.1 ✗ IPAddressDeny= Service does not define an IP add… 0.2 ✗ PrivateDevices= Service potentially has access to… 0.2 ✗ PrivateUsers= Service has access to other users 0.2 ✗ SystemCallFilter=~@resources System call allow list defined fo… 0.2 ✗ RootDirectory=/RootImage= Service runs within the host's ro… 0.1 ✗ SupplementaryGroups= Service runs with supplementary g… 0.1 ✗ RestrictAddressFamilies=~AF_UNIX Service may allocate local sockets 0.1 → Overall exposure level for home-assistant.service: 1.6 OK :-) This can grow to as much as ~1.9 if you use one of the bluetooth or nmap trackers or the emulated_hue component, all of which required elevated permisssions. --- .../modules/services/misc/home-assistant.nix | 72 +++++++++++++++++-- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/misc/home-assistant.nix index 0590f54ae60..9ae86af0875 100644 --- a/nixos/modules/services/misc/home-assistant.nix +++ b/nixos/modules/services/misc/home-assistant.nix @@ -245,22 +245,83 @@ in { rm -f "${cfg.configDir}/ui-lovelace.yaml" ln -s ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml" ''); - serviceConfig = { + serviceConfig = let + # List of capabilities to equip home-assistant with, depending on configured components + capabilities = [ + # Empty string first, so we will never accidentally have an empty capability bounding set + # https://github.com/NixOS/nixpkgs/issues/120617#issuecomment-830685115 + "" + ] ++ (unique (optionals (useComponent "bluetooth_tracker" || useComponent "bluetooth_le_tracker") [ + # Required for interaction with hci devices and bluetooth sockets + # https://www.home-assistant.io/integrations/bluetooth_le_tracker/#rootless-setup-on-core-installs + "CAP_NET_ADMIN" + "CAP_NET_RAW" + ] ++ lib.optionals (useComponent "emulated_hue") [ + # Alexa looks for the service on port 80 + # https://www.home-assistant.io/integrations/emulated_hue + "CAP_NET_BIND_SERVICE" + ] ++ lib.optionals (useComponent "nmap_tracker") [ + # https://www.home-assistant.io/integrations/nmap_tracker#linux-capabilities + "CAP_NET_ADMIN" + "CAP_NET_BIND_SERVICE" + "CAP_NET_RAW" + ])); + in { ExecStart = "${package}/bin/hass --config '${cfg.configDir}'"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; User = "hass"; Group = "hass"; Restart = "on-failure"; + KillSignal = "SIGINT"; + + # Hardening + AmbientCapabilities = capabilities; + CapabilityBoundingSet = capabilities; + DeviceAllow = [ + "char-ttyACM rw" + "char-ttyAMA rw" + "char-ttyUSB rw" + ]; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateTmp = true; + PrivateUsers = false; # prevents gaining capabilities in the host namespace + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProcSubset = "pid"; ProtectSystem = "strict"; + RemoveIPC = true; ReadWritePaths = let + # Allow rw access to explicitly configured paths cfgPath = [ "config" "homeassistant" "allowlist_external_dirs" ]; value = attrByPath cfgPath [] cfg; allowPaths = if isList value then value else singleton value; in [ "${cfg.configDir}" ] ++ allowPaths; - KillSignal = "SIGINT"; - PrivateTmp = true; - RemoveIPC = true; - AmbientCapabilities = "cap_net_raw,cap_net_admin+eip"; + RestrictAddressFamilies = [ + "AF_UNIX" + "AF_INET" + "AF_INET6" + ] ++ optionals (useComponent "bluetooth_tracker" || useComponent "bluetooth_le_tracker") [ + "AF_BLUETOOTH" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SupplementaryGroups = [ "dialout" ]; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ]; + UMask = "0077"; }; path = [ "/run/wrappers" # needed for ping @@ -278,7 +339,6 @@ in { home = cfg.configDir; createHome = true; group = "hass"; - extraGroups = [ "dialout" ]; uid = config.ids.uids.hass; };