mirror of
https://github.com/containers/skopeo.git
synced 2025-07-21 09:59:55 +00:00
docker: fix registry authentication issues
vendor containers/image@d23efe9c5a fix https://bugzilla.redhat.com/show_bug.cgi?id=1413987 Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
parent
d7156f9b3d
commit
c736e69d48
146
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
146
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
@ -45,14 +45,20 @@ var ErrV1NotSupported = errors.New("can't talk to a V1 docker registry")
|
|||||||
|
|
||||||
// dockerClient is configuration for dealing with a single Docker registry.
|
// dockerClient is configuration for dealing with a single Docker registry.
|
||||||
type dockerClient struct {
|
type dockerClient struct {
|
||||||
ctx *types.SystemContext
|
ctx *types.SystemContext
|
||||||
registry string
|
registry string
|
||||||
username string
|
username string
|
||||||
password string
|
password string
|
||||||
wwwAuthenticate string // Cache of a value set by ping() if scheme is not empty
|
scheme string // Cache of a value returned by a successful ping() if not empty
|
||||||
scheme string // Cache of a value returned by a successful ping() if not empty
|
client *http.Client
|
||||||
client *http.Client
|
signatureBase signatureStorageBase
|
||||||
signatureBase signatureStorageBase
|
challenges []challenge
|
||||||
|
scope authScope
|
||||||
|
}
|
||||||
|
|
||||||
|
type authScope struct {
|
||||||
|
remoteName string
|
||||||
|
actions string
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is cloned from docker/go-connections because upstream docker has changed
|
// this is cloned from docker/go-connections because upstream docker has changed
|
||||||
@ -147,7 +153,7 @@ func hasFile(files []os.FileInfo, name string) bool {
|
|||||||
|
|
||||||
// newDockerClient returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
|
// newDockerClient returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
|
||||||
// “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection)
|
// “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection)
|
||||||
func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool) (*dockerClient, error) {
|
func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool, actions string) (*dockerClient, error) {
|
||||||
registry := ref.ref.Hostname()
|
registry := ref.ref.Hostname()
|
||||||
if registry == dockerHostname {
|
if registry == dockerHostname {
|
||||||
registry = dockerRegistry
|
registry = dockerRegistry
|
||||||
@ -184,6 +190,10 @@ func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool)
|
|||||||
password: password,
|
password: password,
|
||||||
client: client,
|
client: client,
|
||||||
signatureBase: sigBase,
|
signatureBase: sigBase,
|
||||||
|
scope: authScope{
|
||||||
|
actions: actions,
|
||||||
|
remoteName: ref.ref.RemoteName(),
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,12 +201,9 @@ func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool)
|
|||||||
// url is NOT an absolute URL, but a path relative to the /v2/ top-level API path. The host name and schema is taken from the client or autodetected.
|
// url is NOT an absolute URL, but a path relative to the /v2/ top-level API path. The host name and schema is taken from the client or autodetected.
|
||||||
func (c *dockerClient) makeRequest(method, url string, headers map[string][]string, stream io.Reader) (*http.Response, error) {
|
func (c *dockerClient) makeRequest(method, url string, headers map[string][]string, stream io.Reader) (*http.Response, error) {
|
||||||
if c.scheme == "" {
|
if c.scheme == "" {
|
||||||
pr, err := c.ping()
|
if err := c.ping(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.wwwAuthenticate = pr.WWWAuthenticate
|
|
||||||
c.scheme = pr.scheme
|
|
||||||
}
|
}
|
||||||
|
|
||||||
url = fmt.Sprintf(baseURL, c.scheme, c.registry) + url
|
url = fmt.Sprintf(baseURL, c.scheme, c.registry) + url
|
||||||
@ -224,7 +231,7 @@ func (c *dockerClient) makeRequestToResolvedURL(method, url string, headers map[
|
|||||||
if c.ctx != nil && c.ctx.DockerRegistryUserAgent != "" {
|
if c.ctx != nil && c.ctx.DockerRegistryUserAgent != "" {
|
||||||
req.Header.Add("User-Agent", c.ctx.DockerRegistryUserAgent)
|
req.Header.Add("User-Agent", c.ctx.DockerRegistryUserAgent)
|
||||||
}
|
}
|
||||||
if c.wwwAuthenticate != "" && sendAuth {
|
if sendAuth {
|
||||||
if err := c.setupRequestAuth(req); err != nil {
|
if err := c.setupRequestAuth(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -237,78 +244,30 @@ func (c *dockerClient) makeRequestToResolvedURL(method, url string, headers map[
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we're using the challenges from the /v2/ ping response and not the one from the destination
|
||||||
|
// URL in this request because:
|
||||||
|
//
|
||||||
|
// 1) docker does that as well
|
||||||
|
// 2) gcr.io is sending 401 without a WWW-Authenticate header in the real request
|
||||||
|
//
|
||||||
|
// debugging: https://github.com/containers/image/pull/211#issuecomment-273426236 and follows up
|
||||||
func (c *dockerClient) setupRequestAuth(req *http.Request) error {
|
func (c *dockerClient) setupRequestAuth(req *http.Request) error {
|
||||||
tokens := strings.SplitN(strings.TrimSpace(c.wwwAuthenticate), " ", 2)
|
if len(c.challenges) == 0 {
|
||||||
if len(tokens) != 2 {
|
return nil
|
||||||
return errors.Errorf("expected 2 tokens in WWW-Authenticate: %d, %s", len(tokens), c.wwwAuthenticate)
|
|
||||||
}
|
}
|
||||||
switch tokens[0] {
|
// assume just one...
|
||||||
case "Basic":
|
challenge := c.challenges[0]
|
||||||
|
switch challenge.Scheme {
|
||||||
|
case "basic":
|
||||||
req.SetBasicAuth(c.username, c.password)
|
req.SetBasicAuth(c.username, c.password)
|
||||||
return nil
|
return nil
|
||||||
case "Bearer":
|
case "bearer":
|
||||||
// FIXME? This gets a new token for every API request;
|
|
||||||
// we may be easily able to reuse a previous token, e.g.
|
|
||||||
// for OpenShift the token only identifies the user and does not vary
|
|
||||||
// across operations. Should we just try the request first, and
|
|
||||||
// only get a new token on failure?
|
|
||||||
// OTOH what to do with the single-use body stream in that case?
|
|
||||||
|
|
||||||
// Try performing the request, expecting it to fail.
|
|
||||||
testReq := *req
|
|
||||||
// Do not use the body stream, or we couldn't reuse it for the "real" call later.
|
|
||||||
testReq.Body = nil
|
|
||||||
testReq.ContentLength = 0
|
|
||||||
res, err := c.client.Do(&testReq)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
chs := parseAuthHeader(res.Header)
|
|
||||||
// We could end up in this "if" statement if the /v2/ call (during ping)
|
|
||||||
// returned 401 with a valid WWW-Authenticate=Bearer header.
|
|
||||||
// That doesn't **always** mean, however, that the specific API request
|
|
||||||
// (different from /v2/) actually needs to be authorized.
|
|
||||||
// One example of this _weird_ scenario happens with GCR.io docker
|
|
||||||
// registries.
|
|
||||||
if res.StatusCode != http.StatusUnauthorized || chs == nil || len(chs) == 0 {
|
|
||||||
// With gcr.io, the /v2/ call returns a 401 with a valid WWW-Authenticate=Bearer
|
|
||||||
// header but the repository could be _public_ (no authorization is needed).
|
|
||||||
// Hence, the registry response contains no challenges and the status
|
|
||||||
// code is not 401.
|
|
||||||
// We just skip this case as it's not standard on docker/distribution
|
|
||||||
// registries (https://github.com/docker/distribution/blob/master/docs/spec/api.md#api-version-check)
|
|
||||||
if res.StatusCode != http.StatusUnauthorized {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// gcr.io private repositories pull instead requires us to send user:pass pair in
|
|
||||||
// order to retrieve a token and setup the correct Bearer token.
|
|
||||||
// try again one last time with Basic Auth
|
|
||||||
testReq2 := *req
|
|
||||||
// Do not use the body stream, or we couldn't reuse it for the "real" call later.
|
|
||||||
testReq2.Body = nil
|
|
||||||
testReq2.ContentLength = 0
|
|
||||||
testReq2.SetBasicAuth(c.username, c.password)
|
|
||||||
res, err := c.client.Do(&testReq2)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
chs = parseAuthHeader(res.Header)
|
|
||||||
if res.StatusCode != http.StatusUnauthorized || chs == nil || len(chs) == 0 {
|
|
||||||
// no need for bearer? wtf?
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Arbitrarily use the first challenge, there is no reason to expect more than one.
|
|
||||||
challenge := chs[0]
|
|
||||||
if challenge.Scheme != "bearer" { // Another artifact of trying to handle WWW-Authenticate before it actually happens.
|
|
||||||
return errors.Errorf("Unimplemented: WWW-Authenticate Bearer replaced by %#v", challenge.Scheme)
|
|
||||||
}
|
|
||||||
realm, ok := challenge.Parameters["realm"]
|
realm, ok := challenge.Parameters["realm"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.Errorf("missing realm in bearer auth challenge")
|
return errors.Errorf("missing realm in bearer auth challenge")
|
||||||
}
|
}
|
||||||
service, _ := challenge.Parameters["service"] // Will be "" if not present
|
service, _ := challenge.Parameters["service"] // Will be "" if not present
|
||||||
scope, _ := challenge.Parameters["scope"] // Will be "" if not present
|
scope := fmt.Sprintf("repository:%s:%s", c.scope.remoteName, c.scope.actions)
|
||||||
token, err := c.getBearerToken(realm, service, scope)
|
token, err := c.getBearerToken(realm, service, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -316,8 +275,7 @@ func (c *dockerClient) setupRequestAuth(req *http.Request) error {
|
|||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.Errorf("no handler for %s authentication", tokens[0])
|
return errors.Errorf("no handler for %s authentication", challenge.Scheme)
|
||||||
// support docker bearer with authconfig's Auth string? see docker2aci
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *dockerClient) getBearerToken(realm, service, scope string) (string, error) {
|
func (c *dockerClient) getBearerToken(realm, service, scope string) (string, error) {
|
||||||
@ -427,39 +385,31 @@ func getAuth(ctx *types.SystemContext, registry string) (string, string, error)
|
|||||||
return "", "", nil
|
return "", "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type pingResponse struct {
|
func (c *dockerClient) ping() error {
|
||||||
WWWAuthenticate string
|
ping := func(scheme string) error {
|
||||||
APIVersion string
|
|
||||||
scheme string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *dockerClient) ping() (*pingResponse, error) {
|
|
||||||
ping := func(scheme string) (*pingResponse, error) {
|
|
||||||
url := fmt.Sprintf(baseURL, scheme, c.registry)
|
url := fmt.Sprintf(baseURL, scheme, c.registry)
|
||||||
resp, err := c.makeRequestToResolvedURL("GET", url, nil, nil, -1, true)
|
resp, err := c.makeRequestToResolvedURL("GET", url, nil, nil, -1, true)
|
||||||
logrus.Debugf("Ping %s err %#v", url, err)
|
logrus.Debugf("Ping %s err %#v", url, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
logrus.Debugf("Ping %s status %d", scheme+"://"+c.registry+"/v2/", resp.StatusCode)
|
logrus.Debugf("Ping %s status %d", scheme+"://"+c.registry+"/v2/", resp.StatusCode)
|
||||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized {
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized {
|
||||||
return nil, errors.Errorf("error pinging repository, response code %d", resp.StatusCode)
|
return errors.Errorf("error pinging repository, response code %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
pr := &pingResponse{}
|
c.challenges = parseAuthHeader(resp.Header)
|
||||||
pr.WWWAuthenticate = resp.Header.Get("WWW-Authenticate")
|
c.scheme = scheme
|
||||||
pr.APIVersion = resp.Header.Get("Docker-Distribution-Api-Version")
|
return nil
|
||||||
pr.scheme = scheme
|
|
||||||
return pr, nil
|
|
||||||
}
|
}
|
||||||
pr, err := ping("https")
|
err := ping("https")
|
||||||
if err != nil && c.ctx != nil && c.ctx.DockerInsecureSkipTLSVerify {
|
if err != nil && c.ctx != nil && c.ctx.DockerInsecureSkipTLSVerify {
|
||||||
pr, err = ping("http")
|
err = ping("http")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "pinging docker registry returned")
|
err = errors.Wrap(err, "pinging docker registry returned")
|
||||||
if c.ctx != nil && c.ctx.DockerDisableV1Ping {
|
if c.ctx != nil && c.ctx.DockerDisableV1Ping {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
// best effort to understand if we're talking to a V1 registry
|
// best effort to understand if we're talking to a V1 registry
|
||||||
pingV1 := func(scheme string) bool {
|
pingV1 := func(scheme string) bool {
|
||||||
@ -484,7 +434,7 @@ func (c *dockerClient) ping() (*pingResponse, error) {
|
|||||||
err = ErrV1NotSupported
|
err = ErrV1NotSupported
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pr, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultConfigDir(confPath string) string {
|
func getDefaultConfigDir(confPath string) string {
|
||||||
|
2
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
2
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
@ -41,7 +41,7 @@ type dockerImageDestination struct {
|
|||||||
|
|
||||||
// newImageDestination creates a new ImageDestination for the specified image reference.
|
// newImageDestination creates a new ImageDestination for the specified image reference.
|
||||||
func newImageDestination(ctx *types.SystemContext, ref dockerReference) (types.ImageDestination, error) {
|
func newImageDestination(ctx *types.SystemContext, ref dockerReference) (types.ImageDestination, error) {
|
||||||
c, err := newDockerClient(ctx, ref, true)
|
c, err := newDockerClient(ctx, ref, true, "push")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
4
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
4
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
@ -32,7 +32,7 @@ type dockerImageSource struct {
|
|||||||
// nil requestedManifestMIMETypes means manifest.DefaultRequestedManifestMIMETypes.
|
// nil requestedManifestMIMETypes means manifest.DefaultRequestedManifestMIMETypes.
|
||||||
// The caller must call .Close() on the returned ImageSource.
|
// The caller must call .Close() on the returned ImageSource.
|
||||||
func newImageSource(ctx *types.SystemContext, ref dockerReference, requestedManifestMIMETypes []string) (*dockerImageSource, error) {
|
func newImageSource(ctx *types.SystemContext, ref dockerReference, requestedManifestMIMETypes []string) (*dockerImageSource, error) {
|
||||||
c, err := newDockerClient(ctx, ref, false)
|
c, err := newDockerClient(ctx, ref, false, "pull")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -261,7 +261,7 @@ func (s *dockerImageSource) getOneSignature(url *url.URL) (signature []byte, mis
|
|||||||
|
|
||||||
// deleteImage deletes the named image from the registry, if supported.
|
// deleteImage deletes the named image from the registry, if supported.
|
||||||
func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
||||||
c, err := newDockerClient(ctx, ref, true)
|
c, err := newDockerClient(ctx, ref, true, "push")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user