From 3615091c933bad381d11f5b7d2fbdf1514ac3bfe Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 6 Apr 2021 23:41:57 +0200 Subject: [PATCH] Evaluate AppArmor support (#2784) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Evaluate AppArmor support * Update supervisor/resolution/evaluations/apparmor.py Co-authored-by: Joakim Sørensen Co-authored-by: Joakim Sørensen --- supervisor/resolution/const.py | 1 + supervisor/resolution/evaluations/apparmor.py | 41 +++++++++++++++ .../evaluation/test_evaluate_apparmor.py | 52 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 supervisor/resolution/evaluations/apparmor.py create mode 100644 tests/resolution/evaluation/test_evaluate_apparmor.py diff --git a/supervisor/resolution/const.py b/supervisor/resolution/const.py index cb85a1ea5..149057ff6 100644 --- a/supervisor/resolution/const.py +++ b/supervisor/resolution/const.py @@ -29,6 +29,7 @@ class UnsupportedReason(str, Enum): CONTAINER = "container" DBUS = "dbus" + APPARMOR = "apparmor" DOCKER_CONFIGURATION = "docker_configuration" DOCKER_VERSION = "docker_version" LXC = "lxc" diff --git a/supervisor/resolution/evaluations/apparmor.py b/supervisor/resolution/evaluations/apparmor.py new file mode 100644 index 000000000..86e58b62d --- /dev/null +++ b/supervisor/resolution/evaluations/apparmor.py @@ -0,0 +1,41 @@ +"""Evaluation class for AppArmor.""" +from pathlib import Path +from typing import List + +from ...const import CoreState +from ...coresys import CoreSys +from ..const import UnsupportedReason +from .base import EvaluateBase + +_APPARMOR_KERNEL = Path("/sys/module/apparmor/parameters/enabled") + + +def setup(coresys: CoreSys) -> EvaluateBase: + """Initialize evaluation-setup function.""" + return EvaluateAppArmor(coresys) + + +class EvaluateAppArmor(EvaluateBase): + """Evaluate is supported/enabled AppArmor.""" + + @property + def reason(self) -> UnsupportedReason: + """Return a UnsupportedReason enum.""" + return UnsupportedReason.APPARMOR + + @property + def on_failure(self) -> str: + """Return a string that is printed when self.evaluate is False.""" + return "AppArmor is required for Home Assistant." + + @property + def states(self) -> List[CoreState]: + """Return a list of valid states when this evaluation can run.""" + return [CoreState.INITIALIZE] + + async def evaluate(self) -> None: + """Run evaluation.""" + try: + return _APPARMOR_KERNEL.read_text().strip().upper() != "Y" + except OSError: + return True diff --git a/tests/resolution/evaluation/test_evaluate_apparmor.py b/tests/resolution/evaluation/test_evaluate_apparmor.py new file mode 100644 index 000000000..c0d961767 --- /dev/null +++ b/tests/resolution/evaluation/test_evaluate_apparmor.py @@ -0,0 +1,52 @@ +"""Test evaluation base.""" +# pylint: disable=import-error,protected-access +from unittest.mock import patch + +from supervisor.const import CoreState +from supervisor.coresys import CoreSys +from supervisor.resolution.evaluations.apparmor import EvaluateAppArmor + + +async def test_evaluation(coresys: CoreSys): + """Test evaluation.""" + apparmor = EvaluateAppArmor(coresys) + coresys.core.state = CoreState.INITIALIZE + + assert apparmor.reason not in coresys.resolution.unsupported + + with patch("pathlib.Path.read_text", return_value="N"): + await apparmor() + assert apparmor.reason in coresys.resolution.unsupported + + with patch("pathlib.Path.read_text", return_value="Y"): + await apparmor() + assert apparmor.reason not in coresys.resolution.unsupported + + with patch("pathlib.Path.read_text", side_effect=OSError): + await apparmor() + assert apparmor.reason in coresys.resolution.unsupported + + +async def test_did_run(coresys: CoreSys): + """Test that the evaluation ran as expected.""" + apparmor = EvaluateAppArmor(coresys) + should_run = apparmor.states + should_not_run = [state for state in CoreState if state not in should_run] + assert len(should_run) != 0 + assert len(should_not_run) != 0 + + with patch( + "supervisor.resolution.evaluations.apparmor.EvaluateAppArmor.evaluate", + return_value=None, + ) as evaluate: + for state in should_run: + coresys.core.state = state + await apparmor() + evaluate.assert_called_once() + evaluate.reset_mock() + + for state in should_not_run: + coresys.core.state = state + await apparmor() + evaluate.assert_not_called() + evaluate.reset_mock()