github.com/kobeld/docker@v1.12.0-rc1/registry/auth.go (about) 1 package registry 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "net/http" 7 "net/url" 8 "strings" 9 "time" 10 11 "github.com/Sirupsen/logrus" 12 "github.com/docker/distribution/registry/client/auth" 13 "github.com/docker/distribution/registry/client/transport" 14 "github.com/docker/engine-api/types" 15 registrytypes "github.com/docker/engine-api/types/registry" 16 ) 17 18 const ( 19 // AuthClientID is used the ClientID used for the token server 20 AuthClientID = "docker" 21 ) 22 23 // loginV1 tries to register/login to the v1 registry server. 24 func loginV1(authConfig *types.AuthConfig, apiEndpoint APIEndpoint, userAgent string) (string, string, error) { 25 registryEndpoint, err := apiEndpoint.ToV1Endpoint(userAgent, nil) 26 if err != nil { 27 return "", "", err 28 } 29 30 serverAddress := registryEndpoint.String() 31 32 logrus.Debugf("attempting v1 login to registry endpoint %s", serverAddress) 33 34 if serverAddress == "" { 35 return "", "", fmt.Errorf("Server Error: Server Address not set.") 36 } 37 38 loginAgainstOfficialIndex := serverAddress == IndexServer 39 40 req, err := http.NewRequest("GET", serverAddress+"users/", nil) 41 if err != nil { 42 return "", "", err 43 } 44 req.SetBasicAuth(authConfig.Username, authConfig.Password) 45 resp, err := registryEndpoint.client.Do(req) 46 if err != nil { 47 // fallback when request could not be completed 48 return "", "", fallbackError{ 49 err: err, 50 } 51 } 52 defer resp.Body.Close() 53 body, err := ioutil.ReadAll(resp.Body) 54 if err != nil { 55 return "", "", err 56 } 57 if resp.StatusCode == http.StatusOK { 58 return "Login Succeeded", "", nil 59 } else if resp.StatusCode == http.StatusUnauthorized { 60 if loginAgainstOfficialIndex { 61 return "", "", fmt.Errorf("Wrong login/password, please try again. Haven't got a Docker ID? Create one at https://hub.docker.com") 62 } 63 return "", "", fmt.Errorf("Wrong login/password, please try again") 64 } else if resp.StatusCode == http.StatusForbidden { 65 if loginAgainstOfficialIndex { 66 return "", "", fmt.Errorf("Login: Account is not active. Please check your e-mail for a confirmation link.") 67 } 68 // *TODO: Use registry configuration to determine what this says, if anything? 69 return "", "", fmt.Errorf("Login: Account is not active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress) 70 } else if resp.StatusCode == http.StatusInternalServerError { // Issue #14326 71 logrus.Errorf("%s returned status code %d. Response Body :\n%s", req.URL.String(), resp.StatusCode, body) 72 return "", "", fmt.Errorf("Internal Server Error") 73 } 74 return "", "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, 75 resp.StatusCode, resp.Header) 76 } 77 78 type loginCredentialStore struct { 79 authConfig *types.AuthConfig 80 } 81 82 func (lcs loginCredentialStore) Basic(*url.URL) (string, string) { 83 return lcs.authConfig.Username, lcs.authConfig.Password 84 } 85 86 func (lcs loginCredentialStore) RefreshToken(*url.URL, string) string { 87 return lcs.authConfig.IdentityToken 88 } 89 90 func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token string) { 91 lcs.authConfig.IdentityToken = token 92 } 93 94 type fallbackError struct { 95 err error 96 } 97 98 func (err fallbackError) Error() string { 99 return err.err.Error() 100 } 101 102 // loginV2 tries to login to the v2 registry server. The given registry 103 // endpoint will be pinged to get authorization challenges. These challenges 104 // will be used to authenticate against the registry to validate credentials. 105 func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent string) (string, string, error) { 106 logrus.Debugf("attempting v2 login to registry endpoint %s", strings.TrimRight(endpoint.URL.String(), "/")+"/v2/") 107 108 modifiers := DockerHeaders(userAgent, nil) 109 authTransport := transport.NewTransport(NewTransport(endpoint.TLSConfig), modifiers...) 110 111 challengeManager, foundV2, err := PingV2Registry(endpoint, authTransport) 112 if err != nil { 113 if !foundV2 { 114 err = fallbackError{err: err} 115 } 116 return "", "", err 117 } 118 119 credentialAuthConfig := *authConfig 120 creds := loginCredentialStore{ 121 authConfig: &credentialAuthConfig, 122 } 123 124 tokenHandlerOptions := auth.TokenHandlerOptions{ 125 Transport: authTransport, 126 Credentials: creds, 127 OfflineAccess: true, 128 ClientID: AuthClientID, 129 } 130 tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions) 131 basicHandler := auth.NewBasicHandler(creds) 132 modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) 133 tr := transport.NewTransport(authTransport, modifiers...) 134 135 loginClient := &http.Client{ 136 Transport: tr, 137 Timeout: 15 * time.Second, 138 } 139 140 endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/" 141 req, err := http.NewRequest("GET", endpointStr, nil) 142 if err != nil { 143 if !foundV2 { 144 err = fallbackError{err: err} 145 } 146 return "", "", err 147 } 148 149 resp, err := loginClient.Do(req) 150 if err != nil { 151 if !foundV2 { 152 err = fallbackError{err: err} 153 } 154 return "", "", err 155 } 156 defer resp.Body.Close() 157 158 if resp.StatusCode != http.StatusOK { 159 // TODO(dmcgowan): Attempt to further interpret result, status code and error code string 160 err := fmt.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode)) 161 if !foundV2 { 162 err = fallbackError{err: err} 163 } 164 return "", "", err 165 } 166 167 return "Login Succeeded", credentialAuthConfig.IdentityToken, nil 168 169 } 170 171 // ResolveAuthConfig matches an auth configuration to a server address or a URL 172 func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig { 173 configKey := GetAuthConfigKey(index) 174 // First try the happy case 175 if c, found := authConfigs[configKey]; found || index.Official { 176 return c 177 } 178 179 convertToHostname := func(url string) string { 180 stripped := url 181 if strings.HasPrefix(url, "http://") { 182 stripped = strings.Replace(url, "http://", "", 1) 183 } else if strings.HasPrefix(url, "https://") { 184 stripped = strings.Replace(url, "https://", "", 1) 185 } 186 187 nameParts := strings.SplitN(stripped, "/", 2) 188 189 return nameParts[0] 190 } 191 192 // Maybe they have a legacy config file, we will iterate the keys converting 193 // them to the new format and testing 194 for registry, ac := range authConfigs { 195 if configKey == convertToHostname(registry) { 196 return ac 197 } 198 } 199 200 // When all else fails, return an empty auth config 201 return types.AuthConfig{} 202 } 203 204 // PingResponseError is used when the response from a ping 205 // was received but invalid. 206 type PingResponseError struct { 207 Err error 208 } 209 210 func (err PingResponseError) Error() string { 211 return err.Error() 212 } 213 214 // PingV2Registry attempts to ping a v2 registry and on success return a 215 // challenge manager for the supported authentication types and 216 // whether v2 was confirmed by the response. If a response is received but 217 // cannot be interpreted a PingResponseError will be returned. 218 func PingV2Registry(endpoint APIEndpoint, transport http.RoundTripper) (auth.ChallengeManager, bool, error) { 219 var ( 220 foundV2 = false 221 v2Version = auth.APIVersion{ 222 Type: "registry", 223 Version: "2.0", 224 } 225 ) 226 227 pingClient := &http.Client{ 228 Transport: transport, 229 Timeout: 15 * time.Second, 230 } 231 endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/" 232 req, err := http.NewRequest("GET", endpointStr, nil) 233 if err != nil { 234 return nil, false, err 235 } 236 resp, err := pingClient.Do(req) 237 if err != nil { 238 return nil, false, err 239 } 240 defer resp.Body.Close() 241 242 versions := auth.APIVersions(resp, DefaultRegistryVersionHeader) 243 for _, pingVersion := range versions { 244 if pingVersion == v2Version { 245 // The version header indicates we're definitely 246 // talking to a v2 registry. So don't allow future 247 // fallbacks to the v1 protocol. 248 249 foundV2 = true 250 break 251 } 252 } 253 254 challengeManager := auth.NewSimpleChallengeManager() 255 if err := challengeManager.AddResponse(resp); err != nil { 256 return nil, foundV2, PingResponseError{ 257 Err: err, 258 } 259 } 260 261 return challengeManager, foundV2, nil 262 }