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