fix(digests): do not mandate sha256 as the only algorithm used for hashing blobs (#2075)

Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
This commit is contained in:
Andrei Aaron
2024-07-19 19:56:31 +03:00
committed by GitHub
parent 6421d8b49a
commit 26be383aae
15 changed files with 530 additions and 129 deletions
+42 -15
View File
@@ -88,9 +88,10 @@ type ManifestBuilder interface {
}
type Image struct {
Manifest ispec.Manifest
Config ispec.Image
Layers [][]byte
Manifest ispec.Manifest
Config ispec.Image
Layers [][]byte
digestAlgorithm godigest.Algorithm
ConfigDescriptor ispec.Descriptor
ManifestDescriptor ispec.Descriptor
@@ -108,13 +109,28 @@ func (img *Image) Digest() godigest.Digest {
panic("unreachable: ispec.Manifest should always be marshable")
}
return godigest.FromBytes(blob)
digestAlgorithm := img.digestAlgorithm
if digestAlgorithm == "" {
digestAlgorithm = godigest.Canonical
}
return digestAlgorithm.FromBytes(blob)
}
func (img *Image) DigestStr() string {
return img.Digest().String()
}
func (img *Image) DigestForAlgorithm(digestAlgorithm godigest.Algorithm) godigest.Digest {
blob, err := json.Marshal(img.Manifest)
if err != nil {
panic("unreachable: ispec.Manifest should always be marshable")
}
return digestAlgorithm.FromBytes(blob)
}
func (img *Image) Size() int {
size := img.ConfigDescriptor.Size + img.ManifestDescriptor.Size
@@ -167,7 +183,15 @@ type Layer struct {
// specifying the layers of the image.
func CreateImageWith() LayerBuilder {
// set default values here
return &BaseImageBuilder{}
return &BaseImageBuilder{
digestAlgorithm: godigest.Canonical,
}
}
func CreateImageWithDigestAlgorithm(digestAlgorithm godigest.Algorithm) LayerBuilder {
return &BaseImageBuilder{
digestAlgorithm: digestAlgorithm,
}
}
func CreateDefaultImage() Image {
@@ -223,6 +247,8 @@ type BaseImageBuilder struct {
annotations map[string]string
subject *ispec.Descriptor
artifactType string
digestAlgorithm godigest.Algorithm
}
func (ib *BaseImageBuilder) Layers(layers []Layer) ConfigBuilder {
@@ -236,7 +262,7 @@ func (ib *BaseImageBuilder) LayerBlobs(layers [][]byte) ConfigBuilder {
ib.layers = append(ib.layers, Layer{
Blob: layer,
MediaType: ispec.MediaTypeImageLayerGzip,
Digest: godigest.FromBytes(layer),
Digest: ib.digestAlgorithm.FromBytes(layer),
})
}
@@ -267,7 +293,7 @@ func (ib *BaseImageBuilder) RandomLayers(count, size int) ConfigBuilder {
ib.layers = append(ib.layers, Layer{
Blob: layer,
MediaType: ispec.MediaTypeImageLayerGzip,
Digest: godigest.FromBytes(layer),
Digest: ib.digestAlgorithm.FromBytes(layer),
})
}
@@ -290,7 +316,7 @@ func (ib *BaseImageBuilder) VulnerableLayers() VulnerableConfigBuilder {
{
Blob: layer,
MediaType: ispec.MediaTypeImageLayerGzip,
Digest: godigest.FromBytes(layer),
Digest: ib.digestAlgorithm.FromBytes(layer),
},
}
@@ -309,7 +335,7 @@ func (ib *BaseImageBuilder) ImageConfig(config ispec.Image) ManifestBuilder {
MediaType: ispec.MediaTypeImageConfig,
Size: int64(len(configBlob)),
Data: configBlob,
Digest: godigest.FromBytes(configBlob),
Digest: ib.digestAlgorithm.FromBytes(configBlob),
}
return ib
@@ -351,7 +377,7 @@ func (ib *BaseImageBuilder) CustomConfigBlob(configBlob []byte, mediaType string
MediaType: mediaType,
Size: int64(len(configBlob)),
Data: configBlob,
Digest: godigest.FromBytes(configBlob),
Digest: ib.digestAlgorithm.FromBytes(configBlob),
}
return ib
@@ -372,7 +398,7 @@ func (ib *BaseImageBuilder) RandomConfig() ManifestBuilder {
ib.configDescriptor = ispec.Descriptor{
MediaType: ispec.MediaTypeImageConfig,
Digest: godigest.FromBytes(configBlob),
Digest: ib.digestAlgorithm.FromBytes(configBlob),
Size: int64(len(configBlob)),
Data: configBlob,
}
@@ -390,7 +416,7 @@ func (ib *BaseImageBuilder) DefaultVulnConfig() ManifestBuilder {
vulnConfigDescriptor := ispec.Descriptor{
MediaType: ispec.MediaTypeImageConfig,
Digest: godigest.FromBytes(configBlob),
Digest: ib.digestAlgorithm.FromBytes(configBlob),
Size: int64(len(configBlob)),
Data: configBlob,
}
@@ -421,7 +447,7 @@ func (ib *BaseImageBuilder) VulnerableConfig(config ispec.Image) ManifestBuilder
vulnConfigDescriptor := ispec.Descriptor{
MediaType: ispec.MediaTypeImageConfig,
Digest: godigest.FromBytes(configBlob),
Digest: ib.digestAlgorithm.FromBytes(configBlob),
Size: int64(len(configBlob)),
Data: configBlob,
}
@@ -446,7 +472,7 @@ func (ib *BaseImageBuilder) RandomVulnConfig() ManifestBuilder {
vulnConfigDescriptor := ispec.Descriptor{
MediaType: ispec.MediaTypeImageConfig,
Digest: godigest.FromBytes(configBlob),
Digest: ib.digestAlgorithm.FromBytes(configBlob),
Size: int64(len(configBlob)),
Data: configBlob,
}
@@ -493,6 +519,7 @@ func (ib *BaseImageBuilder) Build() Image {
Subject: ib.subject,
Annotations: ib.annotations,
},
digestAlgorithm: ib.digestAlgorithm,
}
manifestBlob, err := json.Marshal(img.Manifest)
@@ -502,7 +529,7 @@ func (ib *BaseImageBuilder) Build() Image {
img.ManifestDescriptor = ispec.Descriptor{
MediaType: ispec.MediaTypeImageManifest,
Digest: godigest.FromBytes(manifestBlob),
Digest: ib.digestAlgorithm.FromBytes(manifestBlob),
Size: int64(len(manifestBlob)),
Data: manifestBlob,
}
+37 -11
View File
@@ -11,8 +11,9 @@ import (
)
type MultiarchImage struct {
Index ispec.Index
Images []Image
Index ispec.Index
Images []Image
digestAlgorithm godigest.Algorithm
IndexDescriptor ispec.Descriptor
}
@@ -23,13 +24,28 @@ func (mi *MultiarchImage) Digest() godigest.Digest {
panic("unreachable: ispec.Index should always be marshable")
}
return godigest.FromBytes(indexBlob)
digestAlgorithm := mi.digestAlgorithm
if digestAlgorithm == "" {
digestAlgorithm = godigest.Canonical
}
return digestAlgorithm.FromBytes(indexBlob)
}
func (mi *MultiarchImage) DigestStr() string {
return mi.Digest().String()
}
func (mi *MultiarchImage) DigestForAlgorithm(digestAlgorithm godigest.Algorithm) godigest.Digest {
blob, err := json.Marshal(mi.Index)
if err != nil {
panic("unreachable: ispec.Index should always be marshable")
}
return digestAlgorithm.FromBytes(blob)
}
func (mi MultiarchImage) AsImageMeta() mTypes.ImageMeta {
index := mi.Index
@@ -61,7 +77,15 @@ type MultiarchBuilder interface {
}
func CreateMultiarchWith() ImagesBuilder {
return &BaseMultiarchBuilder{}
return &BaseMultiarchBuilder{
digestAlgorithm: godigest.Canonical,
}
}
func CreateMultiarchWithDigestAlgorithm(digestAlgorithm godigest.Algorithm) ImagesBuilder {
return &BaseMultiarchBuilder{
digestAlgorithm: digestAlgorithm,
}
}
func CreateRandomMultiarch() MultiarchImage {
@@ -85,10 +109,11 @@ func CreateVulnerableMultiarch() MultiarchImage {
}
type BaseMultiarchBuilder struct {
images []Image
subject *ispec.Descriptor
artifactType string
annotations map[string]string
images []Image
subject *ispec.Descriptor
artifactType string
annotations map[string]string
digestAlgorithm godigest.Algorithm
}
func (mb *BaseMultiarchBuilder) Images(images []Image) MultiarchBuilder {
@@ -154,11 +179,12 @@ func (mb *BaseMultiarchBuilder) Build() MultiarchImage {
panic("unreachable: ispec.Index should always be marshable")
}
indexDigest := godigest.FromBytes(indexBlob)
indexDigest := mb.digestAlgorithm.FromBytes(indexBlob)
return MultiarchImage{
Index: index,
Images: mb.images,
Index: index,
Images: mb.images,
digestAlgorithm: mb.digestAlgorithm,
IndexDescriptor: ispec.Descriptor{
MediaType: ispec.MediaTypeImageIndex,
+16 -8
View File
@@ -21,6 +21,12 @@ var (
)
func UploadImage(img Image, baseURL, repo, ref string) error {
digestAlgorithm := img.digestAlgorithm
if digestAlgorithm == "" {
digestAlgorithm = godigest.Canonical
}
for _, blob := range img.Layers {
resp, err := resty.R().Post(baseURL + "/v2/" + repo + "/blobs/uploads/")
if err != nil {
@@ -33,7 +39,7 @@ func UploadImage(img Image, baseURL, repo, ref string) error {
loc := resp.Header().Get("Location")
digest := godigest.FromBytes(blob).String()
digest := digestAlgorithm.FromBytes(blob).String()
resp, err = resty.R().
SetHeader("Content-Length", fmt.Sprintf("%d", len(blob))).
@@ -63,7 +69,7 @@ func UploadImage(img Image, baseURL, repo, ref string) error {
}
}
cdigest := godigest.FromBytes(cblob)
cdigest := digestAlgorithm.FromBytes(cblob)
if img.Manifest.Config.MediaType == ispec.MediaTypeEmptyJSON ||
img.Manifest.Config.Digest == ispec.DescriptorEmptyJSON.Digest {
@@ -117,14 +123,16 @@ func UploadImage(img Image, baseURL, repo, ref string) error {
return ErrPutBlob
}
if inject.ErrStatusCode(resp.StatusCode()) != http.StatusCreated {
return ErrPutBlob
}
return err
}
func UploadImageWithBasicAuth(img Image, baseURL, repo, ref, user, password string) error {
digestAlgorithm := img.digestAlgorithm
if digestAlgorithm == "" {
digestAlgorithm = godigest.Canonical
}
for _, blob := range img.Layers {
resp, err := resty.R().
SetBasicAuth(user, password).
@@ -139,7 +147,7 @@ func UploadImageWithBasicAuth(img Image, baseURL, repo, ref, user, password stri
loc := resp.Header().Get("Location")
digest := godigest.FromBytes(blob).String()
digest := digestAlgorithm.FromBytes(blob).String()
resp, err = resty.R().
SetBasicAuth(user, password).
@@ -163,7 +171,7 @@ func UploadImageWithBasicAuth(img Image, baseURL, repo, ref, user, password stri
return err
}
cdigest := godigest.FromBytes(cblob)
cdigest := digestAlgorithm.FromBytes(cblob)
if img.Manifest.Config.MediaType == ispec.MediaTypeEmptyJSON {
cblob = ispec.DescriptorEmptyJSON.Data
+8 -2
View File
@@ -18,9 +18,15 @@ func WriteImageToFileSystem(image Image, repoName, ref string, storeController s
return err
}
digestAlgorithm := image.digestAlgorithm
if digestAlgorithm == "" {
digestAlgorithm = godigest.Canonical
}
for _, layerBlob := range image.Layers {
layerReader := bytes.NewReader(layerBlob)
layerDigest := godigest.FromBytes(layerBlob)
layerDigest := digestAlgorithm.FromBytes(layerBlob)
_, _, err = store.FullBlobUpload(repoName, layerReader, layerDigest)
if err != nil {
@@ -34,7 +40,7 @@ func WriteImageToFileSystem(image Image, repoName, ref string, storeController s
}
configReader := bytes.NewReader(configBlob)
configDigest := godigest.FromBytes(configBlob)
configDigest := digestAlgorithm.FromBytes(configBlob)
_, _, err = store.FullBlobUpload(repoName, configReader, configDigest)
if err != nil {