github.com/enmand/kubernetes@v1.2.0-alpha.0/pkg/credentialprovider/keyring.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors All rights reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package credentialprovider
    18  
    19  import (
    20  	"encoding/json"
    21  	"net"
    22  	"net/url"
    23  	"path/filepath"
    24  	"sort"
    25  	"strings"
    26  
    27  	docker "github.com/fsouza/go-dockerclient"
    28  	"github.com/golang/glog"
    29  
    30  	"k8s.io/kubernetes/pkg/api"
    31  	"k8s.io/kubernetes/pkg/util/sets"
    32  )
    33  
    34  // DockerKeyring tracks a set of docker registry credentials, maintaining a
    35  // reverse index across the registry endpoints. A registry endpoint is made
    36  // up of a host (e.g. registry.example.com), but it may also contain a path
    37  // (e.g. registry.example.com/foo) This index is important for two reasons:
    38  // - registry endpoints may overlap, and when this happens we must find the
    39  //   most specific match for a given image
    40  // - iterating a map does not yield predictable results
    41  type DockerKeyring interface {
    42  	Lookup(image string) ([]docker.AuthConfiguration, bool)
    43  }
    44  
    45  // BasicDockerKeyring is a trivial map-backed implementation of DockerKeyring
    46  type BasicDockerKeyring struct {
    47  	index []string
    48  	creds map[string][]docker.AuthConfiguration
    49  }
    50  
    51  // lazyDockerKeyring is an implementation of DockerKeyring that lazily
    52  // materializes its dockercfg based on a set of dockerConfigProviders.
    53  type lazyDockerKeyring struct {
    54  	Providers []DockerConfigProvider
    55  }
    56  
    57  func (dk *BasicDockerKeyring) Add(cfg DockerConfig) {
    58  	if dk.index == nil {
    59  		dk.index = make([]string, 0)
    60  		dk.creds = make(map[string][]docker.AuthConfiguration)
    61  	}
    62  	for loc, ident := range cfg {
    63  
    64  		creds := docker.AuthConfiguration{
    65  			Username: ident.Username,
    66  			Password: ident.Password,
    67  			Email:    ident.Email,
    68  		}
    69  
    70  		parsed, err := url.Parse(loc)
    71  		if err != nil {
    72  			glog.Errorf("Entry %q in dockercfg invalid (%v), ignoring", loc, err)
    73  			continue
    74  		}
    75  
    76  		// The docker client allows exact matches:
    77  		//    foo.bar.com/namespace
    78  		// Or hostname matches:
    79  		//    foo.bar.com
    80  		// See ResolveAuthConfig in docker/registry/auth.go.
    81  		if parsed.Host != "" {
    82  			// NOTE: foo.bar.com comes through as Path.
    83  			dk.creds[parsed.Host] = append(dk.creds[parsed.Host], creds)
    84  			dk.index = append(dk.index, parsed.Host)
    85  		}
    86  		if (len(parsed.Path) > 0) && (parsed.Path != "/") {
    87  			key := parsed.Host + parsed.Path
    88  			dk.creds[key] = append(dk.creds[key], creds)
    89  			dk.index = append(dk.index, key)
    90  		}
    91  	}
    92  
    93  	eliminateDupes := sets.NewString(dk.index...)
    94  	dk.index = eliminateDupes.List()
    95  
    96  	// Update the index used to identify which credentials to use for a given
    97  	// image. The index is reverse-sorted so more specific paths are matched
    98  	// first. For example, if for the given image "quay.io/coreos/etcd",
    99  	// credentials for "quay.io/coreos" should match before "quay.io".
   100  	sort.Sort(sort.Reverse(sort.StringSlice(dk.index)))
   101  }
   102  
   103  const defaultRegistryHost = "index.docker.io/v1/"
   104  
   105  // isDefaultRegistryMatch determines whether the given image will
   106  // pull from the default registry (DockerHub) based on the
   107  // characteristics of its name.
   108  func isDefaultRegistryMatch(image string) bool {
   109  	parts := strings.SplitN(image, "/", 2)
   110  
   111  	if len(parts[0]) == 0 {
   112  		return false
   113  	}
   114  
   115  	if len(parts) == 1 {
   116  		// e.g. library/ubuntu
   117  		return true
   118  	}
   119  
   120  	if parts[0] == "docker.io" || parts[0] == "index.docker.io" {
   121  		// resolve docker.io/image and index.docker.io/image as default registry
   122  		return true
   123  	}
   124  
   125  	// From: http://blog.docker.com/2013/07/how-to-use-your-own-registry/
   126  	// Docker looks for either a “.” (domain separator) or “:” (port separator)
   127  	// to learn that the first part of the repository name is a location and not
   128  	// a user name.
   129  	return !strings.ContainsAny(parts[0], ".:")
   130  }
   131  
   132  // url.Parse require a scheme, but ours don't have schemes.  Adding a
   133  // scheme to make url.Parse happy, then clear out the resulting scheme.
   134  func parseSchemelessUrl(schemelessUrl string) (*url.URL, error) {
   135  	parsed, err := url.Parse("https://" + schemelessUrl)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  	// clear out the resulting scheme
   140  	parsed.Scheme = ""
   141  	return parsed, nil
   142  }
   143  
   144  // split the host name into parts, as well as the port
   145  func splitUrl(url *url.URL) (parts []string, port string) {
   146  	host, port, err := net.SplitHostPort(url.Host)
   147  	if err != nil {
   148  		// could not parse port
   149  		host, port = url.Host, ""
   150  	}
   151  	return strings.Split(host, "."), port
   152  }
   153  
   154  // overloaded version of urlsMatch, operating on strings instead of URLs.
   155  func urlsMatchStr(glob string, target string) (bool, error) {
   156  	globUrl, err := parseSchemelessUrl(glob)
   157  	if err != nil {
   158  		return false, err
   159  	}
   160  	targetUrl, err := parseSchemelessUrl(target)
   161  	if err != nil {
   162  		return false, err
   163  	}
   164  	return urlsMatch(globUrl, targetUrl)
   165  }
   166  
   167  // check whether the given target url matches the glob url, which may have
   168  // glob wild cards in the host name.
   169  //
   170  // Examples:
   171  //    globUrl=*.docker.io, targetUrl=blah.docker.io => match
   172  //    globUrl=*.docker.io, targetUrl=not.right.io   => no match
   173  //
   174  // Note that we don't support wildcards in ports and paths yet.
   175  func urlsMatch(globUrl *url.URL, targetUrl *url.URL) (bool, error) {
   176  	globUrlParts, globPort := splitUrl(globUrl)
   177  	targetUrlParts, targetPort := splitUrl(targetUrl)
   178  	if globPort != targetPort {
   179  		// port doesn't match
   180  		return false, nil
   181  	}
   182  	if len(globUrlParts) != len(targetUrlParts) {
   183  		// host name does not have the same number of parts
   184  		return false, nil
   185  	}
   186  	if !strings.HasPrefix(targetUrl.Path, globUrl.Path) {
   187  		// the path of the credential must be a prefix
   188  		return false, nil
   189  	}
   190  	for k, globUrlPart := range globUrlParts {
   191  		targetUrlPart := targetUrlParts[k]
   192  		matched, err := filepath.Match(globUrlPart, targetUrlPart)
   193  		if err != nil {
   194  			return false, err
   195  		}
   196  		if !matched {
   197  			// glob mismatch for some part
   198  			return false, nil
   199  		}
   200  	}
   201  	// everything matches
   202  	return true, nil
   203  }
   204  
   205  // Lookup implements the DockerKeyring method for fetching credentials based on image name.
   206  // Multiple credentials may be returned if there are multiple potentially valid credentials
   207  // available.  This allows for rotation.
   208  func (dk *BasicDockerKeyring) Lookup(image string) ([]docker.AuthConfiguration, bool) {
   209  	// range over the index as iterating over a map does not provide a predictable ordering
   210  	ret := []docker.AuthConfiguration{}
   211  	for _, k := range dk.index {
   212  		// both k and image are schemeless URLs because even though schemes are allowed
   213  		// in the credential configurations, we remove them in Add.
   214  		if matched, _ := urlsMatchStr(k, image); !matched {
   215  			continue
   216  		}
   217  
   218  		ret = append(ret, dk.creds[k]...)
   219  	}
   220  
   221  	if len(ret) > 0 {
   222  		return ret, true
   223  	}
   224  
   225  	// Use credentials for the default registry if provided, and appropriate
   226  	if auth, ok := dk.creds[defaultRegistryHost]; ok && isDefaultRegistryMatch(image) {
   227  		return auth, true
   228  	}
   229  
   230  	return []docker.AuthConfiguration{}, false
   231  }
   232  
   233  // Lookup implements the DockerKeyring method for fetching credentials
   234  // based on image name.
   235  func (dk *lazyDockerKeyring) Lookup(image string) ([]docker.AuthConfiguration, bool) {
   236  	keyring := &BasicDockerKeyring{}
   237  
   238  	for _, p := range dk.Providers {
   239  		keyring.Add(p.Provide())
   240  	}
   241  
   242  	return keyring.Lookup(image)
   243  }
   244  
   245  type FakeKeyring struct {
   246  	auth []docker.AuthConfiguration
   247  	ok   bool
   248  }
   249  
   250  func (f *FakeKeyring) Lookup(image string) ([]docker.AuthConfiguration, bool) {
   251  	return f.auth, f.ok
   252  }
   253  
   254  // unionDockerKeyring delegates to a set of keyrings.
   255  type unionDockerKeyring struct {
   256  	keyrings []DockerKeyring
   257  }
   258  
   259  func (k *unionDockerKeyring) Lookup(image string) ([]docker.AuthConfiguration, bool) {
   260  	authConfigs := []docker.AuthConfiguration{}
   261  
   262  	for _, subKeyring := range k.keyrings {
   263  		if subKeyring == nil {
   264  			continue
   265  		}
   266  
   267  		currAuthResults, _ := subKeyring.Lookup(image)
   268  		authConfigs = append(authConfigs, currAuthResults...)
   269  	}
   270  
   271  	return authConfigs, (len(authConfigs) > 0)
   272  }
   273  
   274  // MakeDockerKeyring inspects the passedSecrets to see if they contain any DockerConfig secrets.  If they do,
   275  // then a DockerKeyring is built based on every hit and unioned with the defaultKeyring.
   276  // If they do not, then the default keyring is returned
   277  func MakeDockerKeyring(passedSecrets []api.Secret, defaultKeyring DockerKeyring) (DockerKeyring, error) {
   278  	passedCredentials := []DockerConfig{}
   279  	for _, passedSecret := range passedSecrets {
   280  		if dockerConfigJsonBytes, dockerConfigJsonExists := passedSecret.Data[api.DockerConfigJsonKey]; (passedSecret.Type == api.SecretTypeDockerConfigJson) && dockerConfigJsonExists && (len(dockerConfigJsonBytes) > 0) {
   281  			dockerConfigJson := DockerConfigJson{}
   282  			if err := json.Unmarshal(dockerConfigJsonBytes, &dockerConfigJson); err != nil {
   283  				return nil, err
   284  			}
   285  
   286  			passedCredentials = append(passedCredentials, dockerConfigJson.Auths)
   287  		} else if dockercfgBytes, dockercfgExists := passedSecret.Data[api.DockerConfigKey]; (passedSecret.Type == api.SecretTypeDockercfg) && dockercfgExists && (len(dockercfgBytes) > 0) {
   288  			dockercfg := DockerConfig{}
   289  			if err := json.Unmarshal(dockercfgBytes, &dockercfg); err != nil {
   290  				return nil, err
   291  			}
   292  
   293  			passedCredentials = append(passedCredentials, dockercfg)
   294  		}
   295  	}
   296  
   297  	if len(passedCredentials) > 0 {
   298  		basicKeyring := &BasicDockerKeyring{}
   299  		for _, currCredentials := range passedCredentials {
   300  			basicKeyring.Add(currCredentials)
   301  		}
   302  		return &unionDockerKeyring{[]DockerKeyring{basicKeyring, defaultKeyring}}, nil
   303  	}
   304  
   305  	return defaultKeyring, nil
   306  }