gopkg.in/docker/docker.v23@v23.0.11/registry/auth.go (about) 1 package registry // import "github.com/docker/docker/registry" 2 3 import ( 4 "net/http" 5 "net/url" 6 "strings" 7 "time" 8 9 "github.com/docker/distribution/registry/client/auth" 10 "github.com/docker/distribution/registry/client/auth/challenge" 11 "github.com/docker/distribution/registry/client/transport" 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/api/types/registry" 14 "github.com/pkg/errors" 15 "github.com/sirupsen/logrus" 16 ) 17 18 // AuthClientID is used the ClientID used for the token server 19 const AuthClientID = "docker" 20 21 type loginCredentialStore struct { 22 authConfig *types.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 *types.AuthConfig 39 } 40 41 // NewStaticCredentialStore returns a credential store 42 // which always returns the same credential values. 43 func NewStaticCredentialStore(auth *types.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 *types.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 logrus.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 converts a registry url which has http|https prepended 129 // to just an hostname. 130 func ConvertToHostname(url string) string { 131 stripped := url 132 if strings.HasPrefix(url, "http://") { 133 stripped = strings.TrimPrefix(url, "http://") 134 } else if strings.HasPrefix(url, "https://") { 135 stripped = strings.TrimPrefix(url, "https://") 136 } 137 return strings.SplitN(stripped, "/", 2)[0] 138 } 139 140 // ResolveAuthConfig matches an auth configuration to a server address or a URL 141 func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registry.IndexInfo) types.AuthConfig { 142 configKey := GetAuthConfigKey(index) 143 // First try the happy case 144 if c, found := authConfigs[configKey]; found || index.Official { 145 return c 146 } 147 148 // Maybe they have a legacy config file, we will iterate the keys converting 149 // them to the new format and testing 150 for registry, ac := range authConfigs { 151 if configKey == ConvertToHostname(registry) { 152 return ac 153 } 154 } 155 156 // When all else fails, return an empty auth config 157 return types.AuthConfig{} 158 } 159 160 // PingResponseError is used when the response from a ping 161 // was received but invalid. 162 type PingResponseError struct { 163 Err error 164 } 165 166 func (err PingResponseError) Error() string { 167 return err.Err.Error() 168 } 169 170 // PingV2Registry attempts to ping a v2 registry and on success return a 171 // challenge manager for the supported authentication types. 172 // If a response is received but cannot be interpreted, a PingResponseError will be returned. 173 func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (challenge.Manager, error) { 174 pingClient := &http.Client{ 175 Transport: transport, 176 Timeout: 15 * time.Second, 177 } 178 endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/" 179 req, err := http.NewRequest(http.MethodGet, endpointStr, nil) 180 if err != nil { 181 return nil, err 182 } 183 resp, err := pingClient.Do(req) 184 if err != nil { 185 return nil, err 186 } 187 defer resp.Body.Close() 188 189 challengeManager := challenge.NewSimpleManager() 190 if err := challengeManager.AddResponse(resp); err != nil { 191 return nil, PingResponseError{ 192 Err: err, 193 } 194 } 195 196 return challengeManager, nil 197 }