From c965a6c4da0b1724122fdc2967f29ad1a90aeafd Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Wed, 31 Jul 2019 15:12:58 -0700 Subject: [PATCH] Prevent push by tag for sub-manifests When pushing a manifest list, all manifests should be pushed by digest and only the final manifest pushed by tag. The Pusher was preventing this by mistakenly disallowing objects to contain a digest. When objects have a digest, only push tags associated with that digest. Signed-off-by: Derek McGowan --- client.go | 5 +++++ remotes/docker/pusher.go | 37 +++++++++++++++++++++++-------------- remotes/docker/resolver.go | 9 +-------- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/client.go b/client.go index 4bd66578d..d9a4e3cbb 100644 --- a/client.go +++ b/client.go @@ -403,6 +403,11 @@ func (c *Client) Push(ctx context.Context, ref string, desc ocispec.Descriptor, } } + // Annotate ref with digest to push only push tag for single digest + if !strings.Contains(ref, "@") { + ref = ref + "@" + desc.Digest.String() + } + pusher, err := pushCtx.Resolver.Pusher(ctx, ref) if err != nil { return err diff --git a/remotes/docker/pusher.go b/remotes/docker/pusher.go index 3e65832b8..d6e29987f 100644 --- a/remotes/docker/pusher.go +++ b/remotes/docker/pusher.go @@ -37,7 +37,7 @@ import ( type dockerPusher struct { *dockerBase - tag string + object string // TODO: namespace tracker tracker StatusTracker @@ -74,11 +74,7 @@ func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (conten case images.MediaTypeDockerSchema2Manifest, images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageManifest, ocispec.MediaTypeImageIndex: isManifest = true - if p.tag == "" { - existCheck = []string{"manifests", desc.Digest.String()} - } else { - existCheck = []string{"manifests", p.tag} - } + existCheck = getManifestPath(p.object, desc.Digest) default: existCheck = []string{"blobs", desc.Digest.String()} } @@ -97,7 +93,7 @@ func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (conten } else { if resp.StatusCode == http.StatusOK { var exists bool - if isManifest && p.tag != "" { + if isManifest && existCheck[1] != desc.Digest.String() { dgstHeader := digest.Digest(resp.Header.Get("Docker-Content-Digest")) if dgstHeader == desc.Digest { exists = true @@ -122,13 +118,7 @@ func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (conten } if isManifest { - var putPath []string - if p.tag != "" { - putPath = []string{"manifests", p.tag} - } else { - putPath = []string{"manifests", desc.Digest.String()} - } - + putPath := getManifestPath(p.object, desc.Digest) req = p.request(host, http.MethodPut, putPath...) req.header.Add("Content-Type", desc.MediaType) } else { @@ -271,6 +261,25 @@ func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (conten }, nil } +func getManifestPath(object string, dgst digest.Digest) []string { + if i := strings.IndexByte(object, '@'); i >= 0 { + if object[i+1:] != dgst.String() { + // use digest, not tag + object = "" + } else { + // strip @ for registry path to make tag + object = object[:i] + } + + } + + if object == "" { + return []string{"manifests", dgst.String()} + } + + return []string{"manifests", object} +} + type pushWriter struct { base *dockerBase ref string diff --git a/remotes/docker/resolver.go b/remotes/docker/resolver.go index 7eacd08af..8e65d8ccc 100644 --- a/remotes/docker/resolver.go +++ b/remotes/docker/resolver.go @@ -399,13 +399,6 @@ func (r *dockerResolver) Pusher(ctx context.Context, ref string) (remotes.Pusher return nil, err } - // Manifests can be pushed by digest like any other object, but the passed in - // reference cannot take a digest without the associated content. A tag is allowed - // and will be used to tag pushed manifests. - if refspec.Object != "" && strings.Contains(refspec.Object, "@") { - return nil, errors.New("cannot use digest reference for push locator") - } - base, err := r.base(refspec) if err != nil { return nil, err @@ -413,7 +406,7 @@ func (r *dockerResolver) Pusher(ctx context.Context, ref string) (remotes.Pusher return dockerPusher{ dockerBase: base, - tag: refspec.Object, + object: refspec.Object, tracker: r.tracker, }, nil }