Skip to content

Commit

Permalink
Fix image name parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
halamix2 committed Nov 20, 2024
1 parent 763a7a8 commit 2954b82
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 39 deletions.
57 changes: 25 additions & 32 deletions internal/validate/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ import (
"github.com/pkg/errors"
)

const (
tagDelim = ":"
)

//go:generate mockery --name=ImageValidatorService
type ImageValidatorService interface {
Validate(ctx context.Context, image string, imagePullCredentials map[string]cliType.AuthConfig) error
Expand Down Expand Up @@ -70,21 +66,27 @@ func (s *notaryService) Validate(ctx context.Context, image string, imagePullCre
return nil
}

split := strings.Split(image, tagDelim)
if len(image) == 0 {
return pkg.NewValidationFailedErr(errors.New("empty image provided"))
}

if len(split) != 2 {
return pkg.NewValidationFailedErr(errors.New("image name is not formatted correctly"))
ref, err := name.ParseReference(image)
if err != nil {
return pkg.NewValidationFailedErr(errors.Wrap(err, "image name could not be parsed"))
}

imgRepo := split[0]
imgTag := split[1]
// name.ParseReference() uses default `latest` tag when no tag/digest was provided
// we want to block all images with no explicit tag/digest provided
if !imageContainsTag(image, ref) {
return pkg.NewValidationFailedErr(errors.New("image is missing tag or hash"))
}

expectedShaBytes, err := s.loggedGetNotaryImageDigestHash(ctx, imgRepo, imgTag)
expectedShaBytes, err := s.loggedGetNotaryImageDigestHash(ctx, ref)
if err != nil {
return err
}

shaImageBytes, shaManifestBytes, err := s.loggedGetRepositoryDigestHash(ctx, image, imagePullCredentials)
shaImageBytes, shaManifestBytes, err := s.loggedGetRepositoryDigestHash(ctx, ref, imagePullCredentials)
if err != nil {
return err
}
Expand All @@ -101,6 +103,10 @@ func (s *notaryService) Validate(ctx context.Context, image string, imagePullCre
return pkg.NewValidationFailedErr(errors.New("unexpected image hash value"))
}

func imageContainsTag(image string, ref name.Reference) bool {
return strings.Contains(image, ref.Identifier())
}

func (s *notaryService) isImageAllowed(imgRepo string) bool {
for _, allowed := range s.AllowedRegistries {
// repository is in allowed list
Expand All @@ -111,23 +117,14 @@ func (s *notaryService) isImageAllowed(imgRepo string) bool {
return false
}

func (s *notaryService) loggedGetRepositoryDigestHash(ctx context.Context, image string, imagePullCredentials map[string]cliType.AuthConfig) ([]byte, []byte, error) {
func (s *notaryService) loggedGetRepositoryDigestHash(ctx context.Context, ref name.Reference, imagePullCredentials map[string]cliType.AuthConfig) ([]byte, []byte, error) {
const message = "request to image registry"
closeLog := helpers.LogStartTime(ctx, message)
defer closeLog()
return s.getRepositoryDigestHash(image, imagePullCredentials)
return s.getRepositoryDigestHash(ref, imagePullCredentials)
}

func (s *notaryService) getRepositoryDigestHash(image string, imagePullCredentials map[string]cliType.AuthConfig) ([]byte, []byte, error) {
if len(image) == 0 {
return nil, nil, pkg.NewValidationFailedErr(errors.New("empty image provided"))
}

ref, err := name.ParseReference(image)
if err != nil {
return nil, nil, pkg.NewValidationFailedErr(errors.Wrap(err, "ref parse"))
}

func (s *notaryService) getRepositoryDigestHash(ref name.Reference, imagePullCredentials map[string]cliType.AuthConfig) ([]byte, []byte, error) {
remoteOptions := make([]remote.Option, 0)

// check if we have credentials for the registry, and use them
Expand Down Expand Up @@ -233,30 +230,26 @@ func getImageDigestHash(ref name.Reference, remoteOptions ...remote.Option) ([]b
return digestBytes, manifestBytes, nil
}

func (s *notaryService) loggedGetNotaryImageDigestHash(ctx context.Context, imgRepo, imgTag string) ([]byte, error) {
func (s *notaryService) loggedGetNotaryImageDigestHash(ctx context.Context, ref name.Reference) ([]byte, error) {
const message = "request to notary"
closeLog := helpers.LogStartTime(ctx, message)
defer closeLog()
result, err := s.getNotaryImageDigestHash(ctx, imgRepo, imgTag)
result, err := s.getNotaryImageDigestHash(ctx, ref)
return result, err
}

func (s *notaryService) getNotaryImageDigestHash(ctx context.Context, imgRepo, imgTag string) ([]byte, error) {
if len(imgRepo) == 0 || len(imgTag) == 0 {
return nil, pkg.NewValidationFailedErr(errors.New("empty arguments provided"))
}

func (s *notaryService) getNotaryImageDigestHash(ctx context.Context, ref name.Reference) ([]byte, error) {
const messageNewRepoClient = "request to notary (NewRepoClient)"
closeLog := helpers.LogStartTime(ctx, messageNewRepoClient)
c, err := s.RepoFactory.NewRepoClient(imgRepo, s.NotaryConfig)
c, err := s.RepoFactory.NewRepoClient(ref.Context().Name(), s.NotaryConfig)
closeLog()
if err != nil {
return nil, pkg.NewUnknownResultErr(err)
}

const messageGetTargetByName = "request to notary (GetTargetByName)"
closeLog = helpers.LogStartTime(ctx, messageGetTargetByName)
target, err := c.GetTargetByName(imgTag)
target, err := c.GetTargetByName(ref.Identifier())
closeLog()
if err != nil {
return nil, parseNotaryErr(err)
Expand Down
39 changes: 39 additions & 0 deletions internal/validate/image_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (

cliType "github.com/docker/cli/cli/config/types"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/stretchr/testify/assert"
)

func Test_parseCredentials(t *testing.T) {
Expand Down Expand Up @@ -70,3 +72,40 @@ func Test_parseCredentials(t *testing.T) {
})
}
}

func Test_ImageContainsTag(t *testing.T) {
tests := []struct {
name string
image string
want bool
}{
{
name: "image with no tag",
image: "image",
want: false,
},
{
name: "image with tag",
image: "image:tag",
want: true,
},
{
name: "image with digest",
image: "image@sha256:fdd33d7bf8cc80f223e30b4aa6c2ad705ffc7cf1a77697f37ed7232bc74484b0",
want: true,
},
{
name: "image with tag and digest",
image: "image:tag@sha256:fdd33d7bf8cc80f223e30b4aa6c2ad705ffc7cf1a77697f37ed7232bc74484b0",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ref, err := name.ParseReference(tt.image)
assert.NoError(t, err)
containsTag := imageContainsTag(tt.image, ref)
assert.Equal(t, tt.want, containsTag)
})
}
}
14 changes: 7 additions & 7 deletions internal/validate/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ var (
hash: []byte{33, 98, 102, 200, 111, 196, 220, 239, 86, 25, 147, 11, 211, 148, 36, 88, 36, 194, 175, 82, 253, 33, 186, 124, 111, 160, 230, 24, 101, 125, 76, 59},
}
differentHashImage = image{
name: "nginx",
name: "docker.io/library/nginx",
tag: "latest",
hash: []byte{1, 2, 3, 4},
}
untrustedImage = image{
name: "nginx",
name: "docker.io/library/nginx",
tag: "untrusted",
}
unknownImage = image{
Expand Down Expand Up @@ -107,17 +107,17 @@ func Test_Validate_InvalidImageName_ShouldReturnError(t *testing.T) {
{
name: "image name without semicolon",
imageName: "makapaka",
expectedErrMsg: "image name is not formatted correctly",
expectedErrMsg: "image is missing tag or hash",
},
{
name: "",
imageName: ":",
expectedErrMsg: "empty arguments provided",
expectedErrMsg: "image name could not be parsed",
},
{
name: "image name with more than one semicolon", //TODO: IMO it's proper image name, but now is not allowed
imageName: "repo:port/image-name:tag",
expectedErrMsg: "image name is not formatted correctly",
name: "image name with more than two semicolon", //TODO: IMO it's proper image name, but now is not allowed
imageName: "repo:port/image-name:tag:hash",
expectedErrMsg: "image name could not be parsed",
},
}
for _, tt := range tests {
Expand Down

0 comments on commit 2954b82

Please sign in to comment.