[release-1.22] Bump c/common to v0.67.1, c/image v5.39.2

Signed-off-by: Tom Sweeney <tsweeney@redhat.com>
This commit is contained in:
Tom Sweeney
2026-04-08 16:51:55 -04:00
parent fd3ba47e00
commit e5890ebb4f
5 changed files with 131 additions and 77 deletions

4
go.mod
View File

@@ -17,8 +17,8 @@ require (
github.com/spf13/cobra v1.10.1
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
go.podman.io/common v0.67.0
go.podman.io/image/v5 v5.39.1
go.podman.io/common v0.67.1
go.podman.io/image/v5 v5.39.2
go.podman.io/storage v1.62.0
golang.org/x/term v0.36.0
gopkg.in/yaml.v3 v3.0.1

8
go.sum
View File

@@ -276,10 +276,10 @@ go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKr
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.podman.io/common v0.67.0 h1:6Ci5oU1ek08OAxBLkHEqSyWmjNh5zf03PRqZ04cPdwU=
go.podman.io/common v0.67.0/go.mod h1:sB9L8LMtmf5Hpek2qkEyRrcSzpb+gYpG3vq5Khima3U=
go.podman.io/image/v5 v5.39.1 h1:loIw4qHzZzBlUguYZau40u8HbR5MrTPQhwT4Hy6sCm0=
go.podman.io/image/v5 v5.39.1/go.mod h1:SlaR6Pra1ATIx4BcuZ16oafb3QcCHISaKcJbtlN/G/0=
go.podman.io/common v0.67.1 h1:HddYLJfkfFUmFJ0V3PVoewguFM9eHkqk0g+fOc2B9R4=
go.podman.io/common v0.67.1/go.mod h1:XVmSLtnhJwGb+ImYn6BXiozxVE3mYZJgiiBuDOiOquk=
go.podman.io/image/v5 v5.39.2 h1:EJua/pRtvgLV/a5y8/RvA+ekKukZh0UuKMvLdTmEWFk=
go.podman.io/image/v5 v5.39.2/go.mod h1:SlaR6Pra1ATIx4BcuZ16oafb3QcCHISaKcJbtlN/G/0=
go.podman.io/storage v1.62.0 h1:0QjX1XlzVmbiaulb+aR/CG6p9+pzaqwIeZPe3tEjHbY=
go.podman.io/storage v1.62.0/go.mod h1:A3UBK0XypjNZ6pghRhuxg62+2NIm5lcUGv/7XyMhMUI=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=

View File

@@ -35,6 +35,7 @@ import (
"go.podman.io/image/v5/types"
"go.podman.io/storage/pkg/fileutils"
"go.podman.io/storage/pkg/homedir"
"golang.org/x/sync/semaphore"
)
const (
@@ -86,8 +87,19 @@ type extensionSignatureList struct {
Signatures []extensionSignature `json:"signatures"`
}
// bearerToken records a cached token we can use to authenticate.
// bearerToken records a cached token we can use to authenticate, or a pending process to obtain one.
//
// The goroutine obtaining the token holds lock to block concurrent token requests, and fills the structure (err and possibly the other fields)
// before releasing the lock.
// Other goroutines obtain lock to block on the token request, if any; and then inspect err to see if the token is usable.
// If it is not, they try to get a new one.
type bearerToken struct {
// lock is held while obtaining the token. Potentially nested inside dockerClient.tokenCacheLock.
// This is a counting semaphore only because we need a cancellable lock operation.
lock *semaphore.Weighted
// The following fields can only be accessed with lock held.
err error // nil if the token was successfully obtained (but may be expired); an error if the next lock holder _must_ obtain a new token.
token string
expirationTime time.Time
}
@@ -116,8 +128,9 @@ type dockerClient struct {
challenges []challenge
supportsSignatures bool
// Private state for setupRequestAuth (key: string, value: bearerToken)
tokenCache sync.Map
// Private state for setupRequestAuth
tokenCacheLock sync.Mutex // Protects tokenCache.
tokenCache map[string]*bearerToken
// Private state for detectProperties:
detectPropertiesOnce sync.Once // detectPropertiesOnce is used to execute detectProperties() at most once.
detectPropertiesError error // detectPropertiesError caches the initial error.
@@ -274,6 +287,7 @@ func newDockerClient(sys *types.SystemContext, registry, reference string) (*doc
registry: registry,
userAgent: userAgent,
tlsClientConfig: tlsClientConfig,
tokenCache: map[string]*bearerToken{},
reportedWarnings: set.New[string](),
}, nil
}
@@ -716,50 +730,11 @@ func (c *dockerClient) setupRequestAuth(req *http.Request, extraScope *authScope
req.SetBasicAuth(c.auth.Username, c.auth.Password)
return nil
case "bearer":
registryToken := c.registryToken
if registryToken == "" {
cacheKey := ""
scopes := []authScope{c.scope}
if extraScope != nil {
// Using ':' as a separator here is unambiguous because getBearerToken below
// uses the same separator when formatting a remote request (and because
// repository names that we create can't contain colons, and extraScope values
// coming from a server come from `parseAuthScope`, which also splits on colons).
cacheKey = fmt.Sprintf("%s:%s:%s", extraScope.resourceType, extraScope.remoteName, extraScope.actions)
if colonCount := strings.Count(cacheKey, ":"); colonCount != 2 {
return fmt.Errorf(
"Internal error: there must be exactly 2 colons in the cacheKey ('%s') but got %d",
cacheKey,
colonCount,
)
}
scopes = append(scopes, *extraScope)
}
var token bearerToken
t, inCache := c.tokenCache.Load(cacheKey)
if inCache {
token = t.(bearerToken)
}
if !inCache || time.Now().After(token.expirationTime) {
var (
t *bearerToken
err error
)
if c.auth.IdentityToken != "" {
t, err = c.getBearerTokenOAuth2(req.Context(), challenge, scopes)
} else {
t, err = c.getBearerToken(req.Context(), challenge, scopes)
}
if err != nil {
return err
}
token = *t
c.tokenCache.Store(cacheKey, token)
}
registryToken = token.token
token, err := c.obtainBearerToken(req.Context(), challenge, extraScope)
if err != nil {
return err
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", registryToken))
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
return nil
default:
logrus.Debugf("no handler for %s authentication", challenge.Scheme)
@@ -769,16 +744,94 @@ func (c *dockerClient) setupRequestAuth(req *http.Request, extraScope *authScope
return nil
}
func (c *dockerClient) getBearerTokenOAuth2(ctx context.Context, challenge challenge,
scopes []authScope) (*bearerToken, error) {
// obtainBearerToken gets an "Authorization: Bearer" token if one is available, or obtains a fresh one.
func (c *dockerClient) obtainBearerToken(ctx context.Context, challenge challenge, extraScope *authScope) (string, error) {
if c.registryToken != "" {
return c.registryToken, nil
}
cacheKey := ""
scopes := []authScope{c.scope}
if extraScope != nil {
// Using ':' as a separator here is unambiguous because getBearerToken below
// uses the same separator when formatting a remote request (and because
// repository names that we create can't contain colons, and extraScope values
// coming from a server come from `parseAuthScope`, which also splits on colons).
cacheKey = fmt.Sprintf("%s:%s:%s", extraScope.resourceType, extraScope.remoteName, extraScope.actions)
if colonCount := strings.Count(cacheKey, ":"); colonCount != 2 {
return "", fmt.Errorf(
"Internal error: there must be exactly 2 colons in the cacheKey ('%s') but got %d",
cacheKey,
colonCount,
)
}
scopes = append(scopes, *extraScope)
}
token, newEntry, err := func() (*bearerToken, bool, error) { // A scope for defer
c.tokenCacheLock.Lock()
defer c.tokenCacheLock.Unlock()
token, ok := c.tokenCache[cacheKey]
if ok {
return token, false, nil
} else {
token = &bearerToken{
lock: semaphore.NewWeighted(1),
}
// If this is a new *bearerToken, lock the entry before adding it to the cache, so that any other goroutine that finds
// this entry blocks until we obtain the token for the first time, and does not see an empty object
// (and does not try to obtain the token itself when we are going to do so).
if err := token.lock.Acquire(ctx, 1); err != nil {
// We do not block on this Acquire, so we dont really expect to fail here — but if ctx is canceled,
// there is no point in trying to continue anyway.
return nil, false, err
}
c.tokenCache[cacheKey] = token
return token, true, nil
}
}()
if err != nil {
return "", err
}
if !newEntry {
// If this is an existing *bearerToken, obtain the lock only after releasing c.tokenCacheLock,
// so that users of other cacheKey values are not blocked for the whole duration of our HTTP roundtrip.
if err := token.lock.Acquire(ctx, 1); err != nil {
return "", err
}
}
defer token.lock.Release(1)
if !newEntry && token.err == nil && !time.Now().After(token.expirationTime) {
return token.token, nil // We have a usable token already.
}
if c.auth.IdentityToken != "" {
err = c.getBearerTokenOAuth2(ctx, token, challenge, scopes)
} else {
err = c.getBearerToken(ctx, token, challenge, scopes)
}
token.err = err
if token.err != nil {
return "", token.err
}
return token.token, nil
}
// getBearerTokenOAuth2 obtains an "Authorization: Bearer" token using a pre-existing identity token per
// https://github.com/distribution/distribution/blob/main/docs/spec/auth/oauth.md for challenge and scopes,
// and writes it into dest.
func (c *dockerClient) getBearerTokenOAuth2(ctx context.Context, dest *bearerToken, challenge challenge,
scopes []authScope) error {
realm, ok := challenge.Parameters["realm"]
if !ok {
return nil, errors.New("missing realm in bearer auth challenge")
return errors.New("missing realm in bearer auth challenge")
}
authReq, err := http.NewRequestWithContext(ctx, http.MethodPost, realm, nil)
if err != nil {
return nil, err
return err
}
// Make the form data required against the oauth2 authentication
@@ -803,26 +856,29 @@ func (c *dockerClient) getBearerTokenOAuth2(ctx context.Context, challenge chall
logrus.Debugf("%s %s", authReq.Method, authReq.URL.Redacted())
res, err := c.client.Do(authReq)
if err != nil {
return nil, err
return err
}
defer res.Body.Close()
if err := httpResponseToError(res, "Trying to obtain access token"); err != nil {
return nil, err
return err
}
return newBearerTokenFromHTTPResponseBody(res)
return dest.readFromHTTPResponseBody(res)
}
func (c *dockerClient) getBearerToken(ctx context.Context, challenge challenge,
scopes []authScope) (*bearerToken, error) {
// getBearerToken obtains an "Authorization: Bearer" token using a GET request, per
// https://github.com/distribution/distribution/blob/main/docs/spec/auth/token.md for challenge and scopes,
// and writes it into dest.
func (c *dockerClient) getBearerToken(ctx context.Context, dest *bearerToken, challenge challenge,
scopes []authScope) error {
realm, ok := challenge.Parameters["realm"]
if !ok {
return nil, errors.New("missing realm in bearer auth challenge")
return errors.New("missing realm in bearer auth challenge")
}
authReq, err := http.NewRequestWithContext(ctx, http.MethodGet, realm, nil)
if err != nil {
return nil, err
return err
}
params := authReq.URL.Query()
@@ -850,22 +906,22 @@ func (c *dockerClient) getBearerToken(ctx context.Context, challenge challenge,
logrus.Debugf("%s %s", authReq.Method, authReq.URL.Redacted())
res, err := c.client.Do(authReq)
if err != nil {
return nil, err
return err
}
defer res.Body.Close()
if err := httpResponseToError(res, "Requesting bearer token"); err != nil {
return nil, err
return err
}
return newBearerTokenFromHTTPResponseBody(res)
return dest.readFromHTTPResponseBody(res)
}
// newBearerTokenFromHTTPResponseBody parses a http.Response to obtain a bearerToken.
// readFromHTTPResponseBody sets token data by parsing a http.Response.
// The caller is still responsible for ensuring res.Body is closed.
func newBearerTokenFromHTTPResponseBody(res *http.Response) (*bearerToken, error) {
func (bt *bearerToken) readFromHTTPResponseBody(res *http.Response) error {
blob, err := iolimits.ReadAtMost(res.Body, iolimits.MaxAuthTokenBodySize)
if err != nil {
return nil, err
return err
}
var token struct {
@@ -881,12 +937,10 @@ func newBearerTokenFromHTTPResponseBody(res *http.Response) (*bearerToken, error
if len(bodySample) > bodySampleLength {
bodySample = bodySample[:bodySampleLength]
}
return nil, fmt.Errorf("decoding bearer token (last URL %q, body start %q): %w", res.Request.URL.Redacted(), string(bodySample), err)
return fmt.Errorf("decoding bearer token (last URL %q, body start %q): %w", res.Request.URL.Redacted(), string(bodySample), err)
}
bt := &bearerToken{
token: token.Token,
}
bt.token = token.Token
if bt.token == "" {
bt.token = token.AccessToken
}
@@ -899,7 +953,7 @@ func newBearerTokenFromHTTPResponseBody(res *http.Response) (*bearerToken, error
token.IssuedAt = time.Now().UTC()
}
bt.expirationTime = token.IssuedAt.Add(time.Duration(token.ExpiresIn) * time.Second)
return bt, nil
return nil
}
// detectPropertiesHelper performs the work of detectProperties which executes

View File

@@ -8,7 +8,7 @@ const (
// VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 39
// VersionPatch is for backwards-compatible bug fixes
VersionPatch = 1
VersionPatch = 2
// VersionDev indicates development branch. Releases will be empty string.
VersionDev = ""

4
vendor/modules.txt vendored
View File

@@ -351,7 +351,7 @@ go.opentelemetry.io/otel/trace
go.opentelemetry.io/otel/trace/embedded
go.opentelemetry.io/otel/trace/internal/telemetry
go.opentelemetry.io/otel/trace/noop
# go.podman.io/common v0.67.0
# go.podman.io/common v0.67.1
## explicit; go 1.24.2
go.podman.io/common/pkg/auth
go.podman.io/common/pkg/capabilities
@@ -361,7 +361,7 @@ go.podman.io/common/pkg/password
go.podman.io/common/pkg/report
go.podman.io/common/pkg/report/camelcase
go.podman.io/common/pkg/retry
# go.podman.io/image/v5 v5.39.1
# go.podman.io/image/v5 v5.39.2
## explicit; go 1.24.0
go.podman.io/image/v5/copy
go.podman.io/image/v5/directory