From 528fd6394eb4c322c13f412c5981f89cd686e91e Mon Sep 17 00:00:00 2001 From: Lokesh Mandvekar Date: Thu, 20 Nov 2025 15:58:38 -0500 Subject: [PATCH] image/storage: allow building container images with `--digest` Allow the user to specify non-Canonical digest algorithm via `supporteddigests.TmpDigestForNewObjects()` instead of hardcoded `digest.Canonical` references. Without --digest or with --digest=sha256, behavior remains unchanged (SHA256 is the default). Tested with a scratch built image. Signed-off-by: Lokesh Mandvekar --- image/storage/storage_dest.go | 17 +++-- image/storage/storage_dest_test.go | 118 +++++++++++++++++++++++++++-- 2 files changed, 119 insertions(+), 16 deletions(-) diff --git a/image/storage/storage_dest.go b/image/storage/storage_dest.go index 000be3ccd1..22ca87a399 100644 --- a/image/storage/storage_dest.go +++ b/image/storage/storage_dest.go @@ -37,6 +37,7 @@ import ( "go.podman.io/storage/pkg/chunked" "go.podman.io/storage/pkg/chunked/toc" "go.podman.io/storage/pkg/ioutils" + supporteddigests "go.podman.io/storage/pkg/supported-digests" ) var ( @@ -288,7 +289,7 @@ func (s *storageImageDestination) putBlobToPendingFile(stream io.Reader, blobinf } defer decompressed.Close() - diffID := digest.Canonical.Digester() + diffID := supporteddigests.TmpDigestForNewObjects().Digester() // Copy the data to the file. // TODO: This can take quite some time, and should ideally be cancellable using context.Context. _, err = io.Copy(diffID.Hash(), decompressed) @@ -856,7 +857,7 @@ func (s *storageImageDestination) computeID(m manifest.Manifest) (string, error) } // ordinaryImageID is a digest of a config, which is a JSON value. // To avoid the risk of collisions, start the input with @ so that the input is not a valid JSON. - tocImageID := digest.FromString("@With TOC:" + tocIDInput).Encoded() + tocImageID := supporteddigests.TmpDigestForNewObjects().FromString("@With TOC:" + tocIDInput).String() logrus.Debugf("Ordinary storage image ID %s; a layer was looked up by TOC, so using image ID %s", ordinaryImageID, tocImageID) return tocImageID, nil } @@ -1070,7 +1071,7 @@ func layerID(parentID string, trusted trustedLayerIdentityData) string { if parentID == "" && !mustHash { return component } - return digest.Canonical.FromString(parentID + "+" + component).Encoded() + return supporteddigests.TmpDigestForNewObjects().FromString(parentID + "+" + component).String() } // createNewLayer creates a new layer newLayerID for (index, trusted) on top of parentLayer (which may be ""). @@ -1488,13 +1489,13 @@ func (s *storageImageDestination) CommitWithOptions(ctx context.Context, options imgOptions.BigData = append(imgOptions.BigData, storage.ImageBigDataOption{ Key: s.lockProtected.configDigest.String(), Data: v, - Digest: digest.Canonical.FromBytes(v), + Digest: supporteddigests.TmpDigestForNewObjects().FromBytes(v), }) } // Set up to save the options.UnparsedToplevel's manifest if it differs from // the per-platform one, which is saved below. if !bytes.Equal(toplevelManifest, s.manifest) { - manifestDigest, err := manifest.Digest(toplevelManifest) + manifestDigest, err := manifest.DigestWithAlgorithm(toplevelManifest, supporteddigests.TmpDigestForNewObjects()) if err != nil { return fmt.Errorf("digesting top-level manifest: %w", err) } @@ -1530,7 +1531,7 @@ func (s *storageImageDestination) CommitWithOptions(ctx context.Context, options imgOptions.BigData = append(imgOptions.BigData, storage.ImageBigDataOption{ Key: "signatures", Data: s.signatures, - Digest: digest.Canonical.FromBytes(s.signatures), + Digest: supporteddigests.TmpDigestForNewObjects().FromBytes(s.signatures), }) } for instanceDigest, signatures := range s.signatureses { @@ -1541,7 +1542,7 @@ func (s *storageImageDestination) CommitWithOptions(ctx context.Context, options imgOptions.BigData = append(imgOptions.BigData, storage.ImageBigDataOption{ Key: key, Data: signatures, - Digest: digest.Canonical.FromBytes(signatures), + Digest: supporteddigests.TmpDigestForNewObjects().FromBytes(signatures), }) } @@ -1643,7 +1644,7 @@ func (s *storageImageDestination) CommitWithOptions(ctx context.Context, options // PutManifest writes the manifest to the destination. func (s *storageImageDestination) PutManifest(ctx context.Context, manifestBlob []byte, instanceDigest *digest.Digest) error { - digest, err := manifest.Digest(manifestBlob) + digest, err := manifest.DigestWithAlgorithm(manifestBlob, supporteddigests.TmpDigestForNewObjects()) if err != nil { return err } diff --git a/image/storage/storage_dest_test.go b/image/storage/storage_dest_test.go index 93b60e6909..0af67be8a1 100644 --- a/image/storage/storage_dest_test.go +++ b/image/storage/storage_dest_test.go @@ -6,13 +6,18 @@ import ( "github.com/opencontainers/go-digest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + supporteddigests "go.podman.io/storage/pkg/supported-digests" ) func TestLayerID(t *testing.T) { - blobDigest, err := digest.Parse("sha256:0000000000000000000000000000000000000000000000000000000000000000") + blobDigestSHA256, err := digest.Parse("sha256:0000000000000000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + blobDigestSHA512, err := digest.Parse("sha512:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") require.NoError(t, err) for _, c := range []struct { + algorithm digest.Algorithm + blobDigest digest.Digest parentID string identifiedByTOC bool diffID string @@ -20,6 +25,8 @@ func TestLayerID(t *testing.T) { expected string }{ { + algorithm: digest.SHA256, + blobDigest: blobDigestSHA256, parentID: "", identifiedByTOC: false, diffID: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", @@ -27,6 +34,8 @@ func TestLayerID(t *testing.T) { expected: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", }, { + algorithm: digest.SHA256, + blobDigest: blobDigestSHA256, parentID: "", identifiedByTOC: false, diffID: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", @@ -34,48 +43,141 @@ func TestLayerID(t *testing.T) { tocDigest: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", }, { + algorithm: digest.SHA256, + blobDigest: blobDigestSHA256, parentID: "", identifiedByTOC: true, diffID: "", tocDigest: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - expected: "07f60ddaf18a3d1fa18a71bf40f0b9889b473e26555d6fffdfbd72ba6a59469e", + expected: "sha256:07f60ddaf18a3d1fa18a71bf40f0b9889b473e26555d6fffdfbd72ba6a59469e", }, { + algorithm: digest.SHA256, + blobDigest: blobDigestSHA256, parentID: "", identifiedByTOC: true, diffID: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", tocDigest: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - expected: "07f60ddaf18a3d1fa18a71bf40f0b9889b473e26555d6fffdfbd72ba6a59469e", + expected: "sha256:07f60ddaf18a3d1fa18a71bf40f0b9889b473e26555d6fffdfbd72ba6a59469e", }, { + algorithm: digest.SHA256, + blobDigest: blobDigestSHA256, parentID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", identifiedByTOC: false, diffID: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", tocDigest: "", - expected: "76f79efda453922cda1cecb6ec9e7cf9d86ea968c6dd199d4030dd00078a1686", + expected: "sha256:76f79efda453922cda1cecb6ec9e7cf9d86ea968c6dd199d4030dd00078a1686", }, { + algorithm: digest.SHA256, + blobDigest: blobDigestSHA256, parentID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", identifiedByTOC: false, diffID: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", tocDigest: "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", - expected: "76f79efda453922cda1cecb6ec9e7cf9d86ea968c6dd199d4030dd00078a1686", + expected: "sha256:76f79efda453922cda1cecb6ec9e7cf9d86ea968c6dd199d4030dd00078a1686", }, { + algorithm: digest.SHA256, + blobDigest: blobDigestSHA256, parentID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", identifiedByTOC: true, diffID: "", tocDigest: "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", - expected: "468becc3d25ee862f81fd728d229a2b2487cfc9b3e6cf3a4d0af8c3fdde0e7a9", + expected: "sha256:468becc3d25ee862f81fd728d229a2b2487cfc9b3e6cf3a4d0af8c3fdde0e7a9", }, { + algorithm: digest.SHA256, + blobDigest: blobDigestSHA256, parentID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", identifiedByTOC: true, diffID: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", tocDigest: "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", - expected: "468becc3d25ee862f81fd728d229a2b2487cfc9b3e6cf3a4d0af8c3fdde0e7a9", + expected: "sha256:468becc3d25ee862f81fd728d229a2b2487cfc9b3e6cf3a4d0af8c3fdde0e7a9", + }, + { + algorithm: digest.SHA512, + blobDigest: blobDigestSHA512, + parentID: "", + identifiedByTOC: false, + diffID: "sha512:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + tocDigest: "", + expected: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + }, + { + algorithm: digest.SHA512, + blobDigest: blobDigestSHA512, + parentID: "", + identifiedByTOC: false, + diffID: "sha512:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + tocDigest: "sha512:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + expected: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + }, + { + algorithm: digest.SHA512, + blobDigest: blobDigestSHA512, + parentID: "", + identifiedByTOC: true, + diffID: "", + tocDigest: "sha512:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + expected: "sha512:129fa63234ab81f43f298346bb7159cc1c8fab6d8ae93f3438f8637f5079ea6dec6ea4740eb6bdbcae3ad1eb00af76011341b2f19fe590daf6f64f06e1302f52", + }, + { + algorithm: digest.SHA512, + blobDigest: blobDigestSHA512, + parentID: "", + identifiedByTOC: true, + diffID: "sha512:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + tocDigest: "sha512:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + expected: "sha512:129fa63234ab81f43f298346bb7159cc1c8fab6d8ae93f3438f8637f5079ea6dec6ea4740eb6bdbcae3ad1eb00af76011341b2f19fe590daf6f64f06e1302f52", + }, + { + algorithm: digest.SHA512, + blobDigest: blobDigestSHA512, + parentID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + identifiedByTOC: false, + diffID: "sha512:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + tocDigest: "", + expected: "sha512:391623fa853a82d2795e8648f06b7c0eb880e345138dd6b9429132d9aa329ff1e6f7b76a3295b8b34d8682090056a798901b9b700e4059b1011d32b68a7bca21", + }, + { + algorithm: digest.SHA512, + blobDigest: blobDigestSHA512, + parentID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + identifiedByTOC: false, + diffID: "sha512:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + tocDigest: "sha512:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + expected: "sha512:391623fa853a82d2795e8648f06b7c0eb880e345138dd6b9429132d9aa329ff1e6f7b76a3295b8b34d8682090056a798901b9b700e4059b1011d32b68a7bca21", + }, + { + algorithm: digest.SHA512, + blobDigest: blobDigestSHA512, + parentID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + identifiedByTOC: true, + diffID: "", + tocDigest: "sha512:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + expected: "sha512:0263be0fd3b851ffe02c877574d1d2b19625e36888e63eec2682de2210b6bbaea262e04085843eda16b7014fd59db6aafa0628c2a02443111ae568ea545b112e", + }, + { + algorithm: digest.SHA512, + blobDigest: blobDigestSHA512, + parentID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + identifiedByTOC: true, + diffID: "sha512:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + tocDigest: "sha512:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + expected: "sha512:0263be0fd3b851ffe02c877574d1d2b19625e36888e63eec2682de2210b6bbaea262e04085843eda16b7014fd59db6aafa0628c2a02443111ae568ea545b112e", }, } { + origAlg := supporteddigests.TmpDigestForNewObjects() + defer func() { + err := supporteddigests.TmpSetDigestForNewObjects(origAlg) + require.NoError(t, err) + }() + + err := supporteddigests.TmpSetDigestForNewObjects(c.algorithm) + require.NoError(t, err) + var diffID, tocDigest digest.Digest if c.diffID != "" { diffID, err = digest.Parse(c.diffID) @@ -98,7 +200,7 @@ func TestLayerID(t *testing.T) { layerIdentifiedByTOC: c.identifiedByTOC, diffID: diffID, tocDigest: tocDigest, - blobDigest: blobDigest, + blobDigest: c.blobDigest, }) assert.Equal(t, c.expected, res) }