diff --git a/.go-version b/.go-version index 3cd33cde0f7..457f0385465 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.26.9 +1.27.2 diff --git a/AI_AGENT_DISCLOSURE.md b/AI_AGENT_DISCLOSURE.md new file mode 100644 index 00000000000..22b0749816d --- /dev/null +++ b/AI_AGENT_DISCLOSURE.md @@ -0,0 +1,4 @@ +*"This contribution was prepared by an AI agent acting on a human's behalf. +The human submitter may not have independently reviewed or tested the change."* + +2026-10-19 diff --git a/Dockerfile b/Dockerfile index 831233d1246..80986102363 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -ARG GO_VERSION=1.26.9 +ARG GO_VERSION=1.27.2 ARG XX_VERSION=1.9.0 ARG GOLANGCI_LINT_VERSION=v2.13.2 ARG ADDLICENSE_VERSION=v1.0.0 diff --git a/go.mod b/go.mod index 32c77020978..d8cd2b48ecf 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/docker/compose/v5 -go 1.26.8 +go 1.27.2 require ( github.com/DefangLabs/secret-detector v0.0.0-20260916192156-3e28d7ed64df @@ -17,8 +17,8 @@ require ( github.com/docker/cli v29.9.1+incompatible github.com/docker/cli-docs-tool v0.11.0 github.com/docker/go-units v0.5.0 - github.com/docker/secrets-engine/client v0.2.0 - github.com/docker/secrets-engine/x v0.8.3 + github.com/docker/secrets-engine/client v0.2.1 + github.com/docker/secrets-engine/x v0.8.4 github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 github.com/fsnotify/fsevents v0.2.0 github.com/google/go-cmp v0.7.0 diff --git a/go.sum b/go.sum index 9acda1765bb..5f08e2c7492 100644 --- a/go.sum +++ b/go.sum @@ -90,10 +90,10 @@ github.com/docker/go-metrics v0.1.0 h1:r76KPNpstz+IvQKSWpYegSkkyzex0V3A1ZGVx6bhG github.com/docker/go-metrics v0.1.0/go.mod h1:PciI3sONtB051kXALN1JoIlpcu54E1FuPh+4DuqEzyw= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/secrets-engine/client v0.2.0 h1:69Q63N5VDbXbxPPgm0LjUtRJ4O41q4cQeFpUxsHZUJo= -github.com/docker/secrets-engine/client v0.2.0/go.mod h1:dL3y9wh+HVHMTInwOOmAmjai6oARC8T+se30RnWgrLs= -github.com/docker/secrets-engine/x v0.8.3 h1:0rt91D54Ah6vI96qbc0Qcaxbkd0n36QAkQm+2614FzA= -github.com/docker/secrets-engine/x v0.8.3/go.mod h1:+s6rb56pq1B376jzzkGqZreFJ3mUtkz+kaYlMuj7/Qs= +github.com/docker/secrets-engine/client v0.2.1 h1:j53uiQp2szKQF7GChnQ1U5vYoRtzoZvojRATEe4EIQ4= +github.com/docker/secrets-engine/client v0.2.1/go.mod h1:YtVX3nFlHzJEF10p2HWZ6grD0+wabbf6su7OCSFCWlE= +github.com/docker/secrets-engine/x v0.8.4 h1:pNFQXpYZilTp4TSOvnbSeanKL9l6Ea0beuR31VrCvnk= +github.com/docker/secrets-engine/x v0.8.4/go.mod h1:KfCMw2fLGea1qsQSTU5owkH8NfRbTBq0PMpXnrAeEZQ= github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg= github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= diff --git a/internal/registry/desktop.go b/internal/registry/desktop.go index 7dadc720c9b..549fe5cee8e 100644 --- a/internal/registry/desktop.go +++ b/internal/registry/desktop.go @@ -18,8 +18,6 @@ package registry import ( "context" - "encoding/base64" - "encoding/json" "errors" "fmt" "strings" @@ -38,7 +36,7 @@ const ( // unexpected request delay by the secrets engine. desktopLookupTimeout = 5 * time.Second sessionExpirySkew = 30 * time.Second - // sessionTTL is how long a token without a readable expiry is reused. + // sessionTTL is how long a session without an expiry claim is reused. sessionTTL = time.Minute // retryInterval is how long Desktop is not asked again after a failed lookup. retryInterval = time.Minute @@ -200,8 +198,8 @@ func (s *desktopSession) fetch() (clitypes.AuthConfig, time.Time, error) { now := s.now() validUntil := now.Add(sessionTTL) - if expiresAt, ok := sessionExpiry(session); ok { - validUntil = expiresAt.Add(-sessionExpirySkew) + if exp := session.Claims.ExpiresAt; exp != nil { + validUntil = exp.Add(-sessionExpirySkew) if !now.Before(validUntil) { return clitypes.AuthConfig{}, time.Time{}, errSessionExpired } @@ -214,31 +212,6 @@ func (s *desktopSession) fetch() (clitypes.AuthConfig, time.Time, error) { }, validUntil, nil } -func sessionExpiry(session dockerhub.UserSession) (time.Time, bool) { - if exp := session.Claims.ExpiresAt; exp != nil { - return exp.Time, true - } - return jwtExpiry(session.AccessToken) -} - -func jwtExpiry(token string) (time.Time, bool) { - parts := strings.Split(token, ".") - if len(parts) != 3 { - return time.Time{}, false - } - payload, err := base64.RawURLEncoding.DecodeString(parts[1]) - if err != nil { - return time.Time{}, false - } - var claims struct { - ExpiresAt *dockerhub.NumericDate `json:"exp"` - } - if err := json.Unmarshal(payload, &claims); err != nil || claims.ExpiresAt == nil { - return time.Time{}, false - } - return claims.ExpiresAt.Time, true -} - func logDesktopFallback(name string, err error) { if isSessionUnavailable(err) { logrus.Debugf("no %s session available from Docker Desktop, using Docker CLI credentials: %v", name, err) diff --git a/internal/registry/desktop_test.go b/internal/registry/desktop_test.go index bae0e296a98..e524a521b86 100644 --- a/internal/registry/desktop_test.go +++ b/internal/registry/desktop_test.go @@ -18,7 +18,6 @@ package registry import ( "context" - "encoding/base64" "errors" "fmt" "sync" @@ -452,51 +451,6 @@ func TestDesktopAuthProvider_RefetchesSessionWithoutExpiry(t *testing.T) { assert.Equal(t, hub.sessionCalls.Load(), int32(2)) } -func fakeJWT(expiresAt time.Time) string { - enc := base64.RawURLEncoding.EncodeToString - return enc([]byte(`{"alg":"none"}`)) + "." + enc(fmt.Appendf(nil, `{"exp":%d}`, expiresAt.Unix())) + ".sig" -} - -func TestDesktopAuthProvider_ExpiryFromTokenWhenClaimsLackIt(t *testing.T) { - t.Run("expired token falls back", func(t *testing.T) { - session := hubSession("hubuser", time.Time{}) - session.AccessToken = fakeJWT(testNow.Add(-time.Minute)) - p := newTestProvider(&fakeHub{session: session}, nil) - - got, err := p.GetAuthConfig("docker.io") - assert.NilError(t, err) - assert.DeepEqual(t, got, cliCredentials("docker.io")) - }) - - t.Run("valid token is reused until it expires", func(t *testing.T) { - session := hubSession("hubuser", time.Time{}) - session.AccessToken = fakeJWT(testNow.Add(10 * time.Minute)) - hub := &fakeHub{session: session} - p := newTestProvider(hub, nil) - - got, err := p.GetAuthConfig("docker.io") - assert.NilError(t, err) - assert.Equal(t, got.Password, session.AccessToken) - - // Past sessionTTL, but the token's own expiry still applies. - p.clock.advance(sessionTTL + time.Minute) - _, err = p.GetAuthConfig("docker.io") - assert.NilError(t, err) - assert.Equal(t, hub.sessionCalls.Load(), int32(1)) - }) -} - -func TestJWTExpiry(t *testing.T) { - exp, ok := jwtExpiry(fakeJWT(testNow)) - assert.Assert(t, ok) - assert.Assert(t, exp.Equal(testNow)) - - for _, token := range []string{"", "opaque-token", "a.%%%.c", "a." + base64.RawURLEncoding.EncodeToString([]byte(`{}`)) + ".c"} { - _, ok := jwtExpiry(token) - assert.Assert(t, !ok, token) - } -} - func TestDesktopAuthProvider_ConcurrentLookupsShareOneFetch(t *testing.T) { hub := &fakeHub{session: hubSession("hubuser", testNow.Add(time.Hour))} p := newTestProvider(hub, nil)