github.com/tompao/docker@v1.9.1/registry/auth.go (about) 1 package registry 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "strings" 9 10 "github.com/Sirupsen/logrus" 11 "github.com/docker/docker/cliconfig" 12 ) 13 14 // Login tries to register/login to the registry server. 15 func Login(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) { 16 // Separates the v2 registry login logic from the v1 logic. 17 if registryEndpoint.Version == APIVersion2 { 18 return loginV2(authConfig, registryEndpoint, "" /* scope */) 19 } 20 return loginV1(authConfig, registryEndpoint) 21 } 22 23 // loginV1 tries to register/login to the v1 registry server. 24 func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) { 25 var ( 26 status string 27 reqBody []byte 28 err error 29 reqStatusCode = 0 30 serverAddress = authConfig.ServerAddress 31 ) 32 33 logrus.Debugf("attempting v1 login to registry endpoint %s", registryEndpoint) 34 35 if serverAddress == "" { 36 return "", fmt.Errorf("Server Error: Server Address not set.") 37 } 38 39 loginAgainstOfficialIndex := serverAddress == IndexServer 40 41 // to avoid sending the server address to the server it should be removed before being marshalled 42 authCopy := *authConfig 43 authCopy.ServerAddress = "" 44 45 jsonBody, err := json.Marshal(authCopy) 46 if err != nil { 47 return "", fmt.Errorf("Config Error: %s", err) 48 } 49 50 // using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status. 51 b := strings.NewReader(string(jsonBody)) 52 req1, err := registryEndpoint.client.Post(serverAddress+"users/", "application/json; charset=utf-8", b) 53 if err != nil { 54 return "", fmt.Errorf("Server Error: %s", err) 55 } 56 reqStatusCode = req1.StatusCode 57 defer req1.Body.Close() 58 reqBody, err = ioutil.ReadAll(req1.Body) 59 if err != nil { 60 return "", fmt.Errorf("Server Error: [%#v] %s", reqStatusCode, err) 61 } 62 63 if reqStatusCode == 201 { 64 if loginAgainstOfficialIndex { 65 status = "Account created. Please use the confirmation link we sent" + 66 " to your e-mail to activate it." 67 } else { 68 // *TODO: Use registry configuration to determine what this says, if anything? 69 status = "Account created. Please see the documentation of the registry " + serverAddress + " for instructions how to activate it." 70 } 71 } else if reqStatusCode == 400 { 72 if string(reqBody) == "\"Username or email already exists\"" { 73 req, err := http.NewRequest("GET", serverAddress+"users/", nil) 74 req.SetBasicAuth(authConfig.Username, authConfig.Password) 75 resp, err := registryEndpoint.client.Do(req) 76 if err != nil { 77 return "", err 78 } 79 defer resp.Body.Close() 80 body, err := ioutil.ReadAll(resp.Body) 81 if err != nil { 82 return "", err 83 } 84 if resp.StatusCode == 200 { 85 return "Login Succeeded", nil 86 } else if resp.StatusCode == 401 { 87 return "", fmt.Errorf("Wrong login/password, please try again") 88 } else if resp.StatusCode == 403 { 89 if loginAgainstOfficialIndex { 90 return "", fmt.Errorf("Login: Account is not Active. Please check your e-mail for a confirmation link.") 91 } 92 // *TODO: Use registry configuration to determine what this says, if anything? 93 return "", fmt.Errorf("Login: Account is not Active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress) 94 } else if resp.StatusCode == 500 { // Issue #14326 95 logrus.Errorf("%s returned status code %d. Response Body :\n%s", req.URL.String(), resp.StatusCode, body) 96 return "", fmt.Errorf("Internal Server Error") 97 } 98 return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, resp.StatusCode, resp.Header) 99 } 100 return "", fmt.Errorf("Registration: %s", reqBody) 101 102 } else if reqStatusCode == 401 { 103 // This case would happen with private registries where /v1/users is 104 // protected, so people can use `docker login` as an auth check. 105 req, err := http.NewRequest("GET", serverAddress+"users/", nil) 106 req.SetBasicAuth(authConfig.Username, authConfig.Password) 107 resp, err := registryEndpoint.client.Do(req) 108 if err != nil { 109 return "", err 110 } 111 defer resp.Body.Close() 112 body, err := ioutil.ReadAll(resp.Body) 113 if err != nil { 114 return "", err 115 } 116 if resp.StatusCode == 200 { 117 return "Login Succeeded", nil 118 } else if resp.StatusCode == 401 { 119 return "", fmt.Errorf("Wrong login/password, please try again") 120 } else { 121 return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, 122 resp.StatusCode, resp.Header) 123 } 124 } else { 125 return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody) 126 } 127 return status, nil 128 } 129 130 // loginV2 tries to login to the v2 registry server. The given registry endpoint has been 131 // pinged or setup with a list of authorization challenges. Each of these challenges are 132 // tried until one of them succeeds. Currently supported challenge schemes are: 133 // HTTP Basic Authorization 134 // Token Authorization with a separate token issuing server 135 // NOTE: the v2 logic does not attempt to create a user account if one doesn't exist. For 136 // now, users should create their account through other means like directly from a web page 137 // served by the v2 registry service provider. Whether this will be supported in the future 138 // is to be determined. 139 func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, scope string) (string, error) { 140 logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint) 141 var ( 142 err error 143 allErrors []error 144 ) 145 146 for _, challenge := range registryEndpoint.AuthChallenges { 147 params := make(map[string]string, len(challenge.Parameters)+1) 148 for k, v := range challenge.Parameters { 149 params[k] = v 150 } 151 params["scope"] = scope 152 logrus.Debugf("trying %q auth challenge with params %v", challenge.Scheme, params) 153 154 switch strings.ToLower(challenge.Scheme) { 155 case "basic": 156 err = tryV2BasicAuthLogin(authConfig, params, registryEndpoint) 157 case "bearer": 158 err = tryV2TokenAuthLogin(authConfig, params, registryEndpoint) 159 default: 160 // Unsupported challenge types are explicitly skipped. 161 err = fmt.Errorf("unsupported auth scheme: %q", challenge.Scheme) 162 } 163 164 if err == nil { 165 return "Login Succeeded", nil 166 } 167 168 logrus.Debugf("error trying auth challenge %q: %s", challenge.Scheme, err) 169 170 allErrors = append(allErrors, err) 171 } 172 173 return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors) 174 } 175 176 func tryV2BasicAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error { 177 req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil) 178 if err != nil { 179 return err 180 } 181 182 req.SetBasicAuth(authConfig.Username, authConfig.Password) 183 184 resp, err := registryEndpoint.client.Do(req) 185 if err != nil { 186 return err 187 } 188 defer resp.Body.Close() 189 190 if resp.StatusCode != http.StatusOK { 191 return fmt.Errorf("basic auth attempt to %s realm %q failed with status: %d %s", registryEndpoint, params["realm"], resp.StatusCode, http.StatusText(resp.StatusCode)) 192 } 193 194 return nil 195 } 196 197 func tryV2TokenAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error { 198 token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint) 199 if err != nil { 200 return err 201 } 202 203 req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil) 204 if err != nil { 205 return err 206 } 207 208 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) 209 210 resp, err := registryEndpoint.client.Do(req) 211 if err != nil { 212 return err 213 } 214 defer resp.Body.Close() 215 216 if resp.StatusCode != http.StatusOK { 217 return fmt.Errorf("token auth attempt to %s realm %q failed with status: %d %s", registryEndpoint, params["realm"], resp.StatusCode, http.StatusText(resp.StatusCode)) 218 } 219 220 return nil 221 } 222 223 // ResolveAuthConfig matches an auth configuration to a server address or a URL 224 func ResolveAuthConfig(config *cliconfig.ConfigFile, index *IndexInfo) cliconfig.AuthConfig { 225 configKey := index.GetAuthConfigKey() 226 // First try the happy case 227 if c, found := config.AuthConfigs[configKey]; found || index.Official { 228 return c 229 } 230 231 convertToHostname := func(url string) string { 232 stripped := url 233 if strings.HasPrefix(url, "http://") { 234 stripped = strings.Replace(url, "http://", "", 1) 235 } else if strings.HasPrefix(url, "https://") { 236 stripped = strings.Replace(url, "https://", "", 1) 237 } 238 239 nameParts := strings.SplitN(stripped, "/", 2) 240 241 return nameParts[0] 242 } 243 244 // Maybe they have a legacy config file, we will iterate the keys converting 245 // them to the new format and testing 246 for registry, ac := range config.AuthConfigs { 247 if configKey == convertToHostname(registry) { 248 return ac 249 } 250 } 251 252 // When all else fails, return an empty auth config 253 return cliconfig.AuthConfig{} 254 }