nixos/home-assistant: harden systemd service

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.
This commit is contained in:
Martin Weinelt 2021-04-24 14:52:14 +02:00
parent 4518794ee5
commit 7d09d7f571
No known key found for this signature in database
GPG key ID: 87C1E9888F856759

View file

@ -245,22 +245,83 @@ in {
rm -f "${cfg.configDir}/ui-lovelace.yaml" rm -f "${cfg.configDir}/ui-lovelace.yaml"
ln -s ${lovelaceConfigFile} "${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}'"; ExecStart = "${package}/bin/hass --config '${cfg.configDir}'";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
User = "hass"; User = "hass";
Group = "hass"; Group = "hass";
Restart = "on-failure"; 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"; ProtectSystem = "strict";
RemoveIPC = true;
ReadWritePaths = let ReadWritePaths = let
# Allow rw access to explicitly configured paths
cfgPath = [ "config" "homeassistant" "allowlist_external_dirs" ]; cfgPath = [ "config" "homeassistant" "allowlist_external_dirs" ];
value = attrByPath cfgPath [] cfg; value = attrByPath cfgPath [] cfg;
allowPaths = if isList value then value else singleton value; allowPaths = if isList value then value else singleton value;
in [ "${cfg.configDir}" ] ++ allowPaths; in [ "${cfg.configDir}" ] ++ allowPaths;
KillSignal = "SIGINT"; RestrictAddressFamilies = [
PrivateTmp = true; "AF_UNIX"
RemoveIPC = true; "AF_INET"
AmbientCapabilities = "cap_net_raw,cap_net_admin+eip"; "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 = [ path = [
"/run/wrappers" # needed for ping "/run/wrappers" # needed for ping
@ -278,7 +339,6 @@ in {
home = cfg.configDir; home = cfg.configDir;
createHome = true; createHome = true;
group = "hass"; group = "hass";
extraGroups = [ "dialout" ];
uid = config.ids.uids.hass; uid = config.ids.uids.hass;
}; };