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