github.com/dpiddy/docker@v1.12.2-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 staticCredentialStore struct { 95 auth *types.AuthConfig 96 } 97 98 // NewStaticCredentialStore returns a credential store 99 // which always returns the same credential values. 100 func NewStaticCredentialStore(auth *types.AuthConfig) auth.CredentialStore { 101 return staticCredentialStore{ 102 auth: auth, 103 } 104 } 105 106 func (scs staticCredentialStore) Basic(*url.URL) (string, string) { 107 if scs.auth == nil { 108 return "", "" 109 } 110 return scs.auth.Username, scs.auth.Password 111 } 112 113 func (scs staticCredentialStore) RefreshToken(*url.URL, string) string { 114 if scs.auth == nil { 115 return "" 116 } 117 return scs.auth.IdentityToken 118 } 119 120 func (scs staticCredentialStore) SetRefreshToken(*url.URL, string, string) { 121 } 122 123 type fallbackError struct { 124 err error 125 } 126 127 func (err fallbackError) Error() string { 128 return err.err.Error() 129 } 130 131 // loginV2 tries to login to the v2 registry server. The given registry 132 // endpoint will be pinged to get authorization challenges. These challenges 133 // will be used to authenticate against the registry to validate credentials. 134 func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent string) (string, string, error) { 135 logrus.Debugf("attempting v2 login to registry endpoint %s", strings.TrimRight(endpoint.URL.String(), "/")+"/v2/") 136 137 modifiers := DockerHeaders(userAgent, nil) 138 authTransport := transport.NewTransport(NewTransport(endpoint.TLSConfig), modifiers...) 139 140 credentialAuthConfig := *authConfig 141 creds := loginCredentialStore{ 142 authConfig: &credentialAuthConfig, 143 } 144 145 loginClient, foundV2, err := v2AuthHTTPClient(endpoint.URL, authTransport, modifiers, creds, nil) 146 if err != nil { 147 return "", "", err 148 } 149 150 endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/" 151 req, err := http.NewRequest("GET", endpointStr, nil) 152 if err != nil { 153 if !foundV2 { 154 err = fallbackError{err: err} 155 } 156 return "", "", err 157 } 158 159 resp, err := loginClient.Do(req) 160 if err != nil { 161 if !foundV2 { 162 err = fallbackError{err: err} 163 } 164 return "", "", err 165 } 166 defer resp.Body.Close() 167 168 if resp.StatusCode != http.StatusOK { 169 // TODO(dmcgowan): Attempt to further interpret result, status code and error code string 170 err := fmt.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode)) 171 if !foundV2 { 172 err = fallbackError{err: err} 173 } 174 return "", "", err 175 } 176 177 return "Login Succeeded", credentialAuthConfig.IdentityToken, nil 178 179 } 180 181 func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, bool, error) { 182 challengeManager, foundV2, err := PingV2Registry(endpoint, authTransport) 183 if err != nil { 184 if !foundV2 { 185 err = fallbackError{err: err} 186 } 187 return nil, foundV2, err 188 } 189 190 tokenHandlerOptions := auth.TokenHandlerOptions{ 191 Transport: authTransport, 192 Credentials: creds, 193 OfflineAccess: true, 194 ClientID: AuthClientID, 195 Scopes: scopes, 196 } 197 tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions) 198 basicHandler := auth.NewBasicHandler(creds) 199 modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) 200 tr := transport.NewTransport(authTransport, modifiers...) 201 202 return &http.Client{ 203 Transport: tr, 204 Timeout: 15 * time.Second, 205 }, foundV2, nil 206 207 } 208 209 // ResolveAuthConfig matches an auth configuration to a server address or a URL 210 func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig { 211 configKey := GetAuthConfigKey(index) 212 // First try the happy case 213 if c, found := authConfigs[configKey]; found || index.Official { 214 return c 215 } 216 217 convertToHostname := func(url string) string { 218 stripped := url 219 if strings.HasPrefix(url, "http://") { 220 stripped = strings.Replace(url, "http://", "", 1) 221 } else if strings.HasPrefix(url, "https://") { 222 stripped = strings.Replace(url, "https://", "", 1) 223 } 224 225 nameParts := strings.SplitN(stripped, "/", 2) 226 227 return nameParts[0] 228 } 229 230 // Maybe they have a legacy config file, we will iterate the keys converting 231 // them to the new format and testing 232 for registry, ac := range authConfigs { 233 if configKey == convertToHostname(registry) { 234 return ac 235 } 236 } 237 238 // When all else fails, return an empty auth config 239 return types.AuthConfig{} 240 } 241 242 // PingResponseError is used when the response from a ping 243 // was received but invalid. 244 type PingResponseError struct { 245 Err error 246 } 247 248 func (err PingResponseError) Error() string { 249 return err.Error() 250 } 251 252 // PingV2Registry attempts to ping a v2 registry and on success return a 253 // challenge manager for the supported authentication types and 254 // whether v2 was confirmed by the response. If a response is received but 255 // cannot be interpreted a PingResponseError will be returned. 256 func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (auth.ChallengeManager, bool, error) { 257 var ( 258 foundV2 = false 259 v2Version = auth.APIVersion{ 260 Type: "registry", 261 Version: "2.0", 262 } 263 ) 264 265 pingClient := &http.Client{ 266 Transport: transport, 267 Timeout: 15 * time.Second, 268 } 269 endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/" 270 req, err := http.NewRequest("GET", endpointStr, nil) 271 if err != nil { 272 return nil, false, err 273 } 274 resp, err := pingClient.Do(req) 275 if err != nil { 276 return nil, false, err 277 } 278 defer resp.Body.Close() 279 280 versions := auth.APIVersions(resp, DefaultRegistryVersionHeader) 281 for _, pingVersion := range versions { 282 if pingVersion == v2Version { 283 // The version header indicates we're definitely 284 // talking to a v2 registry. So don't allow future 285 // fallbacks to the v1 protocol. 286 287 foundV2 = true 288 break 289 } 290 } 291 292 challengeManager := auth.NewSimpleChallengeManager() 293 if err := challengeManager.AddResponse(resp); err != nil { 294 return nil, foundV2, PingResponseError{ 295 Err: err, 296 } 297 } 298 299 return challengeManager, foundV2, nil 300 }