2017-06-15 14:41:54 -04:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
2018-05-03 21:02:44 -04:00
|
|
|
"context"
|
2017-06-15 14:41:54 -04:00
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
manifesttypes "github.com/docker/cli/cli/manifest/types"
|
cli/registry/client: set actions when authn with token
When using a personal access token, Docker Hub produces an error if actions
are requested beyond the token's allowed actions. This resulted in errors
when using a PAT with limited permissions to do a "docker manifest inspect".
This patch sets actions to "pull" only by default, and requests "push" action
for requests that need it.
To verify:
- create a PAT with limited access (read-only)
- log in with your username and the PAT as password
Before this patch:
docker manifest inspect ubuntu:latest
Get "https://registry-1.docker.io/v2/library/ubuntu/manifests/latest": unauthorized: access token has insufficient scopes
With this patch applied:
docker manifest inspect ubuntu:latest
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 424,
"digest": "sha256:56887c5194fddd8db7e36ced1c16b3569d89f74c801dc8a5adbf48236fb34564",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 424,
"digest": "sha256:c835a4f2a632bc91a2b494e871549f0dd83f2966c780e66435774e77e048ddf0",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v7"
}
}
]
}
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 06:13:48 -04:00
|
|
|
"github.com/docker/cli/cli/trust"
|
2017-06-15 14:41:54 -04:00
|
|
|
"github.com/docker/distribution"
|
|
|
|
"github.com/docker/distribution/reference"
|
|
|
|
distributionclient "github.com/docker/distribution/registry/client"
|
|
|
|
registrytypes "github.com/docker/docker/api/types/registry"
|
|
|
|
"github.com/opencontainers/go-digest"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
// RegistryClient is a client used to communicate with a Docker distribution
|
|
|
|
// registry
|
|
|
|
type RegistryClient interface {
|
|
|
|
GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error)
|
|
|
|
GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error)
|
|
|
|
MountBlob(ctx context.Context, source reference.Canonical, target reference.Named) error
|
|
|
|
PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewRegistryClient returns a new RegistryClient with a resolver
|
|
|
|
func NewRegistryClient(resolver AuthConfigResolver, userAgent string, insecure bool) RegistryClient {
|
|
|
|
return &client{
|
|
|
|
authConfigResolver: resolver,
|
|
|
|
insecureRegistry: insecure,
|
|
|
|
userAgent: userAgent,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AuthConfigResolver returns Auth Configuration for an index
|
2023-02-07 20:31:59 -05:00
|
|
|
type AuthConfigResolver func(ctx context.Context, index *registrytypes.IndexInfo) registrytypes.AuthConfig
|
2017-06-15 14:41:54 -04:00
|
|
|
|
|
|
|
// PutManifestOptions is the data sent to push a manifest
|
|
|
|
type PutManifestOptions struct {
|
|
|
|
MediaType string
|
|
|
|
Payload []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
type client struct {
|
|
|
|
authConfigResolver AuthConfigResolver
|
|
|
|
insecureRegistry bool
|
|
|
|
userAgent string
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrBlobCreated returned when a blob mount request was created
|
|
|
|
type ErrBlobCreated struct {
|
|
|
|
From reference.Named
|
|
|
|
Target reference.Named
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err ErrBlobCreated) Error() string {
|
|
|
|
return fmt.Sprintf("blob mounted from: %v to: %v",
|
|
|
|
err.From, err.Target)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrHTTPProto returned if attempting to use TLS with a non-TLS registry
|
|
|
|
type ErrHTTPProto struct {
|
|
|
|
OrigErr string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err ErrHTTPProto) Error() string {
|
|
|
|
return err.OrigErr
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ RegistryClient = &client{}
|
|
|
|
|
|
|
|
// MountBlob into the registry, so it can be referenced by a manifest
|
|
|
|
func (c *client) MountBlob(ctx context.Context, sourceRef reference.Canonical, targetRef reference.Named) error {
|
|
|
|
repoEndpoint, err := newDefaultRepositoryEndpoint(targetRef, c.insecureRegistry)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
cli/registry/client: set actions when authn with token
When using a personal access token, Docker Hub produces an error if actions
are requested beyond the token's allowed actions. This resulted in errors
when using a PAT with limited permissions to do a "docker manifest inspect".
This patch sets actions to "pull" only by default, and requests "push" action
for requests that need it.
To verify:
- create a PAT with limited access (read-only)
- log in with your username and the PAT as password
Before this patch:
docker manifest inspect ubuntu:latest
Get "https://registry-1.docker.io/v2/library/ubuntu/manifests/latest": unauthorized: access token has insufficient scopes
With this patch applied:
docker manifest inspect ubuntu:latest
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 424,
"digest": "sha256:56887c5194fddd8db7e36ced1c16b3569d89f74c801dc8a5adbf48236fb34564",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 424,
"digest": "sha256:c835a4f2a632bc91a2b494e871549f0dd83f2966c780e66435774e77e048ddf0",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v7"
}
}
]
}
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 06:13:48 -04:00
|
|
|
repoEndpoint.actions = trust.ActionsPushAndPull
|
2017-06-15 14:41:54 -04:00
|
|
|
repo, err := c.getRepositoryForReference(ctx, targetRef, repoEndpoint)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
lu, err := repo.Blobs(ctx).Create(ctx, distributionclient.WithMountFrom(sourceRef))
|
|
|
|
switch err.(type) {
|
|
|
|
case distribution.ErrBlobMounted:
|
|
|
|
logrus.Debugf("mount of blob %s succeeded", sourceRef)
|
|
|
|
return nil
|
|
|
|
case nil:
|
|
|
|
default:
|
|
|
|
return errors.Wrapf(err, "failed to mount blob %s to %s", sourceRef, targetRef)
|
|
|
|
}
|
|
|
|
lu.Cancel(ctx)
|
|
|
|
logrus.Debugf("mount of blob %s created", sourceRef)
|
|
|
|
return ErrBlobCreated{From: sourceRef, Target: targetRef}
|
|
|
|
}
|
|
|
|
|
|
|
|
// PutManifest sends the manifest to a registry and returns the new digest
|
|
|
|
func (c *client) PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error) {
|
|
|
|
repoEndpoint, err := newDefaultRepositoryEndpoint(ref, c.insecureRegistry)
|
|
|
|
if err != nil {
|
|
|
|
return digest.Digest(""), err
|
|
|
|
}
|
|
|
|
|
cli/registry/client: set actions when authn with token
When using a personal access token, Docker Hub produces an error if actions
are requested beyond the token's allowed actions. This resulted in errors
when using a PAT with limited permissions to do a "docker manifest inspect".
This patch sets actions to "pull" only by default, and requests "push" action
for requests that need it.
To verify:
- create a PAT with limited access (read-only)
- log in with your username and the PAT as password
Before this patch:
docker manifest inspect ubuntu:latest
Get "https://registry-1.docker.io/v2/library/ubuntu/manifests/latest": unauthorized: access token has insufficient scopes
With this patch applied:
docker manifest inspect ubuntu:latest
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 424,
"digest": "sha256:56887c5194fddd8db7e36ced1c16b3569d89f74c801dc8a5adbf48236fb34564",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 424,
"digest": "sha256:c835a4f2a632bc91a2b494e871549f0dd83f2966c780e66435774e77e048ddf0",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v7"
}
}
]
}
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 06:13:48 -04:00
|
|
|
repoEndpoint.actions = trust.ActionsPushAndPull
|
2017-06-15 14:41:54 -04:00
|
|
|
repo, err := c.getRepositoryForReference(ctx, ref, repoEndpoint)
|
|
|
|
if err != nil {
|
|
|
|
return digest.Digest(""), err
|
|
|
|
}
|
|
|
|
|
|
|
|
manifestService, err := repo.Manifests(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return digest.Digest(""), err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, opts, err := getManifestOptionsFromReference(ref)
|
|
|
|
if err != nil {
|
|
|
|
return digest.Digest(""), err
|
|
|
|
}
|
|
|
|
|
|
|
|
dgst, err := manifestService.Put(ctx, manifest, opts...)
|
|
|
|
return dgst, errors.Wrapf(err, "failed to put manifest %s", ref)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) getRepositoryForReference(ctx context.Context, ref reference.Named, repoEndpoint repositoryEndpoint) (distribution.Repository, error) {
|
2020-03-24 11:42:33 -04:00
|
|
|
repoName, err := reference.WithName(repoEndpoint.Name())
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to parse repo name from %s", ref)
|
|
|
|
}
|
2017-06-15 14:41:54 -04:00
|
|
|
httpTransport, err := c.getHTTPTransportForRepoEndpoint(ctx, repoEndpoint)
|
|
|
|
if err != nil {
|
2020-03-24 11:42:33 -04:00
|
|
|
if !strings.Contains(err.Error(), "server gave HTTP response to HTTPS client") {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !repoEndpoint.endpoint.TLSConfig.InsecureSkipVerify {
|
2017-06-15 14:41:54 -04:00
|
|
|
return nil, ErrHTTPProto{OrigErr: err.Error()}
|
|
|
|
}
|
2020-03-24 11:42:33 -04:00
|
|
|
// --insecure was set; fall back to plain HTTP
|
|
|
|
if url := repoEndpoint.endpoint.URL; url != nil && url.Scheme == "https" {
|
|
|
|
url.Scheme = "http"
|
|
|
|
httpTransport, err = c.getHTTPTransportForRepoEndpoint(ctx, repoEndpoint)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2017-06-15 14:41:54 -04:00
|
|
|
}
|
2018-05-17 11:09:10 -04:00
|
|
|
return distributionclient.NewRepository(repoName, repoEndpoint.BaseURL(), httpTransport)
|
2017-06-15 14:41:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) getHTTPTransportForRepoEndpoint(ctx context.Context, repoEndpoint repositoryEndpoint) (http.RoundTripper, error) {
|
|
|
|
httpTransport, err := getHTTPTransport(
|
|
|
|
c.authConfigResolver(ctx, repoEndpoint.info.Index),
|
|
|
|
repoEndpoint.endpoint,
|
|
|
|
repoEndpoint.Name(),
|
cli/registry/client: set actions when authn with token
When using a personal access token, Docker Hub produces an error if actions
are requested beyond the token's allowed actions. This resulted in errors
when using a PAT with limited permissions to do a "docker manifest inspect".
This patch sets actions to "pull" only by default, and requests "push" action
for requests that need it.
To verify:
- create a PAT with limited access (read-only)
- log in with your username and the PAT as password
Before this patch:
docker manifest inspect ubuntu:latest
Get "https://registry-1.docker.io/v2/library/ubuntu/manifests/latest": unauthorized: access token has insufficient scopes
With this patch applied:
docker manifest inspect ubuntu:latest
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 424,
"digest": "sha256:56887c5194fddd8db7e36ced1c16b3569d89f74c801dc8a5adbf48236fb34564",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"size": 424,
"digest": "sha256:c835a4f2a632bc91a2b494e871549f0dd83f2966c780e66435774e77e048ddf0",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v7"
}
}
]
}
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 06:13:48 -04:00
|
|
|
c.userAgent,
|
|
|
|
repoEndpoint.actions,
|
|
|
|
)
|
2017-06-15 14:41:54 -04:00
|
|
|
return httpTransport, errors.Wrap(err, "failed to configure transport")
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetManifest returns an ImageManifest for the reference
|
|
|
|
func (c *client) GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
|
|
|
|
var result manifesttypes.ImageManifest
|
|
|
|
fetch := func(ctx context.Context, repo distribution.Repository, ref reference.Named) (bool, error) {
|
|
|
|
var err error
|
|
|
|
result, err = fetchManifest(ctx, repo, ref)
|
|
|
|
return result.Ref != nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.iterateEndpoints(ctx, ref, fetch)
|
|
|
|
return result, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetManifestList returns a list of ImageManifest for the reference
|
|
|
|
func (c *client) GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) {
|
|
|
|
result := []manifesttypes.ImageManifest{}
|
|
|
|
fetch := func(ctx context.Context, repo distribution.Repository, ref reference.Named) (bool, error) {
|
|
|
|
var err error
|
|
|
|
result, err = fetchList(ctx, repo, ref)
|
|
|
|
return len(result) > 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.iterateEndpoints(ctx, ref, fetch)
|
|
|
|
return result, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func getManifestOptionsFromReference(ref reference.Named) (digest.Digest, []distribution.ManifestServiceOption, error) {
|
|
|
|
if tagged, isTagged := ref.(reference.NamedTagged); isTagged {
|
|
|
|
tag := tagged.Tag()
|
|
|
|
return "", []distribution.ManifestServiceOption{distribution.WithTag(tag)}, nil
|
|
|
|
}
|
|
|
|
if digested, isDigested := ref.(reference.Canonical); isDigested {
|
|
|
|
return digested.Digest(), []distribution.ManifestServiceOption{}, nil
|
|
|
|
}
|
|
|
|
return "", nil, errors.Errorf("%s no tag or digest", ref)
|
|
|
|
}
|