]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Make SignExpectedPCR tri-valued 1213/head
authorJoerg Behrmann <behrmann@physik.fu-berlin.de>
Mon, 3 Oct 2022 14:55:57 +0000 (16:55 +0200)
committerJoerg Behrmann <behrmann@physik.fu-berlin.de>
Tue, 4 Oct 2022 12:35:52 +0000 (14:35 +0200)
This makes SignEpectedPCR a tri-valued option using aspecial value "auto" in
addition to boolean values, where it will check whether cryptography is
importable and systemd-measure in the PATH and value True in that case and False
else.

For a True value it checks both conditions and fails hard if they are not met.

The checks are kept in the CLI definition so that what comes out stays a clean
boolean value and doesn't leak any decisions into layers further down. This
unfortunately necesitates a custom default value in the tests, so that they are
robust against what's installed on the system they run on and also needs to use
a function for the argparse type=, since actions are not called for every value.

mkosi.md
mkosi/__init__.py
tests/test_config_parser.py

index 76c29b49238935fd58490dce602b4fd3e0d9bb84..8353e95bb5e137ee700fce7b06cbe6a2f0591f33 100644 (file)
--- a/mkosi.md
+++ b/mkosi.md
@@ -566,10 +566,10 @@ a boolean argument: either "1", "yes", or "true" to enable, or "0",
 
 : Measure the components of the unified kernel image (UKI) using
   `systemd-measure` and embed the PCR signature into the unified kernel
-  image.
-
-  This option requires the [`cryptography`](https://cryptography.io/)
-  module.
+  image. This option takes a boolean value or the special value `auto`,
+  which is the default, which is equal to a true value if the
+  [`cryptography`](https://cryptography.io/) module is importable and
+  the `systemd-measure` binary is in `PATH`.
 
 `CompressFs=`, `--compress-fs=`
 
index 7a61759901c45b897d985cf811b9ebf50cf7ad3d..234a7f206e9690a4ddb9929fddca28048bc20a30 100644 (file)
@@ -3992,14 +3992,8 @@ def install_unified_kernel(
             # systemd-measure binary around, then also include a
             # signature of expected PCR 11 values in the kernel image
             if state.config.secure_boot and state.config.sign_expected_pcr:
-                try:
-                    from cryptography import x509
-                    from cryptography.hazmat.primitives import serialization
-                except ImportError:
-                    die("Couldn't import the cryptography Python module. This is needed for the --sign-expected-pcr option.")
-
-                if not shutil.which('systemd-measure'):
-                    die("Couldn't find systemd-measure binary. It is needed for the --sign-expected-pcr option.")
+                from cryptography import x509
+                from cryptography.hazmat.primitives import serialization
 
                 with complete_step("Generating PCR 11 signature…"):
                     # Extract the public key from the SecureBoot certificate
@@ -4831,6 +4825,52 @@ class VerityAction(BooleanAction):
         super().__call__(parser, namespace, values, option_string)
 
 
+def parse_sign_expected_pcr(value: Union[bool, str]) -> bool:
+    if isinstance(value, bool):
+        return value
+
+    if value == "auto":
+        try:
+            # TODO: pyright stumbles over this with
+            # "import_module" is not a known member of module (reportGeneralTypeIssues)
+            # although importlib.import_module exists in Python 3.7
+            # a regular import trips pyflakes, though and I haven't found a way
+            # to silence that
+            importlib.import_module("cryptography") # type: ignore
+            return True if shutil.which('systemd-measure') else False
+        except ImportError:
+            return False
+
+    val = parse_boolean(value)
+    if val:
+        try:
+            importlib.import_module("cryptography") # type: ignore
+        except ImportError:
+            die("Couldn't import the cryptography Python module. This is needed for the --sign-expected-pcr option.")
+
+        if not shutil.which('systemd-measure'):
+            die("Couldn't find systemd-measure binary. It is needed for the --sign-expected-pcr option.")
+
+    return val
+
+
+class SignExpectedPcrAction(BooleanAction):
+    def __call__(
+        self,
+        parser: argparse.ArgumentParser,
+        namespace: argparse.Namespace,
+        values: Union[str, Sequence[Any], None, bool],
+        option_string: Optional[str] = None,
+    ) -> None:
+        if values is None:
+            parsed = False
+        elif isinstance(values, bool) or isinstance(values, str):
+            parsed = parse_sign_expected_pcr(values)
+        else:
+            raise argparse.ArgumentError(self, f"Invalid argument for {option_string}: {values}")
+        setattr(namespace, self.dest, parsed)
+
+
 class CustomHelpFormatter(argparse.HelpFormatter):
     def _format_action_invocation(self, action: argparse.Action) -> str:
         if not action.option_strings or action.nargs == 0:
@@ -5212,7 +5252,10 @@ def create_parser() -> ArgumentParserMkosi:
     )
     group.add_argument(
         "--sign-expected-pcr",
-        action=BooleanAction,
+        metavar="BOOL",
+        default="auto",
+        action=SignExpectedPcrAction,
+        type=parse_sign_expected_pcr,
         help="Measure the components of the unified kernel image (UKI) and embed the PCR signature into the UKI",
     )
     group.add_argument(
index a3e33f0d7a6118561b9990781779dd74ac9dce7e..68a41a297223bcca80a4364b5f7da2a4556d7a70 100644 (file)
@@ -3,7 +3,9 @@
 import configparser
 import contextlib
 import copy
+import importlib
 import os
+import shutil
 from pathlib import Path
 from typing import Any, Dict, Generator, List, Mapping, Optional
 
@@ -36,6 +38,13 @@ class MkosiConfig:
         self.cli_arguments = []
         self.reference_config = {}
 
+    def local_sign_expected_pcr_default(self) -> bool:
+        try:
+            importlib.import_module("cryptography")
+            return True if shutil.which('systemd-measure') else False
+        except ImportError:
+            return False
+
     def add_reference_config(self, job_name: str = DEFAULT_JOB_NAME) -> None:
         """create one initial reference configuration
 
@@ -128,7 +137,7 @@ class MkosiConfig:
             "bios_size": None,
             "verb": Verb.build,
             "verity": False,
-            "sign_expected_pcr": False,
+            "sign_expected_pcr": self.local_sign_expected_pcr_default(),
             "with_docs": False,
             "with_network": False,
             "with_tests": True,