From 906b2790803760e209872bdc9cfdce7d4b658730 Mon Sep 17 00:00:00 2001 From: tamnd Date: Fri, 14 Aug 2015 16:08:08 +0700 Subject: [PATCH] Support new docker config format for private registries --- pkg/api/types.go | 9 +++++ pkg/credentialprovider/config.go | 48 +++++++++++++++++++++++++- pkg/credentialprovider/config_test.go | 29 ++++++++++++++++ pkg/credentialprovider/keyring.go | 9 ++++- pkg/kubelet/dockertools/docker_test.go | 12 +++++++ 5 files changed, 105 insertions(+), 2 deletions(-) diff --git a/pkg/api/types.go b/pkg/api/types.go index a84e86201da..0d2aefc1ce5 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -2073,6 +2073,15 @@ const ( // DockerConfigKey is the key of the required data for SecretTypeDockercfg secrets DockerConfigKey = ".dockercfg" + + // SecretTypeDockerConfigJson contains a dockercfg file that follows the same format rules as ~/.docker/config.json + // + // Required fields: + // - Secret.Data[".dockerconfigjson"] - a serialized ~/.docker/config.json file + SecretTypeDockerConfigJson SecretType = "kubernetes.io/dockerconfigjson" + + // DockerConfigJsonKey is the key of the required data for SecretTypeDockerConfigJson secrets + DockerConfigJsonKey = ".dockerconfigjson" ) type SecretList struct { diff --git a/pkg/credentialprovider/config.go b/pkg/credentialprovider/config.go index 6ba2adb6719..f03bd26c318 100644 --- a/pkg/credentialprovider/config.go +++ b/pkg/credentialprovider/config.go @@ -30,6 +30,13 @@ import ( "github.com/golang/glog" ) +// DockerConfigJson represents ~/.docker/config.json file info +// see https://github.com/docker/docker/pull/12009 +type DockerConfigJson struct { + Auths DockerConfig `json:"auths"` + HttpHeaders map[string]string `json:"HttpHeaders,omitempty"` +} + // DockerConfig represents the config file used by the docker CLI. // This config that represents the credentials that should be used // when pulling images from specific image repositories. @@ -47,8 +54,11 @@ var ( workingDirPath = "" homeDirPath = os.Getenv("HOME") rootDirPath = "/" + homeJsonDirPath = filepath.Join(homeDirPath, ".docker") + rootJsonDirPath = filepath.Join(rootDirPath, ".docker") - configFileName = ".dockercfg" + configFileName = ".dockercfg" + configJsonFileName = "config.json" ) func SetPreferredDockercfgPath(path string) { @@ -64,6 +74,32 @@ func GetPreferredDockercfgPath() string { } func ReadDockerConfigFile() (cfg DockerConfig, err error) { + // Try happy path first - latest config file + dockerConfigJsonLocations := []string{GetPreferredDockercfgPath(), workingDirPath, homeJsonDirPath, rootJsonDirPath} + for _, configPath := range dockerConfigJsonLocations { + absDockerConfigFileLocation, err := filepath.Abs(filepath.Join(configPath, configJsonFileName)) + if err != nil { + glog.Errorf("while trying to canonicalize %s: %v", configPath, err) + continue + } + glog.V(4).Infof("looking for .docker/config.json at %s", absDockerConfigFileLocation) + contents, err := ioutil.ReadFile(absDockerConfigFileLocation) + if os.IsNotExist(err) { + continue + } + if err != nil { + glog.V(4).Infof("while trying to read %s: %v", absDockerConfigFileLocation, err) + continue + } + cfg, err := readDockerConfigJsonFileFromBytes(contents) + if err == nil { + glog.V(4).Infof("found .docker/config.json at %s", absDockerConfigFileLocation) + return cfg, nil + } + } + glog.V(4).Infof("couldn't find valid .docker/config.json after checking in %v", dockerConfigJsonLocations) + + // Can't find latest config file so check for the old one dockerConfigFileLocations := []string{GetPreferredDockercfgPath(), workingDirPath, homeDirPath, rootDirPath} for _, configPath := range dockerConfigFileLocations { absDockerConfigFileLocation, err := filepath.Abs(filepath.Join(configPath, configFileName)) @@ -147,6 +183,16 @@ func readDockerConfigFileFromBytes(contents []byte) (cfg DockerConfig, err error return } +func readDockerConfigJsonFileFromBytes(contents []byte) (cfg DockerConfig, err error) { + var cfgJson DockerConfigJson + if err = json.Unmarshal(contents, &cfgJson); err != nil { + glog.Errorf("while trying to parse blob %q: %v", contents, err) + return nil, err + } + cfg = cfgJson.Auths + return +} + // dockerConfigEntryWithAuth is used solely for deserializing the Auth field // into a dockerConfigEntry during JSON deserialization. type dockerConfigEntryWithAuth struct { diff --git a/pkg/credentialprovider/config_test.go b/pkg/credentialprovider/config_test.go index ea8c4c6e877..587879fe931 100644 --- a/pkg/credentialprovider/config_test.go +++ b/pkg/credentialprovider/config_test.go @@ -22,6 +22,35 @@ import ( "testing" ) +func TestDockerConfigJsonJSONDecode(t *testing.T) { + input := []byte(`{"auths": {"http://foo.example.com":{"username": "foo", "password": "bar", "email": "foo@example.com"}, "http://bar.example.com":{"username": "bar", "password": "baz", "email": "bar@example.com"}}}`) + + expect := DockerConfigJson{ + Auths: DockerConfig(map[string]DockerConfigEntry{ + "http://foo.example.com": { + Username: "foo", + Password: "bar", + Email: "foo@example.com", + }, + "http://bar.example.com": { + Username: "bar", + Password: "baz", + Email: "bar@example.com", + }, + }), + } + + var output DockerConfigJson + err := json.Unmarshal(input, &output) + if err != nil { + t.Errorf("Received unexpected error: %v", err) + } + + if !reflect.DeepEqual(expect, output) { + t.Errorf("Received unexpected output. Expected %#v, got %#v", expect, output) + } +} + func TestDockerConfigJSONDecode(t *testing.T) { input := []byte(`{"http://foo.example.com":{"username": "foo", "password": "bar", "email": "foo@example.com"}, "http://bar.example.com":{"username": "bar", "password": "baz", "email": "bar@example.com"}}`) diff --git a/pkg/credentialprovider/keyring.go b/pkg/credentialprovider/keyring.go index 87a8c862e79..7014e8ec219 100644 --- a/pkg/credentialprovider/keyring.go +++ b/pkg/credentialprovider/keyring.go @@ -277,7 +277,14 @@ func (k *unionDockerKeyring) Lookup(image string) ([]docker.AuthConfiguration, b func MakeDockerKeyring(passedSecrets []api.Secret, defaultKeyring DockerKeyring) (DockerKeyring, error) { passedCredentials := []DockerConfig{} for _, passedSecret := range passedSecrets { - if dockercfgBytes, dockercfgExists := passedSecret.Data[api.DockerConfigKey]; (passedSecret.Type == api.SecretTypeDockercfg) && dockercfgExists && (len(dockercfgBytes) > 0) { + if dockerConfigJsonBytes, dockerConfigJsonExists := passedSecret.Data[api.DockerConfigJsonKey]; (passedSecret.Type == api.SecretTypeDockerConfigJson) && dockerConfigJsonExists && (len(dockerConfigJsonBytes) > 0) { + dockerConfigJson := DockerConfigJson{} + if err := json.Unmarshal(dockerConfigJsonBytes, &dockerConfigJson); err != nil { + return nil, err + } + + passedCredentials = append(passedCredentials, dockerConfigJson.Auths) + } else if dockercfgBytes, dockercfgExists := passedSecret.Data[api.DockerConfigKey]; (passedSecret.Type == api.SecretTypeDockercfg) && dockercfgExists && (len(dockercfgBytes) > 0) { dockercfg := DockerConfig{} if err := json.Unmarshal(dockercfgBytes, &dockercfg); err != nil { return nil, err diff --git a/pkg/kubelet/dockertools/docker_test.go b/pkg/kubelet/dockertools/docker_test.go index e42d68a4b89..6f5c3388df1 100644 --- a/pkg/kubelet/dockertools/docker_test.go +++ b/pkg/kubelet/dockertools/docker_test.go @@ -283,6 +283,12 @@ func TestPullWithSecrets(t *testing.T) { t.Errorf("unexpected error: %v", err) } + dockerConfigJson := map[string]map[string]map[string]string{"auths": dockerCfg} + dockerConfigJsonContent, err := json.Marshal(dockerConfigJson) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + tests := map[string]struct { imageName string passedSecrets []api.Secret @@ -313,6 +319,12 @@ func TestPullWithSecrets(t *testing.T) { credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{"index.docker.io/v1/": {"built-in", "password", "email"}}), []string{`ubuntu:latest using {"username":"passed-user","password":"passed-password","email":"passed-email"}`}, }, + "builtin keyring secrets, but use passed with new docker config": { + "ubuntu", + []api.Secret{{Type: api.SecretTypeDockerConfigJson, Data: map[string][]byte{api.DockerConfigJsonKey: dockerConfigJsonContent}}}, + credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{"index.docker.io/v1/": {"built-in", "password", "email"}}), + []string{`ubuntu:latest using {"username":"passed-user","password":"passed-password","email":"passed-email"}`}, + }, } for _, test := range tests { builtInKeyRing := &credentialprovider.BasicDockerKeyring{}