mirror of
https://github.com/docker/compose.git
synced 2026-06-27 19:53:28 +00:00
fix(oci): route authorizer token fetches through provided transport
Some checks failed
ci / validate (lint) (push) Has been cancelled
ci / validate (validate-docs) (push) Has been cancelled
ci / validate (validate-go-mod) (push) Has been cancelled
ci / validate (validate-headers) (push) Has been cancelled
ci / binary (push) Has been cancelled
ci / bin-image-test (push) Has been cancelled
ci / test (push) Has been cancelled
ci / e2e (plugin, oldstable) (push) Has been cancelled
ci / e2e (standalone, oldstable) (push) Has been cancelled
ci / e2e (plugin, stable) (push) Has been cancelled
ci / e2e (standalone, stable) (push) Has been cancelled
merge / bin-image-prepare (push) Has been cancelled
merge / module-image (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
ci / binary-finalize (push) Has been cancelled
ci / coverage (push) Has been cancelled
ci / release (push) Has been cancelled
merge / bin-image (push) Has been cancelled
merge / desktop-edge-test (push) Has been cancelled
Some checks failed
ci / validate (lint) (push) Has been cancelled
ci / validate (validate-docs) (push) Has been cancelled
ci / validate (validate-go-mod) (push) Has been cancelled
ci / validate (validate-headers) (push) Has been cancelled
ci / binary (push) Has been cancelled
ci / bin-image-test (push) Has been cancelled
ci / test (push) Has been cancelled
ci / e2e (plugin, oldstable) (push) Has been cancelled
ci / e2e (standalone, oldstable) (push) Has been cancelled
ci / e2e (plugin, stable) (push) Has been cancelled
ci / e2e (standalone, stable) (push) Has been cancelled
merge / bin-image-prepare (push) Has been cancelled
merge / module-image (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
ci / binary-finalize (push) Has been cancelled
ci / coverage (push) Has been cancelled
ci / release (push) Has been cancelled
merge / bin-image (push) Has been cancelled
merge / desktop-edge-test (push) Has been cancelled
The transport passed to NewResolver was applied only via docker.WithClient. Pass it via docker.WithAuthClient too so the authorizer's OAuth token fetches use it instead of http.DefaultClient. Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
This commit is contained in:
parent
a3c1c0dc2e
commit
e87f7b79df
2 changed files with 61 additions and 18 deletions
|
|
@ -38,23 +38,28 @@ import (
|
|||
|
||||
// NewResolver sets up an OCI Resolver based on docker/cli config to provide
|
||||
// registry credentials. When transport is non-nil it is used as the HTTP
|
||||
// transport for all registry calls (e.g. to route through Docker Desktop's
|
||||
// PAC-aware proxy); nil falls back to containerd's default transport.
|
||||
// transport for both registry calls and the authorizer's token fetches
|
||||
// (e.g. to route both through Docker Desktop's PAC-aware proxy); nil falls
|
||||
// back to containerd's default transport.
|
||||
func NewResolver(config *configfile.ConfigFile, transport http.RoundTripper, insecureRegistries ...string) remotes.Resolver {
|
||||
authOpts := []docker.AuthorizerOpt{
|
||||
docker.WithAuthCreds(func(host string) (string, string, error) {
|
||||
host = registry.GetAuthConfigKey(host)
|
||||
auth, err := config.GetAuthConfig(host)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if auth.IdentityToken != "" {
|
||||
return "", auth.IdentityToken, nil
|
||||
}
|
||||
return auth.Username, auth.Password, nil
|
||||
}),
|
||||
}
|
||||
if transport != nil {
|
||||
authOpts = append(authOpts, docker.WithAuthClient(&http.Client{Transport: transport}))
|
||||
}
|
||||
opts := []docker.RegistryOpt{
|
||||
docker.WithAuthorizer(docker.NewDockerAuthorizer(
|
||||
docker.WithAuthCreds(func(host string) (string, string, error) {
|
||||
host = registry.GetAuthConfigKey(host)
|
||||
auth, err := config.GetAuthConfig(host)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if auth.IdentityToken != "" {
|
||||
return "", auth.IdentityToken, nil
|
||||
}
|
||||
return auth.Username, auth.Password, nil
|
||||
}),
|
||||
)),
|
||||
docker.WithAuthorizer(docker.NewDockerAuthorizer(authOpts...)),
|
||||
docker.WithPlainHTTP(func(domain string) (bool, error) {
|
||||
// Should be used for testing **only**
|
||||
return slices.Contains(insecureRegistries, domain), nil
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package oci
|
|||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
|
|
@ -27,14 +28,20 @@ import (
|
|||
)
|
||||
|
||||
// recordingRoundTripper counts RoundTrip invocations on a delegate so tests
|
||||
// can verify a supplied transport is actually used by the resolver.
|
||||
// can verify a supplied transport is actually used by the resolver. It also
|
||||
// tracks token-endpoint calls separately so tests can assert the authorizer's
|
||||
// token fetch goes through the same transport.
|
||||
type recordingRoundTripper struct {
|
||||
delegate http.RoundTripper
|
||||
calls atomic.Int32
|
||||
delegate http.RoundTripper
|
||||
calls atomic.Int32
|
||||
authCalls atomic.Int32
|
||||
}
|
||||
|
||||
func (r *recordingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
r.calls.Add(1)
|
||||
if strings.HasSuffix(req.URL.Path, "/token") {
|
||||
r.authCalls.Add(1)
|
||||
}
|
||||
return r.delegate.RoundTrip(req)
|
||||
}
|
||||
|
||||
|
|
@ -64,6 +71,37 @@ func TestNewResolver_UsesProvidedTransport(t *testing.T) {
|
|||
"resolver did not invoke the supplied transport — wiring is broken")
|
||||
}
|
||||
|
||||
// TestNewResolver_AuthorizerUsesProvidedTransport guards that the docker
|
||||
// authorizer's token fetch goes through the supplied transport, not
|
||||
// http.DefaultClient.
|
||||
func TestNewResolver_AuthorizerUsesProvidedTransport(t *testing.T) {
|
||||
var server *httptest.Server
|
||||
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasSuffix(r.URL.Path, "/token") {
|
||||
_, _ = w.Write([]byte(`{"token":"fake","access_token":"fake","expires_in":300}`))
|
||||
return
|
||||
}
|
||||
if r.Header.Get("Authorization") == "" {
|
||||
w.Header().Set("Www-Authenticate", `Bearer realm="`+server.URL+`/token",service="test"`)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
// Authenticated retry — fail fast, we only care the auth dance went
|
||||
// through the supplied transport.
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
host := server.Listener.Addr().String()
|
||||
rec := &recordingRoundTripper{delegate: &http.Transport{}}
|
||||
|
||||
resolver := NewResolver(&configfile.ConfigFile{}, rec, host)
|
||||
_, _, _ = resolver.Resolve(t.Context(), host+"/test/image:latest")
|
||||
|
||||
assert.Assert(t, rec.authCalls.Load() > 0,
|
||||
"authorizer token fetch did not go through the supplied transport (bypassed via http.DefaultClient)")
|
||||
}
|
||||
|
||||
func TestNewResolver_NilTransportIsValid(t *testing.T) {
|
||||
resolver := NewResolver(&configfile.ConfigFile{}, nil)
|
||||
assert.Assert(t, resolver != nil, "NewResolver must return a non-nil resolver when transport is nil")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue