From f30d21361f9a4f2e42a213b982a1622926980028 Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Sun, 3 Sep 2023 12:21:35 -0400 Subject: [PATCH] Skip unnecessary mounts and privileges for landingpage (#4518) * Skip unnecessary mounts for landingpage * Remove privileged and cgroup rules from landingpage --- supervisor/docker/homeassistant.py | 102 +++++++++++--------- tests/docker/test_homeassistant.py | 147 +++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+), 45 deletions(-) create mode 100644 tests/docker/test_homeassistant.py diff --git a/supervisor/docker/homeassistant.py b/supervisor/docker/homeassistant.py index 8f59e3a7d..2f8cafc57 100644 --- a/supervisor/docker/homeassistant.py +++ b/supervisor/docker/homeassistant.py @@ -66,10 +66,14 @@ class DockerHomeAssistant(DockerInterface): def cgroups_rules(self) -> list[str]: """Return a list of needed cgroups permission.""" return ( - self.sys_hardware.policy.get_cgroups_rules(PolicyGroup.UART) - + self.sys_hardware.policy.get_cgroups_rules(PolicyGroup.VIDEO) - + self.sys_hardware.policy.get_cgroups_rules(PolicyGroup.GPIO) - + self.sys_hardware.policy.get_cgroups_rules(PolicyGroup.USB) + [] + if self.sys_homeassistant.version == LANDINGPAGE + else ( + self.sys_hardware.policy.get_cgroups_rules(PolicyGroup.UART) + + self.sys_hardware.policy.get_cgroups_rules(PolicyGroup.VIDEO) + + self.sys_hardware.policy.get_cgroups_rules(PolicyGroup.GPIO) + + self.sys_hardware.policy.get_cgroups_rules(PolicyGroup.USB) + ) ) @property @@ -79,54 +83,62 @@ class DockerHomeAssistant(DockerInterface): MOUNT_DEV, MOUNT_DBUS, MOUNT_UDEV, - # Add folders + # HA config folder Mount( type=MountType.BIND.value, source=self.sys_config.path_extern_homeassistant.as_posix(), target="/config", read_only=False, ), - Mount( - type=MountType.BIND.value, - source=self.sys_config.path_extern_ssl.as_posix(), - target="/ssl", - read_only=True, - ), - Mount( - type=MountType.BIND.value, - source=self.sys_config.path_extern_share.as_posix(), - target="/share", - read_only=False, - propagation=PropagationMode.RSLAVE.value, - ), - Mount( - type=MountType.BIND.value, - source=self.sys_config.path_extern_media.as_posix(), - target="/media", - read_only=False, - propagation=PropagationMode.RSLAVE.value, - ), - # Configuration audio - Mount( - type=MountType.BIND.value, - source=self.sys_homeassistant.path_extern_pulse.as_posix(), - target="/etc/pulse/client.conf", - read_only=True, - ), - Mount( - type=MountType.BIND.value, - source=self.sys_plugins.audio.path_extern_pulse.as_posix(), - target="/run/audio", - read_only=True, - ), - Mount( - type=MountType.BIND.value, - source=self.sys_plugins.audio.path_extern_asound.as_posix(), - target="/etc/asound.conf", - read_only=True, - ), ] + # Landingpage does not need all this access + if self.sys_homeassistant.version != LANDINGPAGE: + mounts.extend( + [ + # All other folders + Mount( + type=MountType.BIND.value, + source=self.sys_config.path_extern_ssl.as_posix(), + target="/ssl", + read_only=True, + ), + Mount( + type=MountType.BIND.value, + source=self.sys_config.path_extern_share.as_posix(), + target="/share", + read_only=False, + propagation=PropagationMode.RSLAVE.value, + ), + Mount( + type=MountType.BIND.value, + source=self.sys_config.path_extern_media.as_posix(), + target="/media", + read_only=False, + propagation=PropagationMode.RSLAVE.value, + ), + # Configuration audio + Mount( + type=MountType.BIND.value, + source=self.sys_homeassistant.path_extern_pulse.as_posix(), + target="/etc/pulse/client.conf", + read_only=True, + ), + Mount( + type=MountType.BIND.value, + source=self.sys_plugins.audio.path_extern_pulse.as_posix(), + target="/run/audio", + read_only=True, + ), + Mount( + type=MountType.BIND.value, + source=self.sys_plugins.audio.path_extern_asound.as_posix(), + target="/etc/asound.conf", + read_only=True, + ), + ] + ) + # Machine ID if MACHINE_ID.exists(): mounts.append(MOUNT_MACHINE_ID) @@ -154,7 +166,7 @@ class DockerHomeAssistant(DockerInterface): name=self.name, hostname=self.name, detach=True, - privileged=True, + privileged=self.sys_homeassistant.version != LANDINGPAGE, init=False, security_opt=self.security_opt, network_mode="host", diff --git a/tests/docker/test_homeassistant.py b/tests/docker/test_homeassistant.py new file mode 100644 index 000000000..6565a5a37 --- /dev/null +++ b/tests/docker/test_homeassistant.py @@ -0,0 +1,147 @@ +"""Test Home Assistant container.""" + +from ipaddress import IPv4Address +from pathlib import Path +from unittest.mock import ANY, patch + +from awesomeversion import AwesomeVersion +from docker.types import Mount + +from supervisor.coresys import CoreSys +from supervisor.docker.homeassistant import DockerHomeAssistant +from supervisor.docker.manager import DockerAPI +from supervisor.homeassistant.const import LANDINGPAGE + + +async def test_homeassistant_start( + coresys: CoreSys, tmp_supervisor_data: Path, path_extern +): + """Test starting homeassistant.""" + coresys.homeassistant.version = AwesomeVersion("2023.8.1") + + with patch.object(DockerAPI, "run") as run, patch.object( + DockerHomeAssistant, "is_running", side_effect=[False, False, True] + ), patch("supervisor.homeassistant.core.asyncio.sleep"): + await coresys.homeassistant.core.start() + + run.assert_called_once() + assert run.call_args.kwargs["name"] == "homeassistant" + assert run.call_args.kwargs["hostname"] == "homeassistant" + assert run.call_args.kwargs["privileged"] is True + assert run.call_args.kwargs["oom_score_adj"] == -300 + assert run.call_args.kwargs["device_cgroup_rules"] + assert run.call_args.kwargs["extra_hosts"] == { + "supervisor": IPv4Address("172.30.32.2"), + "observer": IPv4Address("172.30.32.6"), + } + assert run.call_args.kwargs["environment"] == { + "SUPERVISOR": IPv4Address("172.30.32.2"), + "HASSIO": IPv4Address("172.30.32.2"), + "TZ": ANY, + "SUPERVISOR_TOKEN": ANY, + "HASSIO_TOKEN": ANY, + } + assert run.call_args.kwargs["mounts"] == [ + Mount(type="bind", source="/dev", target="/dev", read_only=True), + Mount(type="bind", source="/run/dbus", target="/run/dbus", read_only=True), + Mount(type="bind", source="/run/udev", target="/run/udev", read_only=True), + Mount( + type="bind", + source=coresys.config.path_extern_homeassistant.as_posix(), + target="/config", + read_only=False, + ), + Mount( + type="bind", + source=coresys.config.path_extern_ssl.as_posix(), + target="/ssl", + read_only=True, + ), + Mount( + type="bind", + source=coresys.config.path_extern_share.as_posix(), + target="/share", + read_only=False, + propagation="rslave", + ), + Mount( + type="bind", + source=coresys.config.path_extern_media.as_posix(), + target="/media", + read_only=False, + propagation="rslave", + ), + Mount( + type="bind", + source=coresys.homeassistant.path_extern_pulse.as_posix(), + target="/etc/pulse/client.conf", + read_only=True, + ), + Mount( + type="bind", + source=coresys.plugins.audio.path_extern_pulse.as_posix(), + target="/run/audio", + read_only=True, + ), + Mount( + type="bind", + source=coresys.plugins.audio.path_extern_asound.as_posix(), + target="/etc/asound.conf", + read_only=True, + ), + Mount( + type="bind", + source="/etc/machine-id", + target="/etc/machine-id", + read_only=True, + ), + ] + assert "volumes" not in run.call_args.kwargs + + +async def test_landingpage_start( + coresys: CoreSys, tmp_supervisor_data: Path, path_extern +): + """Test starting landingpage.""" + coresys.homeassistant.version = LANDINGPAGE + + with patch.object(DockerAPI, "run") as run, patch.object( + DockerHomeAssistant, "is_running", return_value=False + ): + await coresys.homeassistant.core.start() + + run.assert_called_once() + assert run.call_args.kwargs["name"] == "homeassistant" + assert run.call_args.kwargs["hostname"] == "homeassistant" + assert run.call_args.kwargs["privileged"] is False + assert run.call_args.kwargs["oom_score_adj"] == -300 + assert not run.call_args.kwargs["device_cgroup_rules"] + assert run.call_args.kwargs["extra_hosts"] == { + "supervisor": IPv4Address("172.30.32.2"), + "observer": IPv4Address("172.30.32.6"), + } + assert run.call_args.kwargs["environment"] == { + "SUPERVISOR": IPv4Address("172.30.32.2"), + "HASSIO": IPv4Address("172.30.32.2"), + "TZ": ANY, + "SUPERVISOR_TOKEN": ANY, + "HASSIO_TOKEN": ANY, + } + assert run.call_args.kwargs["mounts"] == [ + Mount(type="bind", source="/dev", target="/dev", read_only=True), + Mount(type="bind", source="/run/dbus", target="/run/dbus", read_only=True), + Mount(type="bind", source="/run/udev", target="/run/udev", read_only=True), + Mount( + type="bind", + source=coresys.config.path_extern_homeassistant.as_posix(), + target="/config", + read_only=False, + ), + Mount( + type="bind", + source="/etc/machine-id", + target="/etc/machine-id", + read_only=True, + ), + ] + assert "volumes" not in run.call_args.kwargs