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