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
}
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 {
authConfig := &auth.AuthConfig{}
err := json.NewDecoder(r.Body).Decode(authConfig)
if err != nil {
return err
}
status := ""
if version > 1.1 {
status, err = auth.Login(authConfig, false)
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
}
status, err := auth.Login(authConfig)
if err != nil {
return err
}
if 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 {
authConfig := &auth.AuthConfig{}
if version > 1.1 {
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
return err
}
} else {
localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
if err != nil && err != auth.ErrConfigFileMissing {
return err
}
authConfig = localAuthConfig
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
return err
}
if err := parseForm(r); err != nil {
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{
"GET": {
"/auth": getAuth,
"/version": getVersion,
"/info": getInfo,
"/images/json": getImagesJSON,

View file

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

View file

@ -313,16 +313,21 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
email string
)
authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()]
if !ok {
authconfig = auth.AuthConfig{}
}
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)
if username == "" {
username = cli.authConfig.Username
username = authconfig.Username
}
} else {
username = *flUsername
}
if username != cli.authConfig.Username {
if username != authconfig.Username {
if *flPassword == "" {
fmt.Fprintf(cli.out, "Password: ")
password = readString(cli.in, cli.out)
@ -334,31 +339,30 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
}
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)
if email == "" {
email = cli.authConfig.Email
email = authconfig.Email
}
} else {
email = *flEmail
}
} else {
password = cli.authConfig.Password
email = cli.authConfig.Email
password = authconfig.Password
email = authconfig.Email
}
if oldState != nil {
term.RestoreTerminal(cli.terminalFd, oldState)
}
cli.authConfig.Username = username
cli.authConfig.Password = password
cli.authConfig.Email = email
authconfig.Username = username
authconfig.Password = password
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 {
cli.authConfig.Username = ""
cli.authConfig.Password = ""
cli.authConfig.Email = ""
auth.SaveConfig(cli.authConfig)
delete(cli.configFile.Configs, auth.IndexServerAddress())
auth.SaveConfig(cli.configFile)
return err
}
if err != nil {
@ -368,10 +372,10 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
var out2 APIAuth
err = json.Unmarshal(body, &out2)
if err != nil {
auth.LoadConfig(os.Getenv("HOME"))
cli.configFile, _ = auth.LoadConfig(os.Getenv("HOME"))
return err
}
auth.SaveConfig(cli.authConfig)
auth.SaveConfig(cli.configFile)
if 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
// allow pushing by image ID.
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 {
return err
}
@ -1410,11 +1414,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
func (cli *DockerCli) checkIfLogged(action string) error {
// If condition AND the login failed
if cli.authConfig.Username == "" {
if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" {
if err := cli.CmdLogin(""); err != nil {
return err
}
if cli.authConfig.Username == "" {
if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" {
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
}
authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
configFile, _ := auth.LoadConfig(os.Getenv("HOME"))
return &DockerCli{
proto: proto,
addr: addr,
authConfig: authConfig,
configFile: configFile,
in: in,
out: out,
err: err,
@ -1686,7 +1690,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc
type DockerCli struct {
proto string
addr string
authConfig *auth.AuthConfig
configFile *auth.ConfigFile
in io.ReadCloser
out io.Writer
err io.Writer