diff options
-rw-r--r-- | go.mod | 4 | ||||
-rw-r--r-- | go.sum | 9 | ||||
-rw-r--r-- | test/e2e/images_test.go | 6 | ||||
-rw-r--r-- | vendor/github.com/containers/common/libimage/filters.go | 56 | ||||
-rw-r--r-- | vendor/github.com/containers/common/libimage/image.go | 20 | ||||
-rw-r--r-- | vendor/github.com/containers/common/pkg/auth/auth.go | 108 | ||||
-rw-r--r-- | vendor/github.com/containers/common/pkg/retry/retry.go | 11 | ||||
-rw-r--r-- | vendor/github.com/containers/image/v5/manifest/manifest.go | 9 | ||||
-rw-r--r-- | vendor/github.com/containers/image/v5/manifest/oci.go | 1 | ||||
-rw-r--r-- | vendor/github.com/containers/image/v5/manifest/oci_index.go | 2 | ||||
-rw-r--r-- | vendor/github.com/containers/image/v5/pkg/docker/config/config.go | 165 | ||||
-rw-r--r-- | vendor/modules.txt | 4 |
12 files changed, 228 insertions, 167 deletions
@@ -12,9 +12,9 @@ require ( github.com/containernetworking/cni v1.0.1 github.com/containernetworking/plugins v1.0.1 github.com/containers/buildah v1.23.1 - github.com/containers/common v0.46.1-0.20211202172647-e77d74bd1976 + github.com/containers/common v0.46.1-0.20211205182721-515a2805e7b9 github.com/containers/conmon v2.0.20+incompatible - github.com/containers/image/v5 v5.17.1-0.20211129144953-4f6d0b45be6c + github.com/containers/image/v5 v5.17.1-0.20211201214147-603ec1341d58 github.com/containers/ocicrypt v1.1.2 github.com/containers/psgo v1.7.1 github.com/containers/storage v1.37.1-0.20211130181259-1a158c89a518 @@ -262,14 +262,13 @@ github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNB github.com/containers/buildah v1.23.1 h1:Tpc9DsRuU+0Oofewpxb6OJVNQjCu7yloN/obUqzfDTY= github.com/containers/buildah v1.23.1/go.mod h1:4WnrN0yrA7ab0ppgunixu2WM1rlD2rG8QLJAKbEkZlQ= github.com/containers/common v0.44.2/go.mod h1:7sdP4vmI5Bm6FPFxb3lvAh1Iktb6tiO1MzjUzhxdoGo= -github.com/containers/common v0.46.1-0.20211202172647-e77d74bd1976 h1:xVOGL69ge+1RirZvnrEl9nATL75udt/Hy2BN8qcmeNY= -github.com/containers/common v0.46.1-0.20211202172647-e77d74bd1976/go.mod h1:J8MxXan58zAWbNgpj4ODPlzsuJnYvNc2zKJCZPIMHYQ= +github.com/containers/common v0.46.1-0.20211205182721-515a2805e7b9 h1:BoPxjWIPX+cn3CGNxd1FC10jcq9TBMk1uvGQEWTXWto= +github.com/containers/common v0.46.1-0.20211205182721-515a2805e7b9/go.mod h1:cxAKmvKoYBl/iLZ1YD/SKnJF7wPR9H6xM/Hu75ZN/oA= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/image/v5 v5.16.0/go.mod h1:XgTpfAPLRGOd1XYyCU5cISFr777bLmOerCSpt/v7+Q4= -github.com/containers/image/v5 v5.17.0/go.mod h1:GnYVusVRFPMMTAAUkrcS8NNSpBp8oyrjOUe04AAmRr4= -github.com/containers/image/v5 v5.17.1-0.20211129144953-4f6d0b45be6c h1:WfMOQlq3CDvVe5ONUGfj9/MajskqUHnbo24j24Xg2ZM= -github.com/containers/image/v5 v5.17.1-0.20211129144953-4f6d0b45be6c/go.mod h1:boW5ckkT0wu9obDEiOIxrtWQmz1znMuHiVMQPcpHnk0= +github.com/containers/image/v5 v5.17.1-0.20211201214147-603ec1341d58 h1:DI6d+6aRBC14mbfnh0eYlHeFBSZQ4adDykrS8F/Awrg= +github.com/containers/image/v5 v5.17.1-0.20211201214147-603ec1341d58/go.mod h1:iUA6fv9NnqIhEaP3+dqo22nKMNkSWCj8d5o8Dju0j1Q= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index efa9f399b..a3cc1ce99 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -141,12 +141,12 @@ var _ = Describe("Podman images", func() { }) It("podman images filter reference", func() { - result := podmanTest.Podman([]string{"images", "-q", "-f", "reference=quay.io*"}) + result := podmanTest.Podman([]string{"images", "-q", "-f", "reference=quay.io/libpod/*"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) Expect(result.OutputToStringArray()).To(HaveLen(7)) - retalpine := podmanTest.Podman([]string{"images", "-f", "reference=a*pine"}) + retalpine := podmanTest.Podman([]string{"images", "-f", "reference=*lpine*"}) retalpine.WaitWithDefaultTimeout() Expect(retalpine).Should(Exit(0)) Expect(retalpine.OutputToStringArray()).To(HaveLen(6)) @@ -155,7 +155,7 @@ var _ = Describe("Podman images", func() { retalpine = podmanTest.Podman([]string{"images", "-f", "reference=alpine"}) retalpine.WaitWithDefaultTimeout() Expect(retalpine).Should(Exit(0)) - Expect(retalpine.OutputToStringArray()).To(HaveLen(6)) + Expect(retalpine.OutputToStringArray()).To(HaveLen(2)) Expect(retalpine.OutputToString()).To(ContainSubstring("alpine")) retnone := podmanTest.Podman([]string{"images", "-q", "-f", "reference=bogus"}) diff --git a/vendor/github.com/containers/common/libimage/filters.go b/vendor/github.com/containers/common/libimage/filters.go index 521af5d06..dfa34dd44 100644 --- a/vendor/github.com/containers/common/libimage/filters.go +++ b/vendor/github.com/containers/common/libimage/filters.go @@ -3,13 +3,14 @@ package libimage import ( "context" "fmt" - "path/filepath" + "path" "strconv" "strings" "time" filtersPkg "github.com/containers/common/pkg/filters" "github.com/containers/common/pkg/timetype" + "github.com/containers/image/v5/docker/reference" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -136,7 +137,7 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp filterFuncs = append(filterFuncs, filterReadOnly(readOnly)) case "reference": - filterFuncs = append(filterFuncs, filterReference(value)) + filterFuncs = append(filterFuncs, filterReferences(value)) case "until": ts, err := timetype.GetTimestamp(value, time.Now()) @@ -158,24 +159,43 @@ func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOp return filterFuncs, nil } -// filterReference creates a reference filter for matching the specified value. -func filterReference(value string) filterFunc { - // Replacing all '/' with '|' so that filepath.Match() can work '|' - // character is not valid in image name, so this is safe. - // - // TODO: this has been copied from Podman and requires some more review - // and especially tests. - filter := fmt.Sprintf("*%s*", value) - filter = strings.ReplaceAll(filter, "/", "|") +// filterReferences creates a reference filter for matching the specified value. +func filterReferences(value string) filterFunc { return func(img *Image) (bool, error) { - if len(value) < 1 { - return true, nil + refs, err := img.NamesReferences() + if err != nil { + return false, err } - for _, name := range img.Names() { - newName := strings.ReplaceAll(name, "/", "|") - match, _ := filepath.Match(filter, newName) - if match { - return true, nil + + for _, ref := range refs { + refString := ref.String() // FQN with tag/digest + candidates := []string{refString} + + // Split the reference into 3 components (twice if diggested/tagged): + // 1) Fully-qualified reference + // 2) Without domain + // 3) Without domain and path + if named, isNamed := ref.(reference.Named); isNamed { + candidates = append(candidates, + reference.Path(named), // path/name without tag/digest (Path() removes it) + refString[strings.LastIndex(refString, "/")+1:]) // name with tag/digest + + trimmedString := reference.TrimNamed(named).String() + if refString != trimmedString { + tagOrDigest := refString[len(trimmedString):] + candidates = append(candidates, + trimmedString, // FQN without tag/digest + reference.Path(named)+tagOrDigest, // path/name with tag/digest + trimmedString[strings.LastIndex(trimmedString, "/")+1:]) // name without tag/digest + } + } + + for _, candidate := range candidates { + // path.Match() is also used by Docker's reference.FamiliarMatch(). + matched, _ := path.Match(value, candidate) + if matched { + return true, nil + } } } return false, nil diff --git a/vendor/github.com/containers/common/libimage/image.go b/vendor/github.com/containers/common/libimage/image.go index 50dbc9725..661ca159b 100644 --- a/vendor/github.com/containers/common/libimage/image.go +++ b/vendor/github.com/containers/common/libimage/image.go @@ -44,6 +44,8 @@ type Image struct { completeInspectData *ImageData // Corresponding OCI image. ociv1Image *ociv1.Image + // Names() parsed into references. + namesReferences []reference.Reference } } @@ -59,6 +61,7 @@ func (i *Image) reload() error { i.cached.partialInspectData = nil i.cached.completeInspectData = nil i.cached.ociv1Image = nil + i.cached.namesReferences = nil return nil } @@ -89,6 +92,23 @@ func (i *Image) Names() []string { return i.storageImage.Names } +// NamesReferences returns Names() as references. +func (i *Image) NamesReferences() ([]reference.Reference, error) { + if i.cached.namesReferences != nil { + return i.cached.namesReferences, nil + } + refs := make([]reference.Reference, 0, len(i.Names())) + for _, name := range i.Names() { + ref, err := reference.Parse(name) + if err != nil { + return nil, err + } + refs = append(refs, ref) + } + i.cached.namesReferences = refs + return refs, nil +} + // StorageImage returns the underlying storage.Image. func (i *Image) StorageImage() *storage.Image { return i.storageImage diff --git a/vendor/github.com/containers/common/pkg/auth/auth.go b/vendor/github.com/containers/common/pkg/auth/auth.go index 0934b155f..ff52b028e 100644 --- a/vendor/github.com/containers/common/pkg/auth/auth.go +++ b/vendor/github.com/containers/common/pkg/auth/auth.go @@ -70,13 +70,10 @@ func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginO systemContext = systemContextWithOptions(systemContext, opts.AuthFile, opts.CertDir) var ( - authConfig types.DockerAuthConfig key, registry string - ref reference.Named err error ) - l := len(args) - switch l { + switch len(args) { case 0: if !opts.AcceptUnspecifiedRegistry { return errors.New("please provide a registry to login to") @@ -88,26 +85,18 @@ func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginO logrus.Debugf("registry not specified, default to the first registry %q from registries.conf", key) case 1: - key, registry, ref, err = parseRegistryArgument(args[0], opts.AcceptRepositories) + key, registry, err = parseCredentialsKey(args[0], opts.AcceptRepositories) if err != nil { return err } default: return errors.New("login accepts only one registry to login to") - } - if ref != nil { - authConfig, err = config.GetCredentialsForRef(systemContext, ref) - if err != nil { - return errors.Wrap(err, "get credentials for repository") - } - } else { - authConfig, err = config.GetCredentials(systemContext, registry) - if err != nil { - return errors.Wrap(err, "get credentials") - } + authConfig, err := config.GetCredentials(systemContext, key) + if err != nil { + return errors.Wrap(err, "get credentials") } if opts.GetLoginSet { @@ -173,39 +162,44 @@ func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginO return errors.Wrapf(err, "authenticating creds for %q", key) } -// parseRegistryArgument verifies the provided arg depending if we accept -// repositories or not. -func parseRegistryArgument(arg string, acceptRepositories bool) (key, registry string, maybeRef reference.Named, err error) { +// parseCredentialsKey turns the provided argument into a valid credential key +// and computes the registry part. +func parseCredentialsKey(arg string, acceptRepositories bool) (key, registry string, err error) { if !acceptRepositories { registry = getRegistryName(arg) key = registry - return key, registry, maybeRef, nil + return key, registry, nil } key = trimScheme(arg) if key != arg { - return key, registry, nil, errors.New("credentials key has https[s]:// prefix") + return "", "", errors.New("credentials key has https[s]:// prefix") } registry = getRegistryName(key) if registry == key { - // We cannot parse a reference from a registry, so we stop here - return key, registry, nil, nil + // The key is not namespaced + return key, registry, nil } - ref, parseErr := reference.ParseNamed(key) - if parseErr != nil { - return key, registry, nil, errors.Wrapf(parseErr, "parse reference from %q", key) + // Sanity-check that the key looks reasonable (e.g. doesn't use invalid characters), + // and does not contain a tag or digest. + // WARNING: ref.Named() MUST NOT be used to compute key, because + // reference.ParseNormalizedNamed() turns docker.io/vendor to docker.io/library/vendor + // Ideally c/image should provide dedicated validation functionality. + ref, err := reference.ParseNormalizedNamed(key) + if err != nil { + return "", "", errors.Wrapf(err, "parse reference from %q", key) } - if !reference.IsNameOnly(ref) { - return key, registry, nil, errors.Errorf("reference %q contains tag or digest", ref.String()) + return "", "", errors.Errorf("reference %q contains tag or digest", ref.String()) + } + refRegistry := reference.Domain(ref) + if refRegistry != registry { // This should never happen, check just to make sure + return "", "", fmt.Errorf("internal error: key %q registry mismatch, %q vs. %q", key, ref, refRegistry) } - maybeRef = ref - registry = reference.Domain(ref) - - return key, registry, maybeRef, nil + return key, registry, nil } // getRegistryName scrubs and parses the input to get the server name @@ -271,15 +265,23 @@ func Logout(systemContext *types.SystemContext, opts *LogoutOptions, args []stri } systemContext = systemContextWithOptions(systemContext, opts.AuthFile, "") + if opts.All { + if len(args) != 0 { + return errors.New("--all takes no arguments") + } + if err := config.RemoveAllAuthentication(systemContext); err != nil { + return err + } + fmt.Fprintln(opts.Stdout, "Removed login credentials for all registries") + return nil + } + var ( key, registry string - ref reference.Named err error ) - if len(args) > 1 { - return errors.New("logout accepts only one registry to logout from") - } - if len(args) == 0 && !opts.All { + switch len(args) { + case 0: if !opts.AcceptUnspecifiedRegistry { return errors.New("please provide a registry to logout from") } @@ -288,23 +290,15 @@ func Logout(systemContext *types.SystemContext, opts *LogoutOptions, args []stri } registry = key logrus.Debugf("registry not specified, default to the first registry %q from registries.conf", key) - } - if len(args) != 0 { - if opts.All { - return errors.New("--all takes no arguments") - } - key, registry, ref, err = parseRegistryArgument(args[0], opts.AcceptRepositories) + + case 1: + key, registry, err = parseCredentialsKey(args[0], opts.AcceptRepositories) if err != nil { return err } - } - if opts.All { - if err := config.RemoveAllAuthentication(systemContext); err != nil { - return err - } - fmt.Fprintln(opts.Stdout, "Removed login credentials for all registries") - return nil + default: + return errors.New("logout accepts only one registry to logout from") } err = config.RemoveAuthentication(systemContext, key) @@ -313,17 +307,9 @@ func Logout(systemContext *types.SystemContext, opts *LogoutOptions, args []stri fmt.Fprintf(opts.Stdout, "Removed login credentials for %s\n", key) return nil case config.ErrNotLoggedIn: - var authConfig types.DockerAuthConfig - if ref != nil { - authConfig, err = config.GetCredentialsForRef(systemContext, ref) - if err != nil { - return errors.Wrap(err, "get credentials for repository") - } - } else { - authConfig, err = config.GetCredentials(systemContext, registry) - if err != nil { - return errors.Wrap(err, "get credentials") - } + authConfig, err := config.GetCredentials(systemContext, key) + if err != nil { + return errors.Wrap(err, "get credentials") } authInvalid := docker.CheckAuth(context.Background(), systemContext, authConfig.Username, authConfig.Password, registry) diff --git a/vendor/github.com/containers/common/pkg/retry/retry.go b/vendor/github.com/containers/common/pkg/retry/retry.go index 43e3a6688..a9573e4e8 100644 --- a/vendor/github.com/containers/common/pkg/retry/retry.go +++ b/vendor/github.com/containers/common/pkg/retry/retry.go @@ -45,8 +45,12 @@ func RetryIfNecessary(ctx context.Context, operation func() error, retryOptions func isRetryable(err error) bool { err = errors.Cause(err) - if err == context.Canceled || err == context.DeadlineExceeded { + switch err { + case nil: return false + case context.Canceled, context.DeadlineExceeded: + return false + default: // continue } type unwrapper interface { @@ -57,7 +61,8 @@ func isRetryable(err error) bool { case errcode.Error: switch e.Code { - case errcode.ErrorCodeUnauthorized, errcodev2.ErrorCodeNameUnknown, errcodev2.ErrorCodeManifestUnknown: + case errcode.ErrorCodeUnauthorized, errcode.ErrorCodeDenied, + errcodev2.ErrorCodeNameUnknown, errcodev2.ErrorCodeManifestUnknown: return false } return true @@ -86,7 +91,7 @@ func isRetryable(err error) bool { } } return true - case unwrapper: + case unwrapper: // Test this last, because various error types might implement .Unwrap() err = e.Unwrap() return isRetryable(err) } diff --git a/vendor/github.com/containers/image/v5/manifest/manifest.go b/vendor/github.com/containers/image/v5/manifest/manifest.go index 4b644f253..2e3e5da15 100644 --- a/vendor/github.com/containers/image/v5/manifest/manifest.go +++ b/vendor/github.com/containers/image/v5/manifest/manifest.go @@ -110,7 +110,8 @@ func GuessMIMEType(manifest []byte) string { } switch meta.MediaType { - case DockerV2Schema2MediaType, DockerV2ListMediaType: // A recognized type. + case DockerV2Schema2MediaType, DockerV2ListMediaType, + imgspecv1.MediaTypeImageManifest, imgspecv1.MediaTypeImageIndex: // A recognized type. return meta.MediaType } // this is the only way the function can return DockerV2Schema1MediaType, and recognizing that is essential for stripping the JWS signatures = computing the correct manifest digest. @@ -121,9 +122,9 @@ func GuessMIMEType(manifest []byte) string { } return DockerV2Schema1MediaType case 2: - // best effort to understand if this is an OCI image since mediaType - // isn't in the manifest for OCI anymore - // for docker v2s2 meta.MediaType should have been set. But given the data, this is our best guess. + // Best effort to understand if this is an OCI image since mediaType + // wasn't in the manifest for OCI image-spec < 1.0.2. + // For docker v2s2 meta.MediaType should have been set. But given the data, this is our best guess. ociMan := struct { Config struct { MediaType string `json:"mediaType"` diff --git a/vendor/github.com/containers/image/v5/manifest/oci.go b/vendor/github.com/containers/image/v5/manifest/oci.go index c4616b965..5892184df 100644 --- a/vendor/github.com/containers/image/v5/manifest/oci.go +++ b/vendor/github.com/containers/image/v5/manifest/oci.go @@ -66,6 +66,7 @@ func OCI1FromComponents(config imgspecv1.Descriptor, layers []imgspecv1.Descript return &OCI1{ imgspecv1.Manifest{ Versioned: specs.Versioned{SchemaVersion: 2}, + MediaType: imgspecv1.MediaTypeImageManifest, Config: config, Layers: layers, }, diff --git a/vendor/github.com/containers/image/v5/manifest/oci_index.go b/vendor/github.com/containers/image/v5/manifest/oci_index.go index 5bec43ff9..c4f11e09c 100644 --- a/vendor/github.com/containers/image/v5/manifest/oci_index.go +++ b/vendor/github.com/containers/image/v5/manifest/oci_index.go @@ -119,6 +119,7 @@ func OCI1IndexFromComponents(components []imgspecv1.Descriptor, annotations map[ index := OCI1Index{ imgspecv1.Index{ Versioned: imgspec.Versioned{SchemaVersion: 2}, + MediaType: imgspecv1.MediaTypeImageIndex, Manifests: make([]imgspecv1.Descriptor, len(components)), Annotations: dupStringStringMap(annotations), }, @@ -195,6 +196,7 @@ func OCI1IndexFromManifest(manifest []byte) (*OCI1Index, error) { index := OCI1Index{ Index: imgspecv1.Index{ Versioned: imgspec.Versioned{SchemaVersion: 2}, + MediaType: imgspecv1.MediaTypeImageIndex, Manifests: []imgspecv1.Descriptor{}, Annotations: make(map[string]string), }, diff --git a/vendor/github.com/containers/image/v5/pkg/docker/config/config.go b/vendor/github.com/containers/image/v5/pkg/docker/config/config.go index 63f5bd53e..642057cd7 100644 --- a/vendor/github.com/containers/image/v5/pkg/docker/config/config.go +++ b/vendor/github.com/containers/image/v5/pkg/docker/config/config.go @@ -54,8 +54,8 @@ var ( // SetCredentials stores the username and password in a location // appropriate for sys and the users’ configuration. -// A valid key can be either a registry hostname or additionally a namespace if -// the AuthenticationFileHelper is being unsed. +// A valid key is a repository, a namespace within a registry, or a registry hostname; +// using forms other than just a registry may fail depending on configuration. // Returns a human-redable description of the location that was updated. // NOTE: The return value is only intended to be read by humans; its form is not an API, // it may change (or new forms can be added) any time. @@ -128,11 +128,15 @@ func GetAllCredentials(sys *types.SystemContext) (map[string]types.DockerAuthCon // possible sources, and then call `GetCredentials` on them. That // prevents us from having to reverse engineer the logic in // `GetCredentials`. - allRegistries := make(map[string]bool) - addRegistry := func(s string) { - allRegistries[s] = true + allKeys := make(map[string]bool) + addKey := func(s string) { + allKeys[s] = true } + // To use GetCredentials, we must at least convert the URL forms into host names. + // While we're at it, we’ll also canonicalize docker.io to the standard format. + normalizedDockerIORegistry := normalizeRegistry("docker.io") + helpers, err := sysregistriesv2.CredentialHelpers(sys) if err != nil { return nil, err @@ -151,10 +155,14 @@ func GetAllCredentials(sys *types.SystemContext) (map[string]types.DockerAuthCon // direct mapping to a registry, so we can just // walk the map. for registry := range auths.CredHelpers { - addRegistry(registry) + addKey(registry) } - for registry := range auths.AuthConfigs { - addRegistry(registry) + for key := range auths.AuthConfigs { + key := normalizeAuthFileKey(key, path.legacyFormat) + if key == normalizedDockerIORegistry { + key = "docker.io" + } + addKey(key) } } // External helpers. @@ -166,7 +174,7 @@ func GetAllCredentials(sys *types.SystemContext) (map[string]types.DockerAuthCon switch errors.Cause(err) { case nil: for registry := range creds { - addRegistry(registry) + addKey(registry) } case exec.ErrNotFound: // It's okay if the helper doesn't exist. @@ -179,8 +187,8 @@ func GetAllCredentials(sys *types.SystemContext) (map[string]types.DockerAuthCon // Now use `GetCredentials` to the specific auth configs for each // previously listed registry. authConfigs := make(map[string]types.DockerAuthConfig) - for registry := range allRegistries { - authConf, err := GetCredentials(sys, registry) + for key := range allKeys { + authConf, err := GetCredentials(sys, key) if err != nil { if credentials.IsErrCredentialsNotFoundMessage(err.Error()) { // Ignore if the credentials could not be found (anymore). @@ -189,7 +197,7 @@ func GetAllCredentials(sys *types.SystemContext) (map[string]types.DockerAuthCon // Note: we rely on the logging in `GetCredentials`. return nil, err } - authConfigs[registry] = authConf + authConfigs[key] = authConf } return authConfigs, nil @@ -230,16 +238,14 @@ func getAuthFilePaths(sys *types.SystemContext, homeDir string) []authPath { return paths } -// GetCredentials returns the registry credentials stored in the -// registry-specific credential helpers or in the default global credentials -// helpers with falling back to using either auth.json -// file or .docker/config.json, including support for OAuth2 and IdentityToken. +// GetCredentials returns the registry credentials matching key, appropriate for +// sys and the users’ configuration. // If an entry is not found, an empty struct is returned. +// A valid key is a repository, a namespace within a registry, or a registry hostname. // -// GetCredentialsForRef should almost always be used in favor of this API to -// allow different credentials for different repositories on the same registry. -func GetCredentials(sys *types.SystemContext, registry string) (types.DockerAuthConfig, error) { - return getCredentialsWithHomeDir(sys, nil, registry, homedir.Get()) +// GetCredentialsForRef should almost always be used in favor of this API. +func GetCredentials(sys *types.SystemContext, key string) (types.DockerAuthConfig, error) { + return getCredentialsWithHomeDir(sys, key, homedir.Get()) } // GetCredentialsForRef returns the registry credentials necessary for @@ -247,30 +253,34 @@ func GetCredentials(sys *types.SystemContext, registry string) (types.DockerAuth // appropriate for sys and the users’ configuration. // If an entry is not found, an empty struct is returned. func GetCredentialsForRef(sys *types.SystemContext, ref reference.Named) (types.DockerAuthConfig, error) { - return getCredentialsWithHomeDir(sys, ref, reference.Domain(ref), homedir.Get()) + return getCredentialsWithHomeDir(sys, ref.Name(), homedir.Get()) } // getCredentialsWithHomeDir is an internal implementation detail of // GetCredentialsForRef and GetCredentials. It exists only to allow testing it // with an artificial home directory. -func getCredentialsWithHomeDir(sys *types.SystemContext, ref reference.Named, registry, homeDir string) (types.DockerAuthConfig, error) { - // consistency check of the ref and registry arguments - if ref != nil && reference.Domain(ref) != registry { - return types.DockerAuthConfig{}, errors.Errorf( - "internal error: provided reference domain %q name does not match registry %q", - reference.Domain(ref), registry, - ) +func getCredentialsWithHomeDir(sys *types.SystemContext, key, homeDir string) (types.DockerAuthConfig, error) { + _, err := validateKey(key) + if err != nil { + return types.DockerAuthConfig{}, err } if sys != nil && sys.DockerAuthConfig != nil { - logrus.Debugf("Returning credentials for %s from DockerAuthConfig", registry) + logrus.Debugf("Returning credentials for %s from DockerAuthConfig", key) return *sys.DockerAuthConfig, nil } + var registry string // We compute this once because it is used in several places. + if firstSlash := strings.IndexRune(key, '/'); firstSlash != -1 { + registry = key[:firstSlash] + } else { + registry = key + } + // Anonymous function to query credentials from auth files. getCredentialsFromAuthFiles := func() (types.DockerAuthConfig, string, error) { for _, path := range getAuthFilePaths(sys, homeDir) { - authConfig, err := findAuthentication(ref, registry, path.path, path.legacyFormat) + authConfig, err := findCredentialsInFile(key, registry, path.path, path.legacyFormat) if err != nil { return types.DockerAuthConfig{}, "", err } @@ -291,57 +301,61 @@ func getCredentialsWithHomeDir(sys *types.SystemContext, ref reference.Named, re for _, helper := range helpers { var ( creds types.DockerAuthConfig + helperKey string credHelperPath string err error ) switch helper { // Special-case the built-in helper for auth files. case sysregistriesv2.AuthenticationFileHelper: + helperKey = key creds, credHelperPath, err = getCredentialsFromAuthFiles() // External helpers. default: + // This intentionally uses "registry", not "key"; we don't support namespaced + // credentials in helpers, but a "registry" is a valid parent of "key". + helperKey = registry creds, err = getAuthFromCredHelper(helper, registry) } if err != nil { - logrus.Debugf("Error looking up credentials for %s in credential helper %s: %v", registry, helper, err) + logrus.Debugf("Error looking up credentials for %s in credential helper %s: %v", helperKey, helper, err) multiErr = multierror.Append(multiErr, err) continue } - if len(creds.Username)+len(creds.Password)+len(creds.IdentityToken) == 0 { - continue - } - msg := fmt.Sprintf("Found credentials for %s in credential helper %s", registry, helper) - if credHelperPath != "" { - msg = fmt.Sprintf("%s in file %s", msg, credHelperPath) + if creds != (types.DockerAuthConfig{}) { + msg := fmt.Sprintf("Found credentials for %s in credential helper %s", helperKey, helper) + if credHelperPath != "" { + msg = fmt.Sprintf("%s in file %s", msg, credHelperPath) + } + logrus.Debug(msg) + return creds, nil } - logrus.Debug(msg) - return creds, nil } if multiErr != nil { return types.DockerAuthConfig{}, multiErr } - logrus.Debugf("No credentials for %s found", registry) + logrus.Debugf("No credentials for %s found", key) return types.DockerAuthConfig{}, nil } -// GetAuthentication returns the registry credentials stored in the -// registry-specific credential helpers or in the default global credentials -// helpers with falling back to using either auth.json file or -// .docker/config.json +// GetAuthentication returns the registry credentials matching key, appropriate for +// sys and the users’ configuration. +// If an entry is not found, an empty struct is returned. +// A valid key is a repository, a namespace within a registry, or a registry hostname. // // Deprecated: This API only has support for username and password. To get the // support for oauth2 in container registry authentication, we added the new -// GetCredentials API. The new API should be used and this API is kept to +// GetCredentialsForRef and GetCredentials API. The new API should be used and this API is kept to // maintain backward compatibility. -func GetAuthentication(sys *types.SystemContext, registry string) (string, string, error) { - return getAuthenticationWithHomeDir(sys, registry, homedir.Get()) +func GetAuthentication(sys *types.SystemContext, key string) (string, string, error) { + return getAuthenticationWithHomeDir(sys, key, homedir.Get()) } // getAuthenticationWithHomeDir is an internal implementation detail of GetAuthentication, // it exists only to allow testing it with an artificial home directory. -func getAuthenticationWithHomeDir(sys *types.SystemContext, registry, homeDir string) (string, string, error) { - auth, err := getCredentialsWithHomeDir(sys, nil, registry, homeDir) +func getAuthenticationWithHomeDir(sys *types.SystemContext, key, homeDir string) (string, string, error) { + auth, err := getCredentialsWithHomeDir(sys, key, homeDir) if err != nil { return "", "", err } @@ -353,8 +367,8 @@ func getAuthenticationWithHomeDir(sys *types.SystemContext, registry, homeDir st // RemoveAuthentication removes credentials for `key` from all possible // sources such as credential helpers and auth files. -// A valid key can be either a registry hostname or additionally a namespace if -// the AuthenticationFileHelper is being unsed. +// A valid key is a repository, a namespace within a registry, or a registry hostname; +// using forms other than just a registry may fail depending on configuration. func RemoveAuthentication(sys *types.SystemContext, key string) error { isNamespaced, err := validateKey(key) if err != nil { @@ -639,26 +653,27 @@ func deleteAuthFromCredHelper(credHelper, registry string) error { return helperclient.Erase(p, registry) } -// findAuthentication looks for auth of registry in path. If ref is -// not nil, then it will be taken into account when looking up the -// authentication credentials. -func findAuthentication(ref reference.Named, registry, path string, legacyFormat bool) (types.DockerAuthConfig, error) { +// findCredentialsInFile looks for credentials matching "key" +// (which is "registry" or a namespace in "registry") in "path". +func findCredentialsInFile(key, registry, path string, legacyFormat bool) (types.DockerAuthConfig, error) { auths, err := readJSONFile(path, legacyFormat) if err != nil { return types.DockerAuthConfig{}, errors.Wrapf(err, "reading JSON file %q", path) } // First try cred helpers. They should always be normalized. + // This intentionally uses "registry", not "key"; we don't support namespaced + // credentials in helpers. if ch, exists := auths.CredHelpers[registry]; exists { return getAuthFromCredHelper(ch, registry) } - // Support for different paths in auth. + // Support sub-registry namespaces in auth. // (This is not a feature of ~/.docker/config.json; we support it even for // those files as an extension.) var keys []string - if !legacyFormat && ref != nil { - keys = authKeysForRef(ref) + if !legacyFormat { + keys = authKeysForKey(key) } else { keys = []string{registry} } @@ -689,23 +704,22 @@ func findAuthentication(ref reference.Named, registry, path string, legacyFormat return types.DockerAuthConfig{}, nil } -// authKeysForRef returns the valid paths for a provided reference. For example, -// when given a reference "quay.io/repo/ns/image:tag", then it would return +// authKeysForKey returns the keys matching a provided auth file key, in order +// from the best match to worst. For example, +// when given a repository key "quay.io/repo/ns/image", it returns // - quay.io/repo/ns/image // - quay.io/repo/ns // - quay.io/repo // - quay.io -func authKeysForRef(ref reference.Named) (res []string) { - name := ref.Name() - +func authKeysForKey(key string) (res []string) { for { - res = append(res, name) + res = append(res, key) - lastSlash := strings.LastIndex(name, "/") + lastSlash := strings.LastIndex(key, "/") if lastSlash == -1 { break } - name = name[:lastSlash] + key = key[:lastSlash] } return res @@ -759,11 +773,24 @@ func normalizeRegistry(registry string) string { // validateKey verifies that the input key does not have a prefix that is not // allowed and returns an indicator if the key is namespaced. -func validateKey(key string) (isNamespaced bool, err error) { +func validateKey(key string) (bool, error) { if strings.HasPrefix(key, "http://") || strings.HasPrefix(key, "https://") { - return isNamespaced, errors.Errorf("key %s contains http[s]:// prefix", key) + return false, errors.Errorf("key %s contains http[s]:// prefix", key) + } + + // Ideally this should only accept explicitly valid keys, compare + // validateIdentityRemappingPrefix. For now, just reject values that look + // like tagged or digested values. + if strings.ContainsRune(key, '@') { + return false, fmt.Errorf(`key %s contains a '@' character`, key) } + firstSlash := strings.IndexRune(key, '/') + isNamespaced := firstSlash != -1 + // Reject host/repo:tag, but allow localhost:5000 and localhost:5000/foo. + if isNamespaced && strings.ContainsRune(key[firstSlash+1:], ':') { + return false, fmt.Errorf(`key %s contains a ':' character after host[:port]`, key) + } // check if the provided key contains one or more subpaths. - return strings.ContainsRune(key, '/'), nil + return isNamespaced, nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 04f15620c..df8cf40c8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -106,7 +106,7 @@ github.com/containers/buildah/pkg/rusage github.com/containers/buildah/pkg/sshagent github.com/containers/buildah/pkg/util github.com/containers/buildah/util -# github.com/containers/common v0.46.1-0.20211202172647-e77d74bd1976 +# github.com/containers/common v0.46.1-0.20211205182721-515a2805e7b9 ## explicit github.com/containers/common/libimage github.com/containers/common/libimage/manifests @@ -142,7 +142,7 @@ github.com/containers/common/version # github.com/containers/conmon v2.0.20+incompatible ## explicit github.com/containers/conmon/runner/config -# github.com/containers/image/v5 v5.17.1-0.20211129144953-4f6d0b45be6c +# github.com/containers/image/v5 v5.17.1-0.20211201214147-603ec1341d58 ## explicit github.com/containers/image/v5/copy github.com/containers/image/v5/directory |