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