diff --git a/mock/docs/buildroot-lock-schema-1.0.0.json b/mock/docs/buildroot-lock-schema-1.0.0.json index 9a4cdb25d..a8cd03a32 100644 --- a/mock/docs/buildroot-lock-schema-1.0.0.json +++ b/mock/docs/buildroot-lock-schema-1.0.0.json @@ -83,7 +83,7 @@ "additionalProperties": false, "properties": { "image_digest": { - "description": "Digest got by the 'podman image inspect --format {{ .Digest }}' command, sha256 string", + "description": "SHA256 digest concatenated RootFS layer digests and Config section from 'podman image inspect' command, sha256 string", "type": "string" } } diff --git a/mock/py/mockbuild/buildroot.py b/mock/py/mockbuild/buildroot.py index 6633d0b8c..29e981f64 100644 --- a/mock/py/mockbuild/buildroot.py +++ b/mock/py/mockbuild/buildroot.py @@ -281,13 +281,13 @@ def _fallback(message): self.chroot_image, podman.image_id) podman.tag_image() - digest_expected = self.image_assert_digest + digest_expected = self.config.get("image_assert_digest", True) if digest_expected: getLog().info("Checking image digest: %s", digest_expected) - digest = podman.get_image_digest() + digest = podman.get_layers_digest() if digest != digest_expected: - getLog().warning( + raise BootstrapError( f"Expected digest for image {podman.image} is" f"{digest_expected}, but {digest} found.") diff --git a/mock/py/mockbuild/podman.py b/mock/py/mockbuild/podman.py index 100c920bd..0d5f80463 100644 --- a/mock/py/mockbuild/podman.py +++ b/mock/py/mockbuild/podman.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # vim: noai:ts=4:sw=4:expandtab +import hashlib +import json import os import logging import subprocess @@ -132,26 +134,38 @@ def mounted_image(self): subprocess.run(cmd_umount, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) - def get_image_digest(self): + def get_layers_digest(self): """ - Get the "sha256:..." string for the image we work with. + Get sha256 digest of RootFS layers. This must be identical for + all images containing same order of layers, thus it can be used + as the check that we've loaded same image. """ - the_image = self.image - if the_image.startswith("oci-archive:"): - # We can't query digest from tarball directly, but note - # the image needs to be tagged first! - the_image = self._tagged_id - check = [self.podman_binary, "image", "inspect", the_image, - "--format", "{{ .Digest }}"] + check = [self.podman_binary, "image", "inspect", self.image] result = subprocess.run(check, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False, encoding="utf8") if result.returncode: - raise PodmanError(f"Can't get {the_image} podman image digest: {result.stderr}") + raise PodmanError(f"Can't get {self.image} podman image digest: {result.stderr}") result = result.stdout.strip() - if len(result.splitlines()) != 1: - raise PodmanError(f"The digest of {the_image} image is not a single-line string") - return result + + try: + data = json.loads(result)[0] + except json.JSONDecodeError as exc: + raise PodmanError(f"The manifest data of {self.image} " + "are not json-formatted.") from exc + if 'RootFS' not in data: + raise PodmanError(f"RootFS section of {self.image} is missing.") + if data['RootFS']['Type'] != 'layers': + raise PodmanError(f"Unexpected format for RootFS in {self.image}.") + + # data which should be sufficient to confirm the image + data = { + 'RootFS': data['RootFS'], + 'Config': data['Config'], + } + # convert to json string with ordered dicts and create hash + data = json.dumps(data, sort_keys=True) + return hashlib.sha256(data.encode()).hexdigest() def check_native_image_architecture(self): """ diff --git a/mock/tests/test_buildroot_lock.py b/mock/tests/test_buildroot_lock.py index 9a89ee3b2..4e5eef046 100644 --- a/mock/tests/test_buildroot_lock.py +++ b/mock/tests/test_buildroot_lock.py @@ -99,7 +99,7 @@ def _call_method(plugins, buildroot): _, method = plugins.add_hook.call_args[0] podman_obj = MagicMock() - podman_obj.get_image_digest.return_value = EXPECTED_OUTPUT["bootstrap"]["image_digest"] + podman_obj.get_layers_digest.return_value = EXPECTED_OUTPUT["bootstrap"]["image_digest"] podman_cls = MagicMock(return_value=podman_obj) with patch("mockbuild.plugins.buildroot_lock.Podman", side_effect=podman_cls): method() diff --git a/releng/release-notes-next/podman-digests.feature b/releng/release-notes-next/podman-digests.feature new file mode 100644 index 000000000..de84d3814 --- /dev/null +++ b/releng/release-notes-next/podman-digests.feature @@ -0,0 +1,2 @@ +Hermetic build process is enhanced by adding used imaged digests into the +metadata and confirming that exactly same image is used in the next step.