libimage: {un}tag: reject digests
Make sure that tag and untag reject digested input. Also add unit tests for both to make sure we're not regressing in the future. Fixes: containers/common#710 Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
parent
64369e9623
commit
2a36074db6
|
|
@ -448,14 +448,24 @@ func (i *Image) removeRecursive(ctx context.Context, rmMap map[string]*RemoveIma
|
||||||
return parent.removeRecursive(ctx, rmMap, processedIDs, "", options)
|
return parent.removeRecursive(ctx, rmMap, processedIDs, "", options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errTagDigest = errors.New("tag by digest not supported")
|
||||||
|
|
||||||
// Tag the image with the specified name and store it in the local containers
|
// Tag the image with the specified name and store it in the local containers
|
||||||
// storage. The name is normalized according to the rules of NormalizeName.
|
// storage. The name is normalized according to the rules of NormalizeName.
|
||||||
func (i *Image) Tag(name string) error {
|
func (i *Image) Tag(name string) error {
|
||||||
|
if strings.HasPrefix(name, "sha256:") { // ambiguous input
|
||||||
|
return errors.Wrap(errTagDigest, name)
|
||||||
|
}
|
||||||
|
|
||||||
ref, err := NormalizeName(name)
|
ref, err := NormalizeName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error normalizing name %q", name)
|
return errors.Wrapf(err, "error normalizing name %q", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, isDigested := ref.(reference.Digested); isDigested {
|
||||||
|
return errors.Wrap(errTagDigest, name)
|
||||||
|
}
|
||||||
|
|
||||||
logrus.Debugf("Tagging image %s with %q", i.ID(), ref.String())
|
logrus.Debugf("Tagging image %s with %q", i.ID(), ref.String())
|
||||||
if i.runtime.eventChannel != nil {
|
if i.runtime.eventChannel != nil {
|
||||||
defer i.runtime.writeEvent(&Event{ID: i.ID(), Name: name, Time: time.Now(), Type: EventTypeImageTag})
|
defer i.runtime.writeEvent(&Event{ID: i.ID(), Name: name, Time: time.Now(), Type: EventTypeImageTag})
|
||||||
|
|
@ -480,7 +490,7 @@ var errUntagDigest = errors.New("untag by digest not supported")
|
||||||
// the local containers storage. The name is normalized according to the rules
|
// the local containers storage. The name is normalized according to the rules
|
||||||
// of NormalizeName.
|
// of NormalizeName.
|
||||||
func (i *Image) Untag(name string) error {
|
func (i *Image) Untag(name string) error {
|
||||||
if strings.HasPrefix(name, "sha256:") {
|
if strings.HasPrefix(name, "sha256:") { // ambiguous input
|
||||||
return errors.Wrap(errUntagDigest, name)
|
return errors.Wrap(errUntagDigest, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -488,6 +498,11 @@ func (i *Image) Untag(name string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error normalizing name %q", name)
|
return errors.Wrapf(err, "error normalizing name %q", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, isDigested := ref.(reference.Digested); isDigested {
|
||||||
|
return errors.Wrap(errUntagDigest, name)
|
||||||
|
}
|
||||||
|
|
||||||
name = ref.String()
|
name = ref.String()
|
||||||
|
|
||||||
logrus.Debugf("Untagging %q from image %s", ref.String(), i.ID())
|
logrus.Debugf("Untagging %q from image %s", ref.String(), i.ID())
|
||||||
|
|
|
||||||
|
|
@ -164,3 +164,107 @@ func TestImageFunctions(t *testing.T) {
|
||||||
require.Equal(t, labels, imageData.Labels, "inspect data should match")
|
require.Equal(t, labels, imageData.Labels, "inspect data should match")
|
||||||
require.Equal(t, image.NamesHistory(), imageData.NamesHistory, "inspect data should match")
|
require.Equal(t, image.NamesHistory(), imageData.NamesHistory, "inspect data should match")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTag(t *testing.T) {
|
||||||
|
// Note: this will resolve pull from the GCR registry (see
|
||||||
|
// testdata/registries.conf).
|
||||||
|
busyboxLatest := "docker.io/library/busybox:latest"
|
||||||
|
|
||||||
|
runtime, cleanup := testNewRuntime(t)
|
||||||
|
defer cleanup()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
pullOptions := &PullOptions{}
|
||||||
|
pullOptions.Writer = os.Stdout
|
||||||
|
pulledImages, err := runtime.Pull(ctx, busyboxLatest, config.PullPolicyMissing, pullOptions)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, pulledImages, 1)
|
||||||
|
|
||||||
|
image := pulledImages[0]
|
||||||
|
|
||||||
|
digest := "sha256:adab3844f497ab9171f070d4cae4114b5aec565ac772e2f2579405b78be67c96"
|
||||||
|
|
||||||
|
// Tag
|
||||||
|
for _, test := range []struct {
|
||||||
|
tag string
|
||||||
|
resolvesTo string
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"foo", "localhost/foo:latest", false},
|
||||||
|
{"docker.io/foo", "docker.io/library/foo:latest", false},
|
||||||
|
{"quay.io/bar/foo:tag", "quay.io/bar/foo:tag", false},
|
||||||
|
{"registry.com/$invalid", "", true},
|
||||||
|
{digest, "", true},
|
||||||
|
{"foo@" + digest, "", true},
|
||||||
|
{"quay.io/foo@" + digest, "", true},
|
||||||
|
{"", "", true},
|
||||||
|
} {
|
||||||
|
err := image.Tag(test.tag)
|
||||||
|
if test.expectError {
|
||||||
|
require.Error(t, err, "tag should have failed: %v", test)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
require.NoError(t, err, "tag should have succeeded: %v", test)
|
||||||
|
|
||||||
|
_, resolvedName, err := runtime.LookupImage(test.tag, nil)
|
||||||
|
require.NoError(t, err, "image should have resolved locally: %v", test)
|
||||||
|
require.Equal(t, test.resolvesTo, resolvedName, "image should have resolved correctly: %v", test)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for specific error.
|
||||||
|
err = image.Tag("foo@" + digest)
|
||||||
|
require.True(t, errors.Cause(err) == errTagDigest, "check for specific digest error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUntag(t *testing.T) {
|
||||||
|
// Note: this will resolve pull from the GCR registry (see
|
||||||
|
// testdata/registries.conf).
|
||||||
|
busyboxLatest := "docker.io/library/busybox:latest"
|
||||||
|
|
||||||
|
runtime, cleanup := testNewRuntime(t)
|
||||||
|
defer cleanup()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
pullOptions := &PullOptions{}
|
||||||
|
pullOptions.Writer = os.Stdout
|
||||||
|
pulledImages, err := runtime.Pull(ctx, busyboxLatest, config.PullPolicyMissing, pullOptions)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, pulledImages, 1)
|
||||||
|
|
||||||
|
image := pulledImages[0]
|
||||||
|
|
||||||
|
digest := "sha256:adab3844f497ab9171f070d4cae4114b5aec565ac772e2f2579405b78be67c96"
|
||||||
|
|
||||||
|
// Untag
|
||||||
|
for _, test := range []struct {
|
||||||
|
tag string
|
||||||
|
untag string
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"foo", "foo", false},
|
||||||
|
{"foo", "foo:latest", false},
|
||||||
|
{"foo", "localhost/foo", false},
|
||||||
|
{"foo", "localhost/foo:latest", false},
|
||||||
|
{"quay.io/image/foo", "quay.io/image/foo", false},
|
||||||
|
{"foo", "doNotExist", true},
|
||||||
|
{"foo", digest, true},
|
||||||
|
{"foo", "foo@" + digest, true},
|
||||||
|
{"foo", "localhost/foo@" + digest, true},
|
||||||
|
} {
|
||||||
|
err := image.Tag(test.tag)
|
||||||
|
require.NoError(t, err, "tag should have succeeded: %v", test)
|
||||||
|
|
||||||
|
err = image.Untag(test.untag)
|
||||||
|
if test.expectError {
|
||||||
|
require.Error(t, err, "untag should have failed: %v", test)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
require.NoError(t, err, "untag should have succeedded: %v", test)
|
||||||
|
_, resolvedName, err := runtime.LookupImage(test.tag, nil)
|
||||||
|
require.Error(t, err, "image should not resolve after untag anymore (%s): %v", resolvedName, test)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for specific error.
|
||||||
|
err = image.Untag("foo@" + digest)
|
||||||
|
require.True(t, errors.Cause(err) == errUntagDigest, "check for specific digest error")
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue