Beispiel #1
0
    async def verify_image_integrity(self, image_name: ImageName, **kwargs) -> Dict:
        data = await self._verify_image_config(image_name)

        # Note: We do not need to reconcile manifest layer ids here, as "we" derived them in
        # :func:docker_sign_verify.manifests.DeviceMapperRepositoryManifest.get_layers.

        # Reconcile manifest layers and image layers (in order)...
        uncompressed_layer_files = []
        try:
            for i, layer in enumerate(data.manifest_layers):
                # Retrieve the repository image layer and verify the digest ...
                uncompressed_layer_files.append(tempfile.NamedTemporaryFile())
                data_compressed = await self.get_image_layer_to_disk(
                    image_name, layer, uncompressed_layer_files[i]
                )
                must_be_equal(
                    data.image_layers[i],
                    data_compressed.digest,
                    f"Repository layer[{i}] digest mismatch",
                )
        except Exception:
            # for file in uncompressed_layer_files:
            #     file.close()
            raise

        LOGGER.debug("Integrity check passed.")

        return {
            "compressed_layer_files": "TODO",
            "image_config": data.image_config,
            "manifest": data.manifest,
            "uncompressed_layer_files": uncompressed_layer_files,
        }
    async def verify_image_integrity(self, image_name: ImageName,
                                     **kwargs) -> Dict:
        data = await self._verify_image_config(image_name)

        # Reconcile manifest layers and image layers (in order)...
        uncompressed_layer_files = []
        for i, layer in enumerate(data["manifest_layers"]):
            # Retrieve the archive image layer and verify the digest ...
            uncompressed_layer_files.append(tempfile.NamedTemporaryFile())
            data_uncompressed = await self.get_image_layer_to_disk(
                image_name, layer, uncompressed_layer_files[i])
            must_be_equal(
                data["image_layers"][i],
                data_uncompressed["digest"],
                "Archive layer[{0}] digest mismatch".format(i),
            )

        LOGGER.debug("Integrity check passed.")

        return {
            "compressed_layer_files": "TODO",
            "image_config": data["image_config"],
            "manifest": data["manifest"],
            "uncompressed_layer_files": uncompressed_layer_files,
        }
    async def verify_image_integrity(
            self, image_name: ImageName,
            **kwargs) -> ImageSourceVerifyImageIntegrity:
        data = await self._verify_image_config(image_name, **kwargs)

        # Reconcile manifest layers and image layers (in order)...
        uncompressed_layer_files = []
        try:
            for i, layer in enumerate(data.manifest_layers):
                # Retrieve the archive image layer and verify the digest ...
                uncompressed_layer_files.append(
                    await aiotempfile(prefix="tmp-uncompressed"))
                data_uncompressed = await self.get_image_layer_to_disk(
                    image_name, layer, uncompressed_layer_files[i])
                must_be_equal(
                    data.image_layers[i],
                    data_uncompressed.digest,
                    f"Archive layer[{i}] digest mismatch",
                )
        except Exception:
            for file in uncompressed_layer_files:
                file.close()
            raise

        LOGGER.debug("Integrity check passed.")

        return ImageSourceVerifyImageIntegrity(
            compressed_layer_files=[],  # TODO: Implement this
            image_config=data.image_config,
            manifest=data.manifest,
            uncompressed_layer_files=uncompressed_layer_files,
        )
Beispiel #4
0
def test_must_be_equal_msg(expected: Any = "bar", actual: Any = "foo"):
    """Test that an custom error message can be used."""
    message = "custom message here"
    with pytest.raises(RuntimeError) as exc_info:
        must_be_equal(expected, actual, message)
    assert str(expected) in str(exc_info.value)
    assert str(actual) in str(exc_info.value)
    assert message in str(exc_info.value)
Beispiel #5
0
def test_must_be_equal(expected: Any, actual: Any, result: bool):
    """Test that equality can be determined."""
    if not result:
        with pytest.raises(RuntimeError) as exc_info:
            must_be_equal(expected, actual)
        assert str(expected) in str(exc_info.value)
        assert str(actual) in str(exc_info.value)
        assert "does not match" in str(exc_info.value)
    else:
        must_be_equal(expected, actual)
Beispiel #6
0
    async def _verify_image_config(
        self, image_name: ImageName, **kwargs
    ) -> ImageSourceVerifyImageConfig:
        """
        Verifies the integration of an image configuration against metadata contained within a manifest.

        Args:
            image_name: The image name for which to retrieve the configuration.

        Returns:
            NamedTuple:
                image_config: The image configuration.
                image_layers: The listing of image layer identifiers.
                manifest: The image-source specific manifest.
                manifest_layers: The listing of manifest layer identifiers.
        """

        # Retrieve the image configuration digest and layers identifiers from the manifest ...
        LOGGER.debug("Verifying Integrity: %s ...", image_name.resolve_name())
        manifest = await self.get_manifest(image_name, **kwargs)
        LOGGER.debug("    manifest digest: %s", xellipsis(manifest.get_digest()))
        config_digest = manifest.get_config_digest(image_name)
        LOGGER.debug("    config digest: %s", xellipsis(config_digest))
        manifest_layers = manifest.get_layers(image_name)
        LOGGER.debug("    manifest layers:")
        for layer in manifest_layers:
            LOGGER.debug("        %s", xellipsis(layer))

        # Retrieve the image configuration ...
        image_config = await self.get_image_config(image_name, **kwargs)
        config_digest_canonical = image_config.get_digest_canonical()
        LOGGER.debug(
            "    config digest (canonical): %s", xellipsis(config_digest_canonical)
        )
        must_be_equal(
            config_digest,
            image_config.get_digest(),
            "Image config digest mismatch",
        )

        # Retrieve the image layers from the image configuration ...
        image_layers = image_config.get_image_layers()
        LOGGER.debug("    image layers:")
        for layer in image_layers:
            LOGGER.debug("        %s", xellipsis(layer))

        # Quick check: Ensure that the layer counts are consistent
        must_be_equal(len(manifest_layers), len(image_layers), "Layer count mismatch")

        return ImageSourceVerifyImageConfig(
            image_config=image_config,
            image_layers=image_layers,
            manifest=manifest,
            manifest_layers=manifest_layers,
        )
Beispiel #7
0
    async def verify_signatures(
            self,
            *,
            signer_kwargs: Dict[str,
                                Dict] = None) -> ImageConfigVerifySignatures:
        """
        Verifies the PEM encoded signature values in the image configuration.

        Args:
            signer_kwargs: Parameters to be passed to the underlying Signer instance.

        Returns:
            NamedTuple:
                signature_data: List as defined by :func:~docker_sign_verify.ImageConfig.get_signature_list.
                results: Signer-specific result value.
        """
        signatures = self.get_signature_list()
        if not signatures:
            raise NoSignatureError()

        # Assumptions:
        # * The signature list is ordered.
        # * The first entry in the list *must* be a (co-)signature, as there is nothing older to endorse.
        # * Normalization during canonicalization ensures a consistent empty set.

        results = []
        for i, signature_entry in enumerate(signatures):
            _temp = self.clone()
            # (Co-)signature
            if signature_entry.digest == signatures[0].digest:
                _temp.clear_signature_list()
            # Endorsement
            else:
                _temp.set_signature_list(signatures[:i])

            digest = _temp.get_digest_canonical()
            must_be_equal(
                signature_entry.digest,
                digest,
                "Image config canonical digest mismatch",
                error_type=DigestMismatchError,
            )

            signer = Signer.for_signature(signature_entry.signature,
                                          signer_kwargs=signer_kwargs)
            result = await signer.verify(digest.encode("utf-8"),
                                         signature_entry.signature)
            results.append(result)

        return ImageConfigVerifySignatures(results=results,
                                           signatures=signatures)
    async def verify_signatures(self) -> ImageConfigVerifySignatures:
        """
        Verifies the PEM encoded signature values in the image configuration.

        Returns:
            dict:
                signature_data: List as defined by :func:~docker_sign_verify.ImageConfig.get_signature_list.
                results: Signer-specific result value.
        """
        signatures = self.get_signature_list()
        if not signatures:
            raise NoSignatureError()

        # Assumptions:
        # * The signature list is ordered.
        # * The first entry in the list *must* be a (co-)signature, as there is nothing older to endorse.
        # * Normalization during canonicalization ensures a consistent empty set.

        results = []
        for i, signature in enumerate(signatures):
            _temp = deepcopy(self)
            # (Co-)signature
            if signature["digest"] == signatures[0]["digest"]:
                _temp.clear_signature_list()
            # Endorsement
            else:
                _temp.set_signature_list(signatures[:i])

            digest = _temp.get_digest_canonical()
            must_be_equal(
                signature["digest"],
                digest,
                "Image config canonical digest mismatch",
                error_type=DigestMismatchError,
            )

            signer = Signer.for_signature(signature["signature"])
            result = await signer.verify(digest.encode("utf-8"), signature["signature"])
            results.append(result)

        return {"signatures": signatures, "results": results}
    async def verify_image_integrity(
            self, image_name: ImageName,
            **kwargs) -> ImageSourceVerifyImageIntegrity:
        data = await self._verify_image_config(image_name, **kwargs)

        # Reconcile manifest layers and image layers (in order)...
        compressed_layer_files = []
        uncompressed_layer_files = []
        try:
            for i, layer in enumerate(data.manifest_layers):
                # Retrieve the registry image layer and verify the digest ...
                compressed_layer_files.append(
                    await aiotempfile(prefix="tmp-compressed"))
                data_compressed = await self.get_image_layer_to_disk(
                    image_name, layer, compressed_layer_files[i], **kwargs)
                must_be_equal(
                    layer,
                    data_compressed.digest,
                    f"Registry layer[{i}] digest mismatch",
                )
                must_be_equal(
                    os.path.getsize(compressed_layer_files[i].name),
                    data_compressed.size,
                    f"Registry layer[{i}] size mismatch",
                )

                # Decompress (convert) the registry image layer into the image layer
                # and verify the digest ...
                uncompressed_layer_files.append(
                    await aiotempfile(prefix="tmp-uncompressed"))
                data_uncompressed = await gunzip(
                    compressed_layer_files[i].name,
                    uncompressed_layer_files[i])
                must_be_equal(
                    data.image_layers[i],
                    data_uncompressed.digest,
                    f"Image layer[{i}] digest mismatch",
                )
        except Exception:
            for file in compressed_layer_files + uncompressed_layer_files:
                file.close()
            raise

        LOGGER.debug("Integrity check passed.")

        return ImageSourceVerifyImageIntegrity(
            compressed_layer_files=compressed_layer_files,
            image_config=data.image_config,
            manifest=data.manifest,
            uncompressed_layer_files=uncompressed_layer_files,
        )
    async def verify_image_integrity(
            self, image_name: ImageName,
            **kwargs) -> ImageSourceVerifyImageIntegrity:
        data = await self._verify_image_config(image_name, **kwargs)

        # Reconcile manifest layers and image layers (in order)...
        compressed_layer_files = []
        uncompressed_layer_files = []
        for i, layer in enumerate(data["manifest_layers"]):
            # Retrieve the registry image layer and verify the digest ...
            compressed_layer_files.append(await aiotempfile())
            data_compressed = await self.get_image_layer_to_disk(
                image_name, layer, compressed_layer_files[i], **kwargs)
            must_be_equal(
                layer,
                data_compressed["digest"],
                "Registry layer[{0}] digest mismatch".format(i),
            )
            must_be_equal(
                os.path.getsize(compressed_layer_files[i].name),
                data_compressed["size"],
                "Registry layer[{0}] size mismatch".format(i),
            )

            # Decompress (convert) the registry image layer into the image layer
            # and verify the digest ...
            uncompressed_layer_files.append(await aiotempfile())
            data_uncompressed = await gunzip(compressed_layer_files[i].name,
                                             uncompressed_layer_files[i])
            must_be_equal(
                data["image_layers"][i],
                data_uncompressed["digest"],
                "Image layer[{0}] digest mismatch".format(i),
            )

        LOGGER.debug("Integrity check passed.")

        return {
            "compressed_layer_files": compressed_layer_files,
            "image_config": data["image_config"],
            "manifest": data["manifest"],
            "uncompressed_layer_files": uncompressed_layer_files,
        }