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