123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- /*
- Copyright 2014 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package credentialprovider
- import (
- "net"
- "net/url"
- "path/filepath"
- "sort"
- "strings"
- "k8s.io/klog"
- "k8s.io/apimachinery/pkg/util/sets"
- )
- // DockerKeyring tracks a set of docker registry credentials, maintaining a
- // reverse index across the registry endpoints. A registry endpoint is made
- // up of a host (e.g. registry.example.com), but it may also contain a path
- // (e.g. registry.example.com/foo) This index is important for two reasons:
- // - registry endpoints may overlap, and when this happens we must find the
- // most specific match for a given image
- // - iterating a map does not yield predictable results
- type DockerKeyring interface {
- Lookup(image string) ([]LazyAuthConfiguration, bool)
- }
- // BasicDockerKeyring is a trivial map-backed implementation of DockerKeyring
- type BasicDockerKeyring struct {
- index []string
- creds map[string][]LazyAuthConfiguration
- }
- // lazyDockerKeyring is an implementation of DockerKeyring that lazily
- // materializes its dockercfg based on a set of dockerConfigProviders.
- type lazyDockerKeyring struct {
- Providers []DockerConfigProvider
- }
- // AuthConfig contains authorization information for connecting to a Registry
- // This type mirrors "github.com/docker/docker/api/types.AuthConfig"
- type AuthConfig struct {
- Username string `json:"username,omitempty"`
- Password string `json:"password,omitempty"`
- Auth string `json:"auth,omitempty"`
- // Email is an optional value associated with the username.
- // This field is deprecated and will be removed in a later
- // version of docker.
- Email string `json:"email,omitempty"`
- ServerAddress string `json:"serveraddress,omitempty"`
- // IdentityToken is used to authenticate the user and get
- // an access token for the registry.
- IdentityToken string `json:"identitytoken,omitempty"`
- // RegistryToken is a bearer token to be sent to a registry
- RegistryToken string `json:"registrytoken,omitempty"`
- }
- // LazyAuthConfiguration wraps dockertypes.AuthConfig, potentially deferring its
- // binding. If Provider is non-nil, it will be used to obtain new credentials
- // by calling LazyProvide() on it.
- type LazyAuthConfiguration struct {
- AuthConfig
- Provider DockerConfigProvider
- }
- func DockerConfigEntryToLazyAuthConfiguration(ident DockerConfigEntry) LazyAuthConfiguration {
- return LazyAuthConfiguration{
- AuthConfig: AuthConfig{
- Username: ident.Username,
- Password: ident.Password,
- Email: ident.Email,
- },
- }
- }
- func (dk *BasicDockerKeyring) Add(cfg DockerConfig) {
- if dk.index == nil {
- dk.index = make([]string, 0)
- dk.creds = make(map[string][]LazyAuthConfiguration)
- }
- for loc, ident := range cfg {
- var creds LazyAuthConfiguration
- if ident.Provider != nil {
- creds = LazyAuthConfiguration{
- Provider: ident.Provider,
- }
- } else {
- creds = DockerConfigEntryToLazyAuthConfiguration(ident)
- }
- value := loc
- if !strings.HasPrefix(value, "https://") && !strings.HasPrefix(value, "http://") {
- value = "https://" + value
- }
- parsed, err := url.Parse(value)
- if err != nil {
- klog.Errorf("Entry %q in dockercfg invalid (%v), ignoring", loc, err)
- continue
- }
- // The docker client allows exact matches:
- // foo.bar.com/namespace
- // Or hostname matches:
- // foo.bar.com
- // It also considers /v2/ and /v1/ equivalent to the hostname
- // See ResolveAuthConfig in docker/registry/auth.go.
- effectivePath := parsed.Path
- if strings.HasPrefix(effectivePath, "/v2/") || strings.HasPrefix(effectivePath, "/v1/") {
- effectivePath = effectivePath[3:]
- }
- var key string
- if (len(effectivePath) > 0) && (effectivePath != "/") {
- key = parsed.Host + effectivePath
- } else {
- key = parsed.Host
- }
- dk.creds[key] = append(dk.creds[key], creds)
- dk.index = append(dk.index, key)
- }
- eliminateDupes := sets.NewString(dk.index...)
- dk.index = eliminateDupes.List()
- // Update the index used to identify which credentials to use for a given
- // image. The index is reverse-sorted so more specific paths are matched
- // first. For example, if for the given image "quay.io/coreos/etcd",
- // credentials for "quay.io/coreos" should match before "quay.io".
- sort.Sort(sort.Reverse(sort.StringSlice(dk.index)))
- }
- const (
- defaultRegistryHost = "index.docker.io"
- defaultRegistryKey = defaultRegistryHost + "/v1/"
- )
- // isDefaultRegistryMatch determines whether the given image will
- // pull from the default registry (DockerHub) based on the
- // characteristics of its name.
- func isDefaultRegistryMatch(image string) bool {
- parts := strings.SplitN(image, "/", 2)
- if len(parts[0]) == 0 {
- return false
- }
- if len(parts) == 1 {
- // e.g. library/ubuntu
- return true
- }
- if parts[0] == "docker.io" || parts[0] == "index.docker.io" {
- // resolve docker.io/image and index.docker.io/image as default registry
- return true
- }
- // From: http://blog.docker.com/2013/07/how-to-use-your-own-registry/
- // Docker looks for either a “.” (domain separator) or “:” (port separator)
- // to learn that the first part of the repository name is a location and not
- // a user name.
- return !strings.ContainsAny(parts[0], ".:")
- }
- // url.Parse require a scheme, but ours don't have schemes. Adding a
- // scheme to make url.Parse happy, then clear out the resulting scheme.
- func parseSchemelessUrl(schemelessUrl string) (*url.URL, error) {
- parsed, err := url.Parse("https://" + schemelessUrl)
- if err != nil {
- return nil, err
- }
- // clear out the resulting scheme
- parsed.Scheme = ""
- return parsed, nil
- }
- // split the host name into parts, as well as the port
- func splitUrl(url *url.URL) (parts []string, port string) {
- host, port, err := net.SplitHostPort(url.Host)
- if err != nil {
- // could not parse port
- host, port = url.Host, ""
- }
- return strings.Split(host, "."), port
- }
- // overloaded version of urlsMatch, operating on strings instead of URLs.
- func urlsMatchStr(glob string, target string) (bool, error) {
- globUrl, err := parseSchemelessUrl(glob)
- if err != nil {
- return false, err
- }
- targetUrl, err := parseSchemelessUrl(target)
- if err != nil {
- return false, err
- }
- return urlsMatch(globUrl, targetUrl)
- }
- // check whether the given target url matches the glob url, which may have
- // glob wild cards in the host name.
- //
- // Examples:
- // globUrl=*.docker.io, targetUrl=blah.docker.io => match
- // globUrl=*.docker.io, targetUrl=not.right.io => no match
- //
- // Note that we don't support wildcards in ports and paths yet.
- func urlsMatch(globUrl *url.URL, targetUrl *url.URL) (bool, error) {
- globUrlParts, globPort := splitUrl(globUrl)
- targetUrlParts, targetPort := splitUrl(targetUrl)
- if globPort != targetPort {
- // port doesn't match
- return false, nil
- }
- if len(globUrlParts) != len(targetUrlParts) {
- // host name does not have the same number of parts
- return false, nil
- }
- if !strings.HasPrefix(targetUrl.Path, globUrl.Path) {
- // the path of the credential must be a prefix
- return false, nil
- }
- for k, globUrlPart := range globUrlParts {
- targetUrlPart := targetUrlParts[k]
- matched, err := filepath.Match(globUrlPart, targetUrlPart)
- if err != nil {
- return false, err
- }
- if !matched {
- // glob mismatch for some part
- return false, nil
- }
- }
- // everything matches
- return true, nil
- }
- // Lookup implements the DockerKeyring method for fetching credentials based on image name.
- // Multiple credentials may be returned if there are multiple potentially valid credentials
- // available. This allows for rotation.
- func (dk *BasicDockerKeyring) Lookup(image string) ([]LazyAuthConfiguration, bool) {
- // range over the index as iterating over a map does not provide a predictable ordering
- ret := []LazyAuthConfiguration{}
- for _, k := range dk.index {
- // both k and image are schemeless URLs because even though schemes are allowed
- // in the credential configurations, we remove them in Add.
- if matched, _ := urlsMatchStr(k, image); matched {
- ret = append(ret, dk.creds[k]...)
- }
- }
- if len(ret) > 0 {
- return ret, true
- }
- // Use credentials for the default registry if provided, and appropriate
- if isDefaultRegistryMatch(image) {
- if auth, ok := dk.creds[defaultRegistryHost]; ok {
- return auth, true
- }
- }
- return []LazyAuthConfiguration{}, false
- }
- // Lookup implements the DockerKeyring method for fetching credentials
- // based on image name.
- func (dk *lazyDockerKeyring) Lookup(image string) ([]LazyAuthConfiguration, bool) {
- keyring := &BasicDockerKeyring{}
- for _, p := range dk.Providers {
- keyring.Add(p.Provide(image))
- }
- return keyring.Lookup(image)
- }
- type FakeKeyring struct {
- auth []LazyAuthConfiguration
- ok bool
- }
- func (f *FakeKeyring) Lookup(image string) ([]LazyAuthConfiguration, bool) {
- return f.auth, f.ok
- }
- // UnionDockerKeyring delegates to a set of keyrings.
- type UnionDockerKeyring []DockerKeyring
- func (k UnionDockerKeyring) Lookup(image string) ([]LazyAuthConfiguration, bool) {
- authConfigs := []LazyAuthConfiguration{}
- for _, subKeyring := range k {
- if subKeyring == nil {
- continue
- }
- currAuthResults, _ := subKeyring.Lookup(image)
- authConfigs = append(authConfigs, currAuthResults...)
- }
- return authConfigs, (len(authConfigs) > 0)
- }
|