From 7d09d7f5713dac972ce9d72624d20635899c876d Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Sat, 24 Apr 2021 14:52:14 +0200 Subject: [PATCH 1/4] 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 0590f54ae60e..9ae86af08750 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; }; From 8ab7fc11076373fee3e5cc842176e6fb8c5705b3 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Sun, 25 Apr 2021 14:44:51 +0200 Subject: [PATCH 2/4] nixos/tests/home-assistant: test capability passing Configures the emulated_hue component and expects CAP_NET_BIND_SERVICE to be passed in order to be able to bind to 80/tcp. Also print the systemd security analysis, so we can spot changes more quickly. --- nixos/tests/home-assistant.nix | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix index 3b7295324a18..2224403961e2 100644 --- a/nixos/tests/home-assistant.nix +++ b/nixos/tests/home-assistant.nix @@ -47,6 +47,10 @@ in { payload_on = "let_there_be_light"; payload_off = "off"; }]; + emulated_hue = { + host_ip = "127.0.0.1"; + listen_port = 80; + }; logger = { default = "info"; logs."homeassistant.components.mqtt" = "debug"; @@ -82,6 +86,9 @@ in { hass.succeed( "mosquitto_pub -V mqttv5 -t home-assistant/test -u ${mqttUsername} -P '${mqttPassword}' -m let_there_be_light" ) + with subtest("Check that capabilities are passed for emulated_hue to bind to port 80"): + hass.wait_for_open_port(80) + hass.succeed("curl --fail http://localhost:80/description.xml") with subtest("Print log to ease debugging"): output_log = hass.succeed("cat ${configDir}/home-assistant.log") print("\n### home-assistant.log ###\n") @@ -93,5 +100,8 @@ in { # example line: 2020-06-20 10:01:32 DEBUG (MainThread) [homeassistant.components.mqtt] Received message on home-assistant/test: b'let_there_be_light' with subtest("Check we received the mosquitto message"): assert "let_there_be_light" in output_log + + with subtest("Check systemd unit hardening"): + hass.log(hass.succeed("systemd-analyze security home-assistant.service")) ''; }) From 1dbb60f562f73cfa46d2e5ef21f9d2a98ecba565 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Mon, 26 Apr 2021 02:17:48 +0200 Subject: [PATCH 3/4] nixos/tests/home-assistant: update maintainership to home-assistant team --- nixos/tests/home-assistant.nix | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix index 2224403961e2..c75dd248ecb3 100644 --- a/nixos/tests/home-assistant.nix +++ b/nixos/tests/home-assistant.nix @@ -1,4 +1,4 @@ -import ./make-test-python.nix ({ pkgs, ... }: +import ./make-test-python.nix ({ pkgs, lib, ... }: let configDir = "/var/lib/foobar"; @@ -6,9 +6,7 @@ let mqttPassword = "secret"; in { name = "home-assistant"; - meta = with pkgs.lib; { - maintainers = with maintainers; [ dotlambda ]; - }; + meta.maintainers = lib.teams.home-assistant.members; nodes.hass = { pkgs, ... }: { environment.systemPackages = with pkgs; [ mosquitto ]; From f41349d30d5e1cc72c8041616ecb8c36d56f3682 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Mon, 26 Apr 2021 02:39:09 +0200 Subject: [PATCH 4/4] nixos/home-assistant: Restart systemd unit on restart service Home-assistant through its `--runner` commandline flag supports sending exit code 100 when the `homeassistant.restart` service is called. With `RestartForceExitStatus` we can listen for that specific exit code and restart the whole systemd unit, providing an actual clean restart with fresh processes. Additional treat exit code 100 as a successful termination. --- nixos/modules/services/misc/home-assistant.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/misc/home-assistant.nix index 9ae86af08750..1985f1308811 100644 --- a/nixos/modules/services/misc/home-assistant.nix +++ b/nixos/modules/services/misc/home-assistant.nix @@ -267,11 +267,13 @@ in { "CAP_NET_RAW" ])); in { - ExecStart = "${package}/bin/hass --config '${cfg.configDir}'"; + ExecStart = "${package}/bin/hass --runner --config '${cfg.configDir}'"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; User = "hass"; Group = "hass"; Restart = "on-failure"; + RestartForceExitStatus = "100"; + SuccessExitStatus = "100"; KillSignal = "SIGINT"; # Hardening