diff --git a/api/client/cli.go b/api/client/cli.go index 0a1fb2ef8a..600d4cc5a3 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -15,10 +15,10 @@ import ( "text/template" "time" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" - "github.com/docker/docker/registry" ) // DockerCli represents the docker command line client. @@ -28,8 +28,9 @@ type DockerCli struct { proto string // addr holds the client address. addr string - // configFile holds the configuration file (instance of registry.ConfigFile). - configFile *registry.ConfigFile + + // configFile has the client configuration file + configFile *cliconfig.ConfigFile // in holds the input stream and closer (io.ReadCloser) for the client. in io.ReadCloser // out holds the output stream (io.Writer) for the client. @@ -184,7 +185,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a tr.Dial = (&net.Dialer{Timeout: timeout}).Dial } - configFile, e := registry.LoadConfig(filepath.Join(homedir.Get(), ".docker")) + configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker")) if e != nil { fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) } diff --git a/api/client/create.go b/api/client/create.go index d2987a67e4..b0819a05d7 100644 --- a/api/client/create.go +++ b/api/client/create.go @@ -38,7 +38,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error { } // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) + authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index) buf, err := json.Marshal(authConfig) if err != nil { return err diff --git a/api/client/login.go b/api/client/login.go index e8e87fc5e3..d7da1de2b0 100644 --- a/api/client/login.go +++ b/api/client/login.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/docker/docker/api/types" + "github.com/docker/docker/cliconfig" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" "github.com/docker/docker/registry" @@ -56,7 +57,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig, ok := cli.configFile.AuthConfigs[serverAddress] if !ok { - authconfig = registry.AuthConfig{} + authconfig = cliconfig.AuthConfig{} } if username == "" { diff --git a/api/client/push.go b/api/client/push.go index d4fc4c5c9e..dc4266cb75 100644 --- a/api/client/push.go +++ b/api/client/push.go @@ -28,7 +28,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { return err } // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) + authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index) // If we're not using a custom registry, we know the restrictions // applied to repository names and can warn the user in advance. // Custom repositories can have different rules, and we must also diff --git a/api/client/utils.go b/api/client/utils.go index 804dc0c58d..7a52ad25f4 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -21,6 +21,7 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/signal" @@ -119,7 +120,7 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m } func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) { - cmdAttempt := func(authConfig registry.AuthConfig) (io.ReadCloser, int, error) { + cmdAttempt := func(authConfig cliconfig.AuthConfig) (io.ReadCloser, int, error) { buf, err := json.Marshal(authConfig) if err != nil { return nil, -1, err @@ -150,14 +151,14 @@ func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reade } // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(index) + authConfig := registry.ResolveAuthConfig(cli.configFile, index) body, statusCode, err := cmdAttempt(authConfig) if statusCode == http.StatusUnauthorized { fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName) if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil { return nil, -1, err } - authConfig = cli.configFile.ResolveAuthConfig(index) + authConfig = registry.ResolveAuthConfig(cli.configFile, index) return cmdAttempt(authConfig) } return body, statusCode, err diff --git a/api/server/server.go b/api/server/server.go index f1d46ea8b5..87ee6c8478 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -22,6 +22,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/builder" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" @@ -34,7 +35,6 @@ import ( "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/version" - "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -240,7 +240,7 @@ func streamJSON(out *engine.Output, w http.ResponseWriter, flush bool) { } func (s *Server) postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - var config *registry.AuthConfig + var config *cliconfig.AuthConfig err := json.NewDecoder(r.Body).Decode(&config) r.Body.Close() if err != nil { @@ -729,13 +729,13 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w tag = r.Form.Get("tag") ) authEncoded := r.Header.Get("X-Registry-Auth") - authConfig := ®istry.AuthConfig{} + authConfig := &cliconfig.AuthConfig{} if authEncoded != "" { authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty - authConfig = ®istry.AuthConfig{} + authConfig = &cliconfig.AuthConfig{} } } @@ -803,7 +803,7 @@ func (s *Server) getImagesSearch(eng *engine.Engine, version version.Version, w return err } var ( - config *registry.AuthConfig + config *cliconfig.AuthConfig authEncoded = r.Header.Get("X-Registry-Auth") headers = map[string][]string{} ) @@ -813,7 +813,7 @@ func (s *Server) getImagesSearch(eng *engine.Engine, version version.Version, w if err := json.NewDecoder(authJson).Decode(&config); err != nil { // for a search it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty - config = ®istry.AuthConfig{} + config = &cliconfig.AuthConfig{} } } for k, v := range r.Header { @@ -842,7 +842,7 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h if err := parseForm(r); err != nil { return err } - authConfig := ®istry.AuthConfig{} + authConfig := &cliconfig.AuthConfig{} authEncoded := r.Header.Get("X-Registry-Auth") if authEncoded != "" { @@ -850,7 +850,7 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // to increase compatibility to existing api it is defaulting to be empty - authConfig = ®istry.AuthConfig{} + authConfig = &cliconfig.AuthConfig{} } } else { // the old format is supported for compatibility if there was no authConfig header @@ -1279,9 +1279,9 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R } var ( authEncoded = r.Header.Get("X-Registry-Auth") - authConfig = ®istry.AuthConfig{} + authConfig = &cliconfig.AuthConfig{} configFileEncoded = r.Header.Get("X-Registry-Config") - configFile = ®istry.ConfigFile{} + configFile = &cliconfig.ConfigFile{} buildConfig = builder.NewBuildConfig() ) @@ -1294,7 +1294,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty - authConfig = ®istry.AuthConfig{} + authConfig = &cliconfig.AuthConfig{} } } @@ -1303,7 +1303,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty - configFile = ®istry.ConfigFile{} + configFile = &cliconfig.ConfigFile{} } } diff --git a/builder/evaluator.go b/builder/evaluator.go index 7cbba03514..2f9d4ff85b 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -30,13 +30,13 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/builder/command" "github.com/docker/docker/builder/parser" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/tarsum" - "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -99,8 +99,8 @@ type Builder struct { // the final configs of the Dockerfile but dont want the layers disableCommit bool - AuthConfig *registry.AuthConfig - ConfigFile *registry.ConfigFile + AuthConfig *cliconfig.AuthConfig + ConfigFile *cliconfig.ConfigFile // Deprecated, original writer used for ImagePull. To be removed. OutOld io.Writer diff --git a/builder/internals.go b/builder/internals.go index 9574351ca6..731ca84fed 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -36,6 +36,7 @@ import ( "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/tarsum" "github.com/docker/docker/pkg/urlutil" + "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" ) @@ -443,7 +444,7 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { if err != nil { return nil, err } - resolvedAuth := b.ConfigFile.ResolveAuthConfig(repoInfo.Index) + resolvedAuth := registry.ResolveAuthConfig(b.ConfigFile, repoInfo.Index) pullRegistryAuth = &resolvedAuth } diff --git a/builder/job.go b/builder/job.go index 4c6e55b0a9..115d89a4b9 100644 --- a/builder/job.go +++ b/builder/job.go @@ -12,6 +12,7 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/builder/parser" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" @@ -50,8 +51,8 @@ type Config struct { CpuShares int64 CpuSetCpus string CpuSetMems string - AuthConfig *registry.AuthConfig - ConfigFile *registry.ConfigFile + AuthConfig *cliconfig.AuthConfig + ConfigFile *cliconfig.ConfigFile Stdout io.Writer Context io.ReadCloser @@ -76,8 +77,8 @@ func (b *Config) WaitCancelled() <-chan struct{} { func NewBuildConfig() *Config { return &Config{ - AuthConfig: ®istry.AuthConfig{}, - ConfigFile: ®istry.ConfigFile{}, + AuthConfig: &cliconfig.AuthConfig{}, + ConfigFile: &cliconfig.ConfigFile{}, cancelled: make(chan struct{}), } } diff --git a/cliconfig/config.go b/cliconfig/config.go new file mode 100644 index 0000000000..19a92fbd85 --- /dev/null +++ b/cliconfig/config.go @@ -0,0 +1,208 @@ +package cliconfig + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/docker/docker/pkg/homedir" +) + +const ( + // Where we store the config file + CONFIGFILE = "config.json" + OLD_CONFIGFILE = ".dockercfg" + + // This constant is only used for really old config files when the + // URL wasn't saved as part of the config file and it was just + // assumed to be this value. + DEFAULT_INDEXSERVER = "https://index.docker.io/v1/" +) + +var ( + ErrConfigFileMissing = errors.New("The Auth config file is missing") +) + +// Registry Auth Info +type AuthConfig struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Auth string `json:"auth"` + Email string `json:"email"` + ServerAddress string `json:"serveraddress,omitempty"` +} + +// ~/.docker/config.json file info +type ConfigFile struct { + AuthConfigs map[string]AuthConfig `json:"auths"` + HttpHeaders map[string]string `json:"HttpHeaders,omitempty"` + filename string // Note: not serialized - for internal use only +} + +func NewConfigFile(fn string) *ConfigFile { + return &ConfigFile{ + AuthConfigs: make(map[string]AuthConfig), + HttpHeaders: make(map[string]string), + filename: fn, + } +} + +// load up the auth config information and return values +// FIXME: use the internal golang config parser +func Load(configDir string) (*ConfigFile, error) { + if configDir == "" { + configDir = filepath.Join(homedir.Get(), ".docker") + } + + configFile := ConfigFile{ + AuthConfigs: make(map[string]AuthConfig), + filename: filepath.Join(configDir, CONFIGFILE), + } + + // Try happy path first - latest config file + if _, err := os.Stat(configFile.filename); err == nil { + file, err := os.Open(configFile.filename) + if err != nil { + return &configFile, err + } + defer file.Close() + + if err := json.NewDecoder(file).Decode(&configFile); err != nil { + return &configFile, err + } + + for addr, ac := range configFile.AuthConfigs { + ac.Username, ac.Password, err = DecodeAuth(ac.Auth) + if err != nil { + return &configFile, err + } + ac.Auth = "" + ac.ServerAddress = addr + configFile.AuthConfigs[addr] = ac + } + + return &configFile, nil + } else if !os.IsNotExist(err) { + // if file is there but we can't stat it for any reason other + // than it doesn't exist then stop + return &configFile, err + } + + // Can't find latest config file so check for the old one + confFile := filepath.Join(homedir.Get(), OLD_CONFIGFILE) + + if _, err := os.Stat(confFile); err != nil { + return &configFile, nil //missing file is not an error + } + + b, err := ioutil.ReadFile(confFile) + if err != nil { + return &configFile, err + } + + if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil { + arr := strings.Split(string(b), "\n") + if len(arr) < 2 { + return &configFile, fmt.Errorf("The Auth config file is empty") + } + authConfig := AuthConfig{} + origAuth := strings.Split(arr[0], " = ") + if len(origAuth) != 2 { + return &configFile, fmt.Errorf("Invalid Auth config file") + } + authConfig.Username, authConfig.Password, err = DecodeAuth(origAuth[1]) + if err != nil { + return &configFile, err + } + origEmail := strings.Split(arr[1], " = ") + if len(origEmail) != 2 { + return &configFile, fmt.Errorf("Invalid Auth config file") + } + authConfig.Email = origEmail[1] + authConfig.ServerAddress = DEFAULT_INDEXSERVER + configFile.AuthConfigs[DEFAULT_INDEXSERVER] = authConfig + } else { + for k, authConfig := range configFile.AuthConfigs { + authConfig.Username, authConfig.Password, err = DecodeAuth(authConfig.Auth) + if err != nil { + return &configFile, err + } + authConfig.Auth = "" + authConfig.ServerAddress = k + configFile.AuthConfigs[k] = authConfig + } + } + return &configFile, nil +} + +func (configFile *ConfigFile) Save() error { + // Encode sensitive data into a new/temp struct + tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs)) + for k, authConfig := range configFile.AuthConfigs { + authCopy := authConfig + + authCopy.Auth = EncodeAuth(&authCopy) + authCopy.Username = "" + authCopy.Password = "" + authCopy.ServerAddress = "" + tmpAuthConfigs[k] = authCopy + } + + saveAuthConfigs := configFile.AuthConfigs + configFile.AuthConfigs = tmpAuthConfigs + defer func() { configFile.AuthConfigs = saveAuthConfigs }() + + data, err := json.MarshalIndent(configFile, "", "\t") + if err != nil { + return err + } + + if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil { + return err + } + + err = ioutil.WriteFile(configFile.filename, data, 0600) + if err != nil { + return err + } + + return nil +} + +func (config *ConfigFile) Filename() string { + return config.filename +} + +// create a base64 encoded auth string to store in config +func EncodeAuth(authConfig *AuthConfig) string { + authStr := authConfig.Username + ":" + authConfig.Password + msg := []byte(authStr) + encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) + base64.StdEncoding.Encode(encoded, msg) + return string(encoded) +} + +// decode the auth string +func DecodeAuth(authStr string) (string, string, error) { + decLen := base64.StdEncoding.DecodedLen(len(authStr)) + decoded := make([]byte, decLen) + authByte := []byte(authStr) + n, err := base64.StdEncoding.Decode(decoded, authByte) + if err != nil { + return "", "", err + } + if n > decLen { + return "", "", fmt.Errorf("Something went wrong decoding auth config") + } + arr := strings.SplitN(string(decoded), ":", 2) + if len(arr) != 2 { + return "", "", fmt.Errorf("Invalid auth configuration file") + } + password := strings.Trim(arr[1], "\x00") + return arr[0], password, nil +} diff --git a/registry/config_file_test.go b/cliconfig/config_file_test.go similarity index 94% rename from registry/config_file_test.go rename to cliconfig/config_file_test.go index 6f8bd74f53..6d1125f7bf 100644 --- a/registry/config_file_test.go +++ b/cliconfig/config_file_test.go @@ -1,4 +1,4 @@ -package registry +package cliconfig import ( "io/ioutil" @@ -14,7 +14,7 @@ import ( func TestMissingFile(t *testing.T) { tmpHome, _ := ioutil.TempDir("", "config-test") - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on missing file: %q", err) } @@ -36,7 +36,7 @@ func TestSaveFileToDirs(t *testing.T) { tmpHome += "/.docker" - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on missing file: %q", err) } @@ -58,7 +58,7 @@ func TestEmptyFile(t *testing.T) { fn := filepath.Join(tmpHome, CONFIGFILE) ioutil.WriteFile(fn, []byte(""), 0600) - _, err := LoadConfig(tmpHome) + _, err := Load(tmpHome) if err == nil { t.Fatalf("Was supposed to fail") } @@ -69,7 +69,7 @@ func TestEmptyJson(t *testing.T) { fn := filepath.Join(tmpHome, CONFIGFILE) ioutil.WriteFile(fn, []byte("{}"), 0600) - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on empty json file: %q", err) } @@ -104,7 +104,7 @@ func TestOldJson(t *testing.T) { js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}` ioutil.WriteFile(fn, []byte(js), 0600) - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on empty json file: %q", err) } @@ -133,7 +133,7 @@ func TestNewJson(t *testing.T) { js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }` ioutil.WriteFile(fn, []byte(js), 0600) - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on empty json file: %q", err) } diff --git a/graph/pull.go b/graph/pull.go index b62591ffb6..5bfa37316c 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -12,6 +12,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" @@ -23,7 +24,7 @@ import ( type ImagePullConfig struct { Parallel bool MetaHeaders map[string][]string - AuthConfig *registry.AuthConfig + AuthConfig *cliconfig.AuthConfig Json bool OutStream io.Writer } diff --git a/graph/push.go b/graph/push.go index 34db27b910..62ff94e0c4 100644 --- a/graph/push.go +++ b/graph/push.go @@ -12,6 +12,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" @@ -26,7 +27,7 @@ var ErrV2RegistryUnavailable = errors.New("error v2 registry unavailable") type ImagePushConfig struct { MetaHeaders map[string][]string - AuthConfig *registry.AuthConfig + AuthConfig *cliconfig.AuthConfig Tag string Json bool OutStream io.Writer diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 0c412c8629..587712ffca 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -18,6 +18,7 @@ import ( "github.com/Sirupsen/logrus" apiserver "github.com/docker/docker/api/server" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/engine" @@ -28,7 +29,6 @@ import ( "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -135,7 +135,7 @@ func setupBaseImage() { imagePullConfig := &graph.ImagePullConfig{ Parallel: true, OutStream: ioutils.NopWriteCloser(os.Stdout), - AuthConfig: ®istry.AuthConfig{}, + AuthConfig: &cliconfig.AuthConfig{}, } d := getDaemon(eng) if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig); err != nil { diff --git a/registry/auth.go b/registry/auth.go index ef4985abcd..1ac1ca984e 100644 --- a/registry/auth.go +++ b/registry/auth.go @@ -1,51 +1,21 @@ package registry import ( - "encoding/base64" "encoding/json" - "errors" "fmt" "io/ioutil" "net/http" - "os" - "path/filepath" "strings" "sync" "time" "github.com/Sirupsen/logrus" - "github.com/docker/docker/pkg/homedir" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/pkg/requestdecorator" ) -const ( - // Where we store the config file - CONFIGFILE = "config.json" - OLD_CONFIGFILE = ".dockercfg" -) - -var ( - ErrConfigFileMissing = errors.New("The Auth config file is missing") -) - -// Registry Auth Info -type AuthConfig struct { - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - Auth string `json:"auth"` - Email string `json:"email"` - ServerAddress string `json:"serveraddress,omitempty"` -} - -// ~/.docker/config.json file info -type ConfigFile struct { - AuthConfigs map[string]AuthConfig `json:"auths"` - HttpHeaders map[string]string `json:"HttpHeaders,omitempty"` - filename string // Note: not serialized - for internal use only -} - type RequestAuthorization struct { - authConfig *AuthConfig + authConfig *cliconfig.AuthConfig registryEndpoint *Endpoint resource string scope string @@ -56,7 +26,7 @@ type RequestAuthorization struct { tokenExpiration time.Time } -func NewRequestAuthorization(authConfig *AuthConfig, registryEndpoint *Endpoint, resource, scope string, actions []string) *RequestAuthorization { +func NewRequestAuthorization(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, resource, scope string, actions []string) *RequestAuthorization { return &RequestAuthorization{ authConfig: authConfig, registryEndpoint: registryEndpoint, @@ -121,160 +91,8 @@ func (auth *RequestAuthorization) Authorize(req *http.Request) error { return nil } -// create a base64 encoded auth string to store in config -func encodeAuth(authConfig *AuthConfig) string { - authStr := authConfig.Username + ":" + authConfig.Password - msg := []byte(authStr) - encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) - base64.StdEncoding.Encode(encoded, msg) - return string(encoded) -} - -// decode the auth string -func decodeAuth(authStr string) (string, string, error) { - decLen := base64.StdEncoding.DecodedLen(len(authStr)) - decoded := make([]byte, decLen) - authByte := []byte(authStr) - n, err := base64.StdEncoding.Decode(decoded, authByte) - if err != nil { - return "", "", err - } - if n > decLen { - return "", "", fmt.Errorf("Something went wrong decoding auth config") - } - arr := strings.SplitN(string(decoded), ":", 2) - if len(arr) != 2 { - return "", "", fmt.Errorf("Invalid auth configuration file") - } - password := strings.Trim(arr[1], "\x00") - return arr[0], password, nil -} - -// load up the auth config information and return values -// FIXME: use the internal golang config parser -func LoadConfig(configDir string) (*ConfigFile, error) { - if configDir == "" { - configDir = filepath.Join(homedir.Get(), ".docker") - } - - configFile := ConfigFile{ - AuthConfigs: make(map[string]AuthConfig), - filename: filepath.Join(configDir, CONFIGFILE), - } - - // Try happy path first - latest config file - if _, err := os.Stat(configFile.filename); err == nil { - file, err := os.Open(configFile.filename) - if err != nil { - return &configFile, err - } - defer file.Close() - - if err := json.NewDecoder(file).Decode(&configFile); err != nil { - return &configFile, err - } - - for addr, ac := range configFile.AuthConfigs { - ac.Username, ac.Password, err = decodeAuth(ac.Auth) - if err != nil { - return &configFile, err - } - ac.Auth = "" - ac.ServerAddress = addr - configFile.AuthConfigs[addr] = ac - } - - return &configFile, nil - } else if !os.IsNotExist(err) { - // if file is there but we can't stat it for any reason other - // than it doesn't exist then stop - return &configFile, err - } - - // Can't find latest config file so check for the old one - confFile := filepath.Join(homedir.Get(), OLD_CONFIGFILE) - - if _, err := os.Stat(confFile); err != nil { - return &configFile, nil //missing file is not an error - } - - b, err := ioutil.ReadFile(confFile) - if err != nil { - return &configFile, err - } - - if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil { - arr := strings.Split(string(b), "\n") - if len(arr) < 2 { - return &configFile, fmt.Errorf("The Auth config file is empty") - } - authConfig := AuthConfig{} - origAuth := strings.Split(arr[0], " = ") - if len(origAuth) != 2 { - return &configFile, fmt.Errorf("Invalid Auth config file") - } - authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1]) - if err != nil { - return &configFile, err - } - origEmail := strings.Split(arr[1], " = ") - if len(origEmail) != 2 { - return &configFile, fmt.Errorf("Invalid Auth config file") - } - authConfig.Email = origEmail[1] - authConfig.ServerAddress = IndexServerAddress() - // *TODO: Switch to using IndexServerName() instead? - configFile.AuthConfigs[IndexServerAddress()] = authConfig - } else { - for k, authConfig := range configFile.AuthConfigs { - authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) - if err != nil { - return &configFile, err - } - authConfig.Auth = "" - authConfig.ServerAddress = k - configFile.AuthConfigs[k] = authConfig - } - } - return &configFile, nil -} - -func (configFile *ConfigFile) Save() error { - // Encode sensitive data into a new/temp struct - tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs)) - for k, authConfig := range configFile.AuthConfigs { - authCopy := authConfig - - authCopy.Auth = encodeAuth(&authCopy) - authCopy.Username = "" - authCopy.Password = "" - authCopy.ServerAddress = "" - tmpAuthConfigs[k] = authCopy - } - - saveAuthConfigs := configFile.AuthConfigs - configFile.AuthConfigs = tmpAuthConfigs - defer func() { configFile.AuthConfigs = saveAuthConfigs }() - - data, err := json.MarshalIndent(configFile, "", "\t") - if err != nil { - return err - } - - if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil { - return err - } - - err = ioutil.WriteFile(configFile.filename, data, 0600) - if err != nil { - return err - } - - return nil -} - // Login tries to register/login to the registry server. -func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { +func Login(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { // Separates the v2 registry login logic from the v1 logic. if registryEndpoint.Version == APIVersion2 { return loginV2(authConfig, registryEndpoint, factory) @@ -283,7 +101,7 @@ func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestd } // loginV1 tries to register/login to the v1 registry server. -func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { +func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { var ( status string reqBody []byte @@ -396,7 +214,7 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *reques // now, users should create their account through other means like directly from a web page // served by the v2 registry service provider. Whether this will be supported in the future // is to be determined. -func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { +func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint) var ( err error @@ -429,7 +247,7 @@ func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *reques return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors) } -func tryV2BasicAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error { +func tryV2BasicAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error { req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil) if err != nil { return err @@ -450,7 +268,7 @@ func tryV2BasicAuthLogin(authConfig *AuthConfig, params map[string]string, regis return nil } -func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error { +func tryV2TokenAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error { token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint, client, factory) if err != nil { return err @@ -477,7 +295,7 @@ func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, regis } // this method matches a auth configuration to a server address or a url -func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { +func ResolveAuthConfig(config *cliconfig.ConfigFile, index *IndexInfo) cliconfig.AuthConfig { configKey := index.GetAuthConfigKey() // First try the happy case if c, found := config.AuthConfigs[configKey]; found || index.Official { @@ -499,16 +317,12 @@ func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { // Maybe they have a legacy config file, we will iterate the keys converting // them to the new format and testing - for registry, config := range config.AuthConfigs { + for registry, ac := range config.AuthConfigs { if configKey == convertToHostname(registry) { - return config + return ac } } // When all else fails, return an empty auth config - return AuthConfig{} -} - -func (config *ConfigFile) Filename() string { - return config.filename + return cliconfig.AuthConfig{} } diff --git a/registry/auth_test.go b/registry/auth_test.go index b07aa7dbc8..71b963a1f1 100644 --- a/registry/auth_test.go +++ b/registry/auth_test.go @@ -5,14 +5,16 @@ import ( "os" "path/filepath" "testing" + + "github.com/docker/docker/cliconfig" ) func TestEncodeAuth(t *testing.T) { - newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} - authStr := encodeAuth(newAuthConfig) - decAuthConfig := &AuthConfig{} + newAuthConfig := &cliconfig.AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} + authStr := cliconfig.EncodeAuth(newAuthConfig) + decAuthConfig := &cliconfig.AuthConfig{} var err error - decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr) + decAuthConfig.Username, decAuthConfig.Password, err = cliconfig.DecodeAuth(authStr) if err != nil { t.Fatal(err) } @@ -27,19 +29,16 @@ func TestEncodeAuth(t *testing.T) { } } -func setupTempConfigFile() (*ConfigFile, error) { +func setupTempConfigFile() (*cliconfig.ConfigFile, error) { root, err := ioutil.TempDir("", "docker-test-auth") if err != nil { return nil, err } - root = filepath.Join(root, CONFIGFILE) - configFile := &ConfigFile{ - AuthConfigs: make(map[string]AuthConfig), - filename: root, - } + root = filepath.Join(root, cliconfig.CONFIGFILE) + configFile := cliconfig.NewConfigFile(root) for _, registry := range []string{"testIndex", IndexServerAddress()} { - configFile.AuthConfigs[registry] = AuthConfig{ + configFile.AuthConfigs[registry] = cliconfig.AuthConfig{ Username: "docker-user", Password: "docker-pass", Email: "docker@docker.io", @@ -54,7 +53,7 @@ func TestSameAuthDataPostSave(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.filename) + defer os.RemoveAll(configFile.Filename()) err = configFile.Save() if err != nil { @@ -81,7 +80,7 @@ func TestResolveAuthConfigIndexServer(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.filename) + defer os.RemoveAll(configFile.Filename()) indexConfig := configFile.AuthConfigs[IndexServerAddress()] @@ -92,10 +91,10 @@ func TestResolveAuthConfigIndexServer(t *testing.T) { Official: false, } - resolved := configFile.ResolveAuthConfig(officialIndex) + resolved := ResolveAuthConfig(configFile, officialIndex) assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServerAddress()") - resolved = configFile.ResolveAuthConfig(privateIndex) + resolved = ResolveAuthConfig(configFile, privateIndex) assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServerAddress()") } @@ -104,26 +103,26 @@ func TestResolveAuthConfigFullURL(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.filename) + defer os.RemoveAll(configFile.Filename()) - registryAuth := AuthConfig{ + registryAuth := cliconfig.AuthConfig{ Username: "foo-user", Password: "foo-pass", Email: "foo@example.com", } - localAuth := AuthConfig{ + localAuth := cliconfig.AuthConfig{ Username: "bar-user", Password: "bar-pass", Email: "bar@example.com", } - officialAuth := AuthConfig{ + officialAuth := cliconfig.AuthConfig{ Username: "baz-user", Password: "baz-pass", Email: "baz@example.com", } configFile.AuthConfigs[IndexServerAddress()] = officialAuth - expectedAuths := map[string]AuthConfig{ + expectedAuths := map[string]cliconfig.AuthConfig{ "registry.example.com": registryAuth, "localhost:8000": localAuth, "registry.com": localAuth, @@ -160,12 +159,12 @@ func TestResolveAuthConfigFullURL(t *testing.T) { } for _, registry := range registries { configFile.AuthConfigs[registry] = configured - resolved := configFile.ResolveAuthConfig(index) + resolved := ResolveAuthConfig(configFile, index) if resolved.Email != configured.Email { t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email) } delete(configFile.AuthConfigs, registry) - resolved = configFile.ResolveAuthConfig(index) + resolved = ResolveAuthConfig(configFile, index) if resolved.Email == configured.Email { t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email) } diff --git a/registry/registry_test.go b/registry/registry_test.go index a066de9f8e..b4bd4ee724 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/pkg/requestdecorator" ) @@ -20,7 +21,7 @@ const ( ) func spawnTestRegistrySession(t *testing.T) *Session { - authConfig := &AuthConfig{} + authConfig := &cliconfig.AuthConfig{} endpoint, err := NewEndpoint(makeIndex("/v1/")) if err != nil { t.Fatal(err) @@ -33,7 +34,7 @@ func spawnTestRegistrySession(t *testing.T) *Session { } func TestPublicSession(t *testing.T) { - authConfig := &AuthConfig{} + authConfig := &cliconfig.AuthConfig{} getSessionDecorators := func(index *IndexInfo) int { endpoint, err := NewEndpoint(index) diff --git a/registry/service.go b/registry/service.go index cf29732f49..87fc1d076f 100644 --- a/registry/service.go +++ b/registry/service.go @@ -1,5 +1,7 @@ package registry +import "github.com/docker/docker/cliconfig" + type Service struct { Config *ServiceConfig } @@ -15,7 +17,7 @@ func NewService(options *Options) *Service { // Auth contacts the public registry with the provided credentials, // and returns OK if authentication was sucessful. // It can be used to verify the validity of a client's credentials. -func (s *Service) Auth(authConfig *AuthConfig) (string, error) { +func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) { addr := authConfig.ServerAddress if addr == "" { // Use the official registry address if not specified. @@ -35,7 +37,7 @@ func (s *Service) Auth(authConfig *AuthConfig) (string, error) { // Search queries the public registry for images matching the specified // search terms, and returns the results. -func (s *Service) Search(term string, authConfig *AuthConfig, headers map[string][]string) (*SearchResults, error) { +func (s *Service) Search(term string, authConfig *cliconfig.AuthConfig, headers map[string][]string) (*SearchResults, error) { repoInfo, err := s.ResolveRepository(term) if err != nil { return nil, err diff --git a/registry/session.go b/registry/session.go index 940e407e90..dd868a2b35 100644 --- a/registry/session.go +++ b/registry/session.go @@ -18,20 +18,21 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/requestdecorator" "github.com/docker/docker/pkg/tarsum" ) type Session struct { - authConfig *AuthConfig + authConfig *cliconfig.AuthConfig reqFactory *requestdecorator.RequestFactory indexEndpoint *Endpoint jar *cookiejar.Jar timeout TimeoutType } -func NewSession(authConfig *AuthConfig, factory *requestdecorator.RequestFactory, endpoint *Endpoint, timeout bool) (r *Session, err error) { +func NewSession(authConfig *cliconfig.AuthConfig, factory *requestdecorator.RequestFactory, endpoint *Endpoint, timeout bool) (r *Session, err error) { r = &Session{ authConfig: authConfig, indexEndpoint: endpoint, @@ -600,12 +601,12 @@ func (r *Session) SearchRepositories(term string) (*SearchResults, error) { return result, err } -func (r *Session) GetAuthConfig(withPasswd bool) *AuthConfig { +func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig { password := "" if withPasswd { password = r.authConfig.Password } - return &AuthConfig{ + return &cliconfig.AuthConfig{ Username: r.authConfig.Username, Password: password, Email: r.authConfig.Email,