fix: make image digest check more permissive (#2510)

* fix: make image digest check more permissive

* use extant implementation for digest check
This commit is contained in:
Luke Kingland 2024-09-24 16:14:48 +09:00 committed by GitHub
parent 63e3e52294
commit eb17ea77e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 54 additions and 60 deletions

View File

@ -9,6 +9,7 @@ import (
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/google/go-containerregistry/pkg/name"
"github.com/ory/viper"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/resource"
@ -785,51 +786,13 @@ func printDeployMessages(out io.Writer, f fn.Function) {
}
}
// isDigested returns true if provided image string 'v' has digest and false if not.
// Includes basic validation that a provided digest is correctly formatted.
// Given that image is not digested, image will still be validated and return
// a combination of bool (img has valid digest) and err (img is in valid format)
// Therefore returned combination of [false,nil] means "valid undigested image".
// isDigested checks that the given image reference has a digest. Invalid
// reference return error.
func isDigested(v string) (validDigest bool, err error) {
var digest string
vv := strings.Split(v, "@")
if len(vv) < 2 {
// image does NOT have a digest, validate further
if v == "" {
err = fmt.Errorf("provided image is empty, cannot validate")
return
}
vvv := strings.Split(v, ":")
if len(vvv) < 2 {
// assume user knows what hes doing
return
} else if len(vvv) > 2 {
err = fmt.Errorf("image '%v' contains an invalid tag (extra ':')", v)
return
}
tag := vvv[1]
if tag == "" {
err = fmt.Errorf("image '%v' has an empty tag", v)
return
}
return
} else if len(vv) > 2 {
// image is invalid
err = fmt.Errorf("image '%v' contains an invalid digest (extra '@')", v)
return
ref, err := name.ParseReference(v)
if err != nil {
return false, err
}
// image has a digest, validate further
digest = vv[1]
if !strings.HasPrefix(digest, "sha256:") {
err = fmt.Errorf("image digest '%s' requires 'sha256:' prefix", digest)
return
}
if len(digest[7:]) != 64 {
err = fmt.Errorf("image digest '%v' has an invalid sha256 hash length of %v when it should be 64", digest, len(digest[7:]))
}
validDigest = true
return
_, ok := ref.(name.Digest)
return ok, nil
}

View File

@ -756,34 +756,34 @@ func TestDeploy_ImageWithDigestErrors(t *testing.T) {
image string // value to provide as --image
build string // If provided, the value of the build flag
push bool // if true, explicitly set argument --push=true
errString string // the string value of an expected error
errPrefix string // the string value of an expected error
}{
{
name: "correctly formatted full image with digest yields no error (degen case)",
image: "example.com/myNamespace/myFunction:latest@sha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4e",
image: "example.com/namespace/myfunction@sha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4e",
build: "false",
},
{
name: "--build forced on yields error",
image: "example.com/myNamespace/myFunction:latest@sha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4e",
image: "example.com/mynamespace/myfunction@sha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4e",
build: "true",
errString: "building can not be enabled when using an image with digest",
errPrefix: "building can not be enabled when using an image with digest",
},
{
name: "push flag explicitly set with digest should error",
image: "example.com/myNamespace/myFunction:latest@sha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4e",
image: "example.com/mynamespace/myfunction@sha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4e",
push: true,
errString: "pushing is not valid when specifying an image with digest",
errPrefix: "pushing is not valid when specifying an image with digest",
},
{
name: "invalid digest prefix 'Xsha256', expect error",
image: "example.com/myNamespace/myFunction:latest@Xsha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4e",
errString: "image digest 'Xsha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4e' requires 'sha256:' prefix",
image: "example.com/mynamespace/myfunction@Xsha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4e",
errPrefix: "could not parse reference",
},
{
name: "invalid sha hash length(added X at the end), expect error",
image: "example.com/myNamespace/myFunction:latest@sha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4eX",
errString: "image digest 'sha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4eX' has an invalid sha256 hash length of 65 when it should be 64",
image: "example.com/mynamespace/myfunction@sha256:7d66645b0add6de7af77ef332ecd4728649a2f03b9a2716422a054805b595c4eX",
errPrefix: "could not parse reference",
},
}
@ -823,11 +823,11 @@ func TestDeploy_ImageWithDigestErrors(t *testing.T) {
cmd.SetArgs(args)
err = cmd.Execute()
if err != nil {
if tt.errString == "" {
if tt.errPrefix == "" {
t.Fatal(err) // no error was expected. fail
}
if tt.errString != err.Error() {
t.Fatalf("expected error '%v' not received. got '%v'", tt.errString, err.Error())
if !strings.HasPrefix(err.Error(), tt.errPrefix) {
t.Fatalf("expected error prefix '%v' not received. got '%v'", tt.errPrefix, err.Error())
}
// There was an error, but it was expected
}
@ -842,7 +842,7 @@ func TestDeploy_ImageWithDigestErrors(t *testing.T) {
func TestDeploy_ImageWithDigestDoesntPopulateBuild(t *testing.T) {
root := FromTempDirectory(t)
// image with digest (well almost, atleast in length and syntax)
const img = "docker.io/4141gauron3268@sha256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
const img = "docker.io/4141gauron3268@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
// Create a new Function in the temp directory
_, err := fn.New().Init(fn.Function{Runtime: "go", Root: root})
if err != nil {
@ -869,7 +869,7 @@ func TestDeploy_ImageWithDigestDoesntPopulateBuild(t *testing.T) {
// TestDeploy_WithoutDigest ensures that images specified with --image and
// without digest are correctly processed and propagated to .Deploy.Image
func TestDeploy_WithoutDigest(t *testing.T) {
const sha = "sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
const sha = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
tests := []struct {
name string //name of the test
@ -2140,3 +2140,34 @@ func TestDeploy_CorrectImageDeployed(t *testing.T) {
})
}
}
// Test_isDigested ensures that the function is properly delegating to
// by checking both that it will fail on an invalid reference and will
// return true if the image contains a digest and false otherwise.
// See the delegate from the implementation for comprehensive tests.
func Test_isDigested(t *testing.T) {
var digested bool
var err error
// Ensure validation
_, err = isDigested("invalid&image@sha256:12345")
if err == nil {
t.Fatal("did not validate image reference")
}
// Ensure positive case
if digested, err = isDigested("alpine"); err != nil {
t.Fatal(err)
}
if digested {
t.Fatal("reported digested on undigested image reference")
}
// Ensure negative case
if digested, err = isDigested("alpine@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); err != nil {
t.Fatal(err)
}
if !digested {
t.Fatal("did not report image reference has digest")
}
}