Skip to content

Commit

Permalink
Fix invocation image digest validation
Browse files Browse the repository at this point in the history
Bundles pushed with cnab-to-oci will have invocation images with more
than one repoDigest, which is valid. One is for the original invocation
image that was pushed. The other is for the relocated invocation image
that is now inside the bundle repository.

https://porter.sh/distribute-bundles/#image-references-after-publishing

We should check all of the repoDigests and if a match is found, then it
is valid.

Signed-off-by: Carolyn Van Slyck <[email protected]>
  • Loading branch information
carolynvs committed Oct 1, 2020
1 parent 02823c6 commit f74a606
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 44 deletions.
40 changes: 22 additions & 18 deletions driver/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,27 +417,31 @@ func (d *Driver) validateImageDigest(image bundle.InvocationImage, repoDigests [
return nil
}

switch count := len(repoDigests); {
case count == 0:
if len(repoDigests) == 0 {
return fmt.Errorf("image %s has no repo digests", image.Image)
case count > 1:
return fmt.Errorf("image %s has more than one repo digest", image.Image)
}

// RepoDigests are of the form 'imageName@sha256:<sha256>'; we parse out the digest itself for comparison
repoDigest := repoDigests[0]
ref, err := reference.ParseNormalizedNamed(repoDigest)
if err != nil {
return fmt.Errorf("unable to parse repo digest %s", repoDigest)
}
digestRef, ok := ref.(reference.Digested)
if !ok {
return fmt.Errorf("unable to parse repo digest %s", repoDigest)
}
digest := digestRef.Digest().String()
for _, repoDigest := range repoDigests {
// RepoDigests are of the form 'imageName@sha256:<sha256>' or imageName:<tag>
// We only care about the ones in digest form
ref, err := reference.ParseNormalizedNamed(repoDigest)
if err != nil {
return fmt.Errorf("unable to parse repo digest %s", repoDigest)
}

if digest == image.Digest {
return nil
digestRef, ok := ref.(reference.Digested)
if !ok {
continue
}

digest := digestRef.Digest().String()

// image.Digest is the digest of the original invocation image defined in the bundle.
// It persists even when the bundle's invocation image has been relocated.
if digest == image.Digest {
return nil
}
}
return fmt.Errorf("content digest mismatch: image %s has digest %s but the value should be %s according to the bundle file", image.Image, digest, image.Digest)

return fmt.Errorf("content digest mismatch: invocation image %s was defined in the bundle with the digest %s but no matching repoDigest was found upon inspecting the image", image.Image, image.Digest)
}
42 changes: 16 additions & 26 deletions driver/docker/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/strslice"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/cnabio/cnab-go/bundle"
"github.com/cnabio/cnab-go/driver"
Expand Down Expand Up @@ -87,8 +88,12 @@ func TestDriver_GetConfigurationOptions(t *testing.T) {
}

func TestDriver_ValidateImageDigest(t *testing.T) {
// Mimic the digests created when a bundle is pushed with cnab-to-oci
// there is one for the original invocation image and another
// for the relocated invocation image inside the bundle repository
repoDigests := []string{
"myreg/myimg@sha256:d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05a",
"myreg/mybun-installer:v1.0.0",
"myreg/mybun@sha256:d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05a",
}

t.Run("no image digest", func(t *testing.T) {
Expand All @@ -101,55 +106,40 @@ func TestDriver_ValidateImageDigest(t *testing.T) {
assert.NoError(t, err)
})

t.Run("image digest exists - no match exists", func(t *testing.T) {
t.Run("image digest exists - no match found", func(t *testing.T) {
d := &Driver{}

image := bundle.InvocationImage{}
image.Image = "myreg/myimg"
image.Image = "myreg/mybun@sha256:d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05a"
image.Digest = "sha256:185518070891758909c9f839cf4ca393ee977ac378609f700f60a771a2dfe321"

err := d.validateImageDigest(image, repoDigests)
assert.NotNil(t, err, "expected an error")
require.NotNil(t, err, "expected an error")
assert.Contains(t, err.Error(), "content digest mismatch")
})

t.Run("image digest exists - repo digest unparseable", func(t *testing.T) {
d := &Driver{}

image := bundle.InvocationImage{}
image.Image = "myreg/myimg"
image.Image = "myreg/mybun@sha256:d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05a"
image.Digest = "sha256:185518070891758909c9f839cf4ca393ee977ac378609f700f60a771a2dfe321"

badRepoDigests := []string{"myreg/myimg@sha256:deadbeef"}
badRepoDigests := []string{"myreg/mybun@sha256:deadbeef"}

err := d.validateImageDigest(image, badRepoDigests)
assert.NotNil(t, err, "expected an error")
assert.EqualError(t, err, "unable to parse repo digest myreg/myimg@sha256:deadbeef")
require.NotNil(t, err, "expected an error")
assert.Contains(t, err.Error(), "unable to parse repo digest")
})

t.Run("image digest exists - more than one repo digest exists", func(t *testing.T) {
t.Run("image digest exists - match found", func(t *testing.T) {
d := &Driver{}

image := bundle.InvocationImage{}
image.Image = "myreg/myimg"
image.Digest = "sha256:d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05a"

multipleRepoDigests := append(repoDigests,
"myreg/myimg@sha256:185518070891758909c9f839cf4ca393ee977ac378609f700f60a771a2dfe321")

err := d.validateImageDigest(image, multipleRepoDigests)
assert.NotNil(t, err, "expected an error")
assert.EqualError(t, err, "image myreg/myimg has more than one repo digest")
})

t.Run("image digest exists - an exact match exists", func(t *testing.T) {
d := &Driver{}

image := bundle.InvocationImage{}
image.Image = "myreg/myimg"
image.Image = "myreg/mybun@sha256:d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05a"
image.Digest = "sha256:d366a4665ab44f0648d7a00ae3fae139d55e32f9712c67accd604bb55df9d05a"

err := d.validateImageDigest(image, repoDigests)
assert.NoError(t, err)
require.NoError(t, err, "validateImageDigest failed")
})
}

0 comments on commit f74a606

Please sign in to comment.