github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/registry/auth.go (about) 1 package registry // import "github.com/docker/docker/registry" 2 3 import ( 4 "context" 5 "net/http" 6 "net/url" 7 "strings" 8 "time" 9 10 "github.com/containerd/log" 11 "github.com/docker/distribution/registry/client/auth" 12 "github.com/docker/distribution/registry/client/auth/challenge" 13 "github.com/docker/distribution/registry/client/transport" 14 "github.com/docker/docker/api/types/registry" 15 "github.com/pkg/errors" 16 ) 17 18 // AuthClientID is used the ClientID used for the token server 19 const AuthClientID = "docker" 20 21 type loginCredentialStore struct { 22 authConfig *registry.AuthConfig 23 } 24 25 func (lcs loginCredentialStore) Basic(*url.URL) (string, string) { 26 return lcs.authConfig.Username, lcs.authConfig.Password 27 } 28 29 func (lcs loginCredentialStore) RefreshToken(*url.URL, string) string { 30 return lcs.authConfig.IdentityToken 31 } 32 33 func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token string) { 34 lcs.authConfig.IdentityToken = token 35 } 36 37 type staticCredentialStore struct { 38 auth *registry.AuthConfig 39 } 40 41 // NewStaticCredentialStore returns a credential store 42 // which always returns the same credential values. 43 func NewStaticCredentialStore(auth *registry.AuthConfig) auth.CredentialStore { 44 return staticCredentialStore{ 45 auth: auth, 46 } 47 } 48 49 func (scs staticCredentialStore) Basic(*url.URL) (string, string) { 50 if scs.auth == nil { 51 return "", "" 52 } 53 return scs.auth.Username, scs.auth.Password 54 } 55 56 func (scs staticCredentialStore) RefreshToken(*url.URL, string) string { 57 if scs.auth == nil { 58 return "" 59 } 60 return scs.auth.IdentityToken 61 } 62 63 func (scs staticCredentialStore) SetRefreshToken(*url.URL, string, string) { 64 } 65 66 // loginV2 tries to login to the v2 registry server. The given registry 67 // endpoint will be pinged to get authorization challenges. These challenges 68 // will be used to authenticate against the registry to validate credentials. 69 func loginV2(authConfig *registry.AuthConfig, endpoint APIEndpoint, userAgent string) (string, string, error) { 70 var ( 71 endpointStr = strings.TrimRight(endpoint.URL.String(), "/") + "/v2/" 72 modifiers = Headers(userAgent, nil) 73 authTransport = transport.NewTransport(newTransport(endpoint.TLSConfig), modifiers...) 74 credentialAuthConfig = *authConfig 75 creds = loginCredentialStore{authConfig: &credentialAuthConfig} 76 ) 77 78 log.G(context.TODO()).Debugf("attempting v2 login to registry endpoint %s", endpointStr) 79 80 loginClient, err := v2AuthHTTPClient(endpoint.URL, authTransport, modifiers, creds, nil) 81 if err != nil { 82 return "", "", err 83 } 84 85 req, err := http.NewRequest(http.MethodGet, endpointStr, nil) 86 if err != nil { 87 return "", "", err 88 } 89 90 resp, err := loginClient.Do(req) 91 if err != nil { 92 err = translateV2AuthError(err) 93 return "", "", err 94 } 95 defer resp.Body.Close() 96 97 if resp.StatusCode == http.StatusOK { 98 return "Login Succeeded", credentialAuthConfig.IdentityToken, nil 99 } 100 101 // TODO(dmcgowan): Attempt to further interpret result, status code and error code string 102 return "", "", errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode)) 103 } 104 105 func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, error) { 106 challengeManager, err := PingV2Registry(endpoint, authTransport) 107 if err != nil { 108 return nil, err 109 } 110 111 tokenHandlerOptions := auth.TokenHandlerOptions{ 112 Transport: authTransport, 113 Credentials: creds, 114 OfflineAccess: true, 115 ClientID: AuthClientID, 116 Scopes: scopes, 117 } 118 tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions) 119 basicHandler := auth.NewBasicHandler(creds) 120 modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) 121 122 return &http.Client{ 123 Transport: transport.NewTransport(authTransport, modifiers...), 124 Timeout: 15 * time.Second, 125 }, nil 126 } 127 128 // ConvertToHostname normalizes a registry URL which has http|https prepended 129 // to just its hostname. It is used to match credentials, which may be either 130 // stored as hostname or as hostname including scheme (in legacy configuration 131 // files). 132 func ConvertToHostname(url string) string { 133 stripped := url 134 if strings.HasPrefix(url, "http://") { 135 stripped = strings.TrimPrefix(url, "http://") 136 } else if strings.HasPrefix(url, "https://") { 137 stripped = strings.TrimPrefix(url, "https://") 138 } 139 return strings.SplitN(stripped, "/", 2)[0] 140 } 141 142 // ResolveAuthConfig matches an auth configuration to a server address or a URL 143 func ResolveAuthConfig(authConfigs map[string]registry.AuthConfig, index *registry.IndexInfo) registry.AuthConfig { 144 configKey := GetAuthConfigKey(index) 145 // First try the happy case 146 if c, found := authConfigs[configKey]; found || index.Official { 147 return c 148 } 149 150 // Maybe they have a legacy config file, we will iterate the keys converting 151 // them to the new format and testing 152 for registryURL, ac := range authConfigs { 153 if configKey == ConvertToHostname(registryURL) { 154 return ac 155 } 156 } 157 158 // When all else fails, return an empty auth config 159 return registry.AuthConfig{} 160 } 161 162 // PingResponseError is used when the response from a ping 163 // was received but invalid. 164 type PingResponseError struct { 165 Err error 166 } 167 168 func (err PingResponseError) Error() string { 169 return err.Err.Error() 170 } 171 172 // PingV2Registry attempts to ping a v2 registry and on success return a 173 // challenge manager for the supported authentication types. 174 // If a response is received but cannot be interpreted, a PingResponseError will be returned. 175 func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (challenge.Manager, error) { 176 pingClient := &http.Client{ 177 Transport: transport, 178 Timeout: 15 * time.Second, 179 } 180 endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/" 181 req, err := http.NewRequest(http.MethodGet, endpointStr, nil) 182 if err != nil { 183 return nil, err 184 } 185 resp, err := pingClient.Do(req) 186 if err != nil { 187 return nil, err 188 } 189 defer resp.Body.Close() 190 191 challengeManager := challenge.NewSimpleManager() 192 if err := challengeManager.AddResponse(resp); err != nil { 193 return nil, PingResponseError{ 194 Err: err, 195 } 196 } 197 198 return challengeManager, nil 199 }