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