| /* |
| 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 { |
| continue |
| } |
| |
| 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()) |
| } |
| |
| 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) |
| } |