Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions integration/dockerfiles/Dockerfile_test_issue_mz851
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# mz851: building an OCI base with --reproducible and
# FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS flipped the output to a docker
# schema2 manifest while keeping the OCI base layer descriptors verbatim. The
# resulting docker manifest carrying OCI layer media types is malformed and
# breaks strict readers like dockle.
#
# Breaks on v1.28.0 behind FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS. The
# cause is mutate.Canonical rebuilding the image on an empty (docker) base, so
# --reproducible alone already rewrites to docker, and ReplaceBase then splices
# the pristine OCI base layers back on top of that docker manifest.
#
# Pin an OCI base and add one RUN so kaniko produces a layer of its own. With
# the fix the preserved base layers are relabeled so the output manifest,
# config, and every layer are consistently dockerv2.
FROM debian:12.10@sha256:6bc30d909583f38600edd6609e29eb3fb284ab8affce8d0389f332fc91c2dd91
RUN echo 'hi' > /root/hi.txt
1 change: 1 addition & 0 deletions integration/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ func NewDockerFileBuilder() *DockerFileBuilder {
d.TestReproducibleDockerfiles = map[string]struct{}{
"Dockerfile_test_copy_reproducible": {},
"Dockerfile_test_issue_mz731": {},
"Dockerfile_test_issue_mz851": {},
}
return &d
}
Expand Down
8 changes: 6 additions & 2 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,11 @@ func TestReproducible(t *testing.T) {
t.Fatalf("build %s -> %s: %v", dockerfile, ref, err)
}
}
const baseRef = "alpine@sha256:5ce5f501c457015c4b91f91a15ac69157d9b06f1a75cf9107bf2b62e0843983a"
baseRefs := map[string]string{
"Dockerfile_test_copy_reproducible": "alpine@sha256:5ce5f501c457015c4b91f91a15ac69157d9b06f1a75cf9107bf2b62e0843983a",
"Dockerfile_test_issue_mz731": "alpine@sha256:5ce5f501c457015c4b91f91a15ac69157d9b06f1a75cf9107bf2b62e0843983a",
"Dockerfile_test_issue_mz851": "debian@sha256:6bc30d909583f38600edd6609e29eb3fb284ab8affce8d0389f332fc91c2dd91",
}
for dockerfile := range imageBuilder.TestReproducibleDockerfiles {
if match, _ := filepath.Match(config.dockerfilesPattern, dockerfile); !match {
continue
Expand All @@ -991,7 +995,7 @@ func TestReproducible(t *testing.T) {
containerDiff(t, ref0, ref1)

// mz731: make sure the inherited base layers were not mutated.
base := layerDigests(t, baseRef)
base := layerDigests(t, baseRefs[dockerfile])
kaniko := layerDigests(t, ref0)
testutil.CheckDeepEqual(t, base, kaniko[:len(base)])
})
Expand Down
4 changes: 4 additions & 0 deletions pkg/executor/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,10 @@ func DoBuild(opts *config.KanikoOptions) (image v1.Image, retErr error) {
if len(opts.Annotations) > 0 {
sourceImage = mutate.Annotations(sourceImage, opts.Annotations).(v1.Image)
}
err = image_util.AssertConsistentMediaType(sourceImage)
if err != nil {
return nil, err
}
pushImage = sourceImage
}
if stage.Final {
Expand Down
52 changes: 51 additions & 1 deletion pkg/image/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ package image

import (
"encoding/json"
"strings"

v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/partial"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/osscontainertools/kaniko/pkg/util"
"github.com/sirupsen/logrus"
)

// ReplaceBase returns img with its first len(base.Layers()) layers and first
Expand Down Expand Up @@ -77,7 +80,21 @@ func ReplaceBase(img, base v1.Image) (v1.Image, error) {
}
if !h.EmptyLayer {
if li < len(baseLayers) {
a.Layer = baseLayers[li]
mt, err := baseLayers[li].MediaType()
if err != nil {
return nil, err
}
switch mt {
case types.DockerLayer, types.DockerUncompressedLayer, types.DockerForeignLayer:
a.Layer = baseLayers[li]
case types.OCILayer:
a.Layer = &mediaTypeLayer{Layer: baseLayers[li], mediaType: types.DockerLayer}
case types.OCIUncompressedLayer:
a.Layer = &mediaTypeLayer{Layer: baseLayers[li], mediaType: types.DockerUncompressedLayer}
default:
logrus.Warnf("not preserving base layers: base image has %s layers, incompatible with a reproducible dockerv2 image", mt)
return img, nil
}
} else {
a.Layer = imgLayers[li]
}
Expand All @@ -100,6 +117,39 @@ func ReplaceBase(img, base v1.Image) (v1.Image, error) {
return mutate.ConfigFile(stacked, finalCfg)
}

type mediaTypeLayer struct {
v1.Layer
mediaType types.MediaType
}

func (l *mediaTypeLayer) MediaType() (types.MediaType, error) {
return l.mediaType, nil
}

func AssertConsistentMediaType(img v1.Image) error {
man, err := img.Manifest()
if err != nil {
return err
}
oci, docker := false, false
classify := func(mt types.MediaType) {
s := string(mt)
switch {
case strings.Contains(s, types.OCIVendorPrefix):
oci = true
case strings.Contains(s, types.DockerVendorPrefix):
docker = true
}
}
classify(man.MediaType)
classify(man.Config.MediaType)
for _, l := range man.Layers {
classify(l.MediaType)
}
util.Assert("image.consistent-media-type", !(oci && docker), "manifest mixes OCI and docker media types")
return nil
}

// WithoutAnnotations returns an image whose manifest has no annotations.
// mutate.Annotations only merges into existing annotations and cannot delete
// keys, so we wrap the image to intercept Manifest and RawManifest instead.
Expand Down
Loading