change dockercfg to json and support multiple auth remote

This commit is contained in:
Victor Vieux 2013-07-23 15:04:31 +00:00
parent 9a15db21a6
commit 3bae188b8d
3 changed files with 87 additions and 127 deletions

58
api.go
View File

@ -81,54 +81,15 @@ func getBoolParam(value string) (bool, error) {
return ret, nil return ret, nil
} }
func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if version > 1.1 {
w.WriteHeader(http.StatusNotFound)
return nil
}
authConfig, err := auth.LoadConfig(srv.runtime.root)
if err != nil {
if err != auth.ErrConfigFileMissing {
return err
}
authConfig = &auth.AuthConfig{}
}
b, err := json.Marshal(&auth.AuthConfig{Username: authConfig.Username, Email: authConfig.Email})
if err != nil {
return err
}
writeJSON(w, b)
return nil
}
func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
authConfig := &auth.AuthConfig{} authConfig := &auth.AuthConfig{}
err := json.NewDecoder(r.Body).Decode(authConfig) err := json.NewDecoder(r.Body).Decode(authConfig)
if err != nil { if err != nil {
return err return err
} }
status := "" status, err := auth.Login(authConfig)
if version > 1.1 { if err != nil {
status, err = auth.Login(authConfig, false) return err
if err != nil {
return err
}
} else {
localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
if err != nil {
if err != auth.ErrConfigFileMissing {
return err
}
}
if authConfig.Username == localAuthConfig.Username {
authConfig.Password = localAuthConfig.Password
}
newAuthConfig := auth.NewAuthConfig(authConfig.Username, authConfig.Password, authConfig.Email, srv.runtime.root)
status, err = auth.Login(newAuthConfig, true)
if err != nil {
return err
}
} }
if status != "" { if status != "" {
b, err := json.Marshal(&APIAuth{Status: status}) b, err := json.Marshal(&APIAuth{Status: status})
@ -429,16 +390,8 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht
func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
authConfig := &auth.AuthConfig{} authConfig := &auth.AuthConfig{}
if version > 1.1 { if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { return err
return err
}
} else {
localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
if err != nil && err != auth.ErrConfigFileMissing {
return err
}
authConfig = localAuthConfig
} }
if err := parseForm(r); err != nil { if err := parseForm(r); err != nil {
return err return err
@ -854,7 +807,6 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{ m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
"GET": { "GET": {
"/auth": getAuth,
"/version": getVersion, "/version": getVersion,
"/info": getInfo, "/info": getInfo,
"/images/json": getImagesJSON, "/images/json": getImagesJSON,

View File

@ -25,19 +25,15 @@ var (
) )
type AuthConfig struct { type AuthConfig struct {
Username string `json:"username"` Username string `json:"username,omitempty"`
Password string `json:"password"` Password string `json:"password,omitempty"`
Auth string `json:"auth"`
Email string `json:"email"` Email string `json:"email"`
rootPath string
} }
func NewAuthConfig(username, password, email, rootPath string) *AuthConfig { type ConfigFile struct {
return &AuthConfig{ Configs map[string]AuthConfig `json:"configs,omitempty"`
Username: username, rootPath string
Password: password,
Email: email,
rootPath: rootPath,
}
} }
func IndexServerAddress() string { func IndexServerAddress() string {
@ -54,61 +50,83 @@ func encodeAuth(authConfig *AuthConfig) string {
} }
// decode the auth string // decode the auth string
func decodeAuth(authStr string) (*AuthConfig, error) { func decodeAuth(authStr string) (string, string, error) {
decLen := base64.StdEncoding.DecodedLen(len(authStr)) decLen := base64.StdEncoding.DecodedLen(len(authStr))
decoded := make([]byte, decLen) decoded := make([]byte, decLen)
authByte := []byte(authStr) authByte := []byte(authStr)
n, err := base64.StdEncoding.Decode(decoded, authByte) n, err := base64.StdEncoding.Decode(decoded, authByte)
if err != nil { if err != nil {
return nil, err return "", "", err
} }
if n > decLen { if n > decLen {
return nil, fmt.Errorf("Something went wrong decoding auth config") return "", "", fmt.Errorf("Something went wrong decoding auth config")
} }
arr := strings.Split(string(decoded), ":") arr := strings.Split(string(decoded), ":")
if len(arr) != 2 { if len(arr) != 2 {
return nil, fmt.Errorf("Invalid auth configuration file") return "", "", fmt.Errorf("Invalid auth configuration file")
} }
password := strings.Trim(arr[1], "\x00") password := strings.Trim(arr[1], "\x00")
return &AuthConfig{Username: arr[0], Password: password}, nil return arr[0], password, nil
} }
// load up the auth config information and return values // load up the auth config information and return values
// FIXME: use the internal golang config parser // FIXME: use the internal golang config parser
func LoadConfig(rootPath string) (*AuthConfig, error) { func LoadConfig(rootPath string) (*ConfigFile, error) {
configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath}
confFile := path.Join(rootPath, CONFIGFILE) confFile := path.Join(rootPath, CONFIGFILE)
if _, err := os.Stat(confFile); err != nil { if _, err := os.Stat(confFile); err != nil {
return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing return &configFile, ErrConfigFileMissing
} }
b, err := ioutil.ReadFile(confFile) b, err := ioutil.ReadFile(confFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
arr := strings.Split(string(b), "\n")
if len(arr) < 2 { if err := json.Unmarshal(b, &configFile.Configs); err != nil {
return nil, fmt.Errorf("The Auth config file is empty") arr := strings.Split(string(b), "\n")
if len(arr) < 2 {
return nil, fmt.Errorf("The Auth config file is empty")
}
authConfig := AuthConfig{}
origAuth := strings.Split(arr[0], " = ")
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
if err != nil {
return nil, err
}
origEmail := strings.Split(arr[1], " = ")
authConfig.Email = origEmail[1]
configFile.Configs[IndexServerAddress()] = authConfig
} else {
for k, authConfig := range configFile.Configs {
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
if err != nil {
return nil, err
}
configFile.Configs[k] = authConfig
}
} }
origAuth := strings.Split(arr[0], " = ") return &configFile, nil
origEmail := strings.Split(arr[1], " = ")
authConfig, err := decodeAuth(origAuth[1])
if err != nil {
return nil, err
}
authConfig.Email = origEmail[1]
authConfig.rootPath = rootPath
return authConfig, nil
} }
// save the auth config // save the auth config
func SaveConfig(authConfig *AuthConfig) error { func SaveConfig(configFile *ConfigFile) error {
confFile := path.Join(authConfig.rootPath, CONFIGFILE) confFile := path.Join(configFile.rootPath, CONFIGFILE)
if len(authConfig.Email) == 0 { if len(configFile.Configs) == 0 {
os.Remove(confFile) os.Remove(confFile)
return nil return nil
} }
lines := "auth = " + encodeAuth(authConfig) + "\n" + "email = " + authConfig.Email + "\n" for k, authConfig := range configFile.Configs {
b := []byte(lines) authConfig.Auth = encodeAuth(&authConfig)
err := ioutil.WriteFile(confFile, b, 0600) authConfig.Username = ""
authConfig.Password = ""
configFile.Configs[k] = authConfig
}
b, err := json.Marshal(configFile.Configs)
if err != nil {
return err
}
err = ioutil.WriteFile(confFile, b, 0600)
if err != nil { if err != nil {
return err return err
} }
@ -116,8 +134,7 @@ func SaveConfig(authConfig *AuthConfig) error {
} }
// try to register/login to the registry server // try to register/login to the registry server
func Login(authConfig *AuthConfig, store bool) (string, error) { func Login(authConfig *AuthConfig) (string, error) {
storeConfig := false
client := &http.Client{} client := &http.Client{}
reqStatusCode := 0 reqStatusCode := 0
var status string var status string
@ -143,7 +160,6 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
if reqStatusCode == 201 { if reqStatusCode == 201 {
status = "Account created. Please use the confirmation link we sent" + status = "Account created. Please use the confirmation link we sent" +
" to your e-mail to activate it." " to your e-mail to activate it."
storeConfig = true
} else if reqStatusCode == 403 { } else if reqStatusCode == 403 {
return "", fmt.Errorf("Login: Your account hasn't been activated. " + return "", fmt.Errorf("Login: Your account hasn't been activated. " +
"Please check your e-mail for a confirmation link.") "Please check your e-mail for a confirmation link.")
@ -162,14 +178,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
} }
if resp.StatusCode == 200 { if resp.StatusCode == 200 {
status = "Login Succeeded" status = "Login Succeeded"
storeConfig = true
} else if resp.StatusCode == 401 { } else if resp.StatusCode == 401 {
if store {
authConfig.Email = ""
if err := SaveConfig(authConfig); err != nil {
return "", err
}
}
return "", fmt.Errorf("Wrong login/password, please try again") return "", fmt.Errorf("Wrong login/password, please try again")
} else { } else {
return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
@ -181,10 +190,5 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
} else { } else {
return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody) return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody)
} }
if storeConfig && store {
if err := SaveConfig(authConfig); err != nil {
return "", err
}
}
return status, nil return status, nil
} }

View File

@ -313,16 +313,21 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
email string email string
) )
authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()]
if !ok {
authconfig = auth.AuthConfig{}
}
if *flUsername == "" { if *flUsername == "" {
fmt.Fprintf(cli.out, "Username (%s): ", cli.authConfig.Username) fmt.Fprintf(cli.out, "Username (%s): ", authconfig.Username)
username = readAndEchoString(cli.in, cli.out) username = readAndEchoString(cli.in, cli.out)
if username == "" { if username == "" {
username = cli.authConfig.Username username = authconfig.Username
} }
} else { } else {
username = *flUsername username = *flUsername
} }
if username != cli.authConfig.Username { if username != authconfig.Username {
if *flPassword == "" { if *flPassword == "" {
fmt.Fprintf(cli.out, "Password: ") fmt.Fprintf(cli.out, "Password: ")
password = readString(cli.in, cli.out) password = readString(cli.in, cli.out)
@ -334,31 +339,30 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
} }
if *flEmail == "" { if *flEmail == "" {
fmt.Fprintf(cli.out, "Email (%s): ", cli.authConfig.Email) fmt.Fprintf(cli.out, "Email (%s): ", authconfig.Email)
email = readAndEchoString(cli.in, cli.out) email = readAndEchoString(cli.in, cli.out)
if email == "" { if email == "" {
email = cli.authConfig.Email email = authconfig.Email
} }
} else { } else {
email = *flEmail email = *flEmail
} }
} else { } else {
password = cli.authConfig.Password password = authconfig.Password
email = cli.authConfig.Email email = authconfig.Email
} }
if oldState != nil { if oldState != nil {
term.RestoreTerminal(cli.terminalFd, oldState) term.RestoreTerminal(cli.terminalFd, oldState)
} }
cli.authConfig.Username = username authconfig.Username = username
cli.authConfig.Password = password authconfig.Password = password
cli.authConfig.Email = email authconfig.Email = email
cli.configFile.Configs[auth.IndexServerAddress()] = authconfig
body, statusCode, err := cli.call("POST", "/auth", cli.authConfig) body, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[auth.IndexServerAddress()])
if statusCode == 401 { if statusCode == 401 {
cli.authConfig.Username = "" delete(cli.configFile.Configs, auth.IndexServerAddress())
cli.authConfig.Password = "" auth.SaveConfig(cli.configFile)
cli.authConfig.Email = ""
auth.SaveConfig(cli.authConfig)
return err return err
} }
if err != nil { if err != nil {
@ -368,10 +372,10 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
var out2 APIAuth var out2 APIAuth
err = json.Unmarshal(body, &out2) err = json.Unmarshal(body, &out2)
if err != nil { if err != nil {
auth.LoadConfig(os.Getenv("HOME")) cli.configFile, _ = auth.LoadConfig(os.Getenv("HOME"))
return err return err
} }
auth.SaveConfig(cli.authConfig) auth.SaveConfig(cli.configFile)
if out2.Status != "" { if out2.Status != "" {
fmt.Fprintf(cli.out, "%s\n", out2.Status) fmt.Fprintf(cli.out, "%s\n", out2.Status)
} }
@ -802,10 +806,10 @@ func (cli *DockerCli) CmdPush(args ...string) error {
// Custom repositories can have different rules, and we must also // Custom repositories can have different rules, and we must also
// allow pushing by image ID. // allow pushing by image ID.
if len(strings.SplitN(name, "/", 2)) == 1 { if len(strings.SplitN(name, "/", 2)) == 1 {
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.authConfig.Username, name) return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.configFile.Configs[auth.IndexServerAddress()].Username, name)
} }
buf, err := json.Marshal(cli.authConfig) buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()])
if err != nil { if err != nil {
return err return err
} }
@ -1410,11 +1414,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
func (cli *DockerCli) checkIfLogged(action string) error { func (cli *DockerCli) checkIfLogged(action string) error {
// If condition AND the login failed // If condition AND the login failed
if cli.authConfig.Username == "" { if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" {
if err := cli.CmdLogin(""); err != nil { if err := cli.CmdLogin(""); err != nil {
return err return err
} }
if cli.authConfig.Username == "" { if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" {
return fmt.Errorf("Please login prior to %s. ('docker login')", action) return fmt.Errorf("Please login prior to %s. ('docker login')", action)
} }
} }
@ -1670,11 +1674,11 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc
err = out err = out
} }
authConfig, _ := auth.LoadConfig(os.Getenv("HOME")) configFile, _ := auth.LoadConfig(os.Getenv("HOME"))
return &DockerCli{ return &DockerCli{
proto: proto, proto: proto,
addr: addr, addr: addr,
authConfig: authConfig, configFile: configFile,
in: in, in: in,
out: out, out: out,
err: err, err: err,
@ -1686,7 +1690,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc
type DockerCli struct { type DockerCli struct {
proto string proto string
addr string addr string
authConfig *auth.AuthConfig configFile *auth.ConfigFile
in io.ReadCloser in io.ReadCloser
out io.Writer out io.Writer
err io.Writer err io.Writer