updated vendor

This commit is contained in:
2026-06-16 08:02:19 +02:00
parent 2f7f99d3f0
commit 77299d0c64
1283 changed files with 67302 additions and 208958 deletions
+1 -1
View File
@@ -109,7 +109,7 @@ func (cc *concurrentCache) Set(ctx context.Context, registry string, scheme Sche
}, " ")
statusValue, _ := cc.status.LoadOrStore(statusKey, syncutil.NewOnce())
fetchOnce := statusValue.(*syncutil.Once)
fetchedFirst, result, err := fetchOnce.Do(ctx, func() (interface{}, error) {
fetchedFirst, result, err := fetchOnce.Do(ctx, func() (any, error) {
return fetch(ctx)
})
if fetchedFirst {
+101 -1
View File
@@ -23,6 +23,7 @@ import (
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
@@ -136,7 +137,50 @@ func (c *Client) send(req *http.Request) (*http.Response, error) {
for key, values := range c.Header {
req.Header[key] = append(req.Header[key], values...)
}
return c.client().Do(req)
// Drop the Authorization header when a redirect crosses an HTTP origin
// (scheme, host, or port). The standard library only strips sensitive
// headers when the hostname changes, so a redirect to a different port on
// the same host would otherwise forward credentials to an unintended
// endpoint. Any caller-provided CheckRedirect is preserved.
// Reference: https://github.com/oras-project/oras-go/security/advisories/GHSA-vh4v-2xq2-g5cg
client := c.client()
clientCopy := *client
checkRedirect := client.CheckRedirect
clientCopy.CheckRedirect = func(req *http.Request, via []*http.Request) error {
if len(via) > 0 && !sameHTTPOrigin(via[len(via)-1].URL, req.URL) {
req.Header.Del("Authorization")
}
if checkRedirect != nil {
return checkRedirect(req, via)
}
return nil
}
return clientCopy.Do(req)
}
// sameHTTPOrigin reports whether a and b share the same HTTP origin, i.e. the
// same scheme and host. Default ports are normalized so that, for example,
// "example.com" and "example.com:443" compare equal over https.
func sameHTTPOrigin(a, b *url.URL) bool {
if !strings.EqualFold(a.Scheme, b.Scheme) {
return false
}
return canonicalHost(a) == canonicalHost(b)
}
// canonicalHost returns the lower-cased host of u with the default port for its
// scheme applied when no explicit port is present.
func canonicalHost(u *url.URL) string {
port := u.Port()
if port == "" {
switch strings.ToLower(u.Scheme) {
case "https":
port = "443"
case "http":
port = "80"
}
}
return strings.ToLower(u.Hostname()) + ":" + port
}
// credential resolves the credential for the given registry.
@@ -156,6 +200,49 @@ func (c *Client) cache() Cache {
return c.Cache
}
// validateRealm rejects bearer token realm URLs that would have the client
// forward credentials to obviously unsafe destinations:
//
// - schemes other than http or https,
// - http realms when the registry was contacted over https (TLS downgrade),
// - hosts that are IP literals in loopback, link-local, private, or
// unspecified ranges (e.g. cloud instance metadata services such as
// 169.254.169.254).
//
// Cross-host realms with a public hostname are permitted, because the
// distribution spec allows a separate token endpoint (e.g. Docker Hub's
// auth.docker.io). When the registry itself is reached at the same hostname
// as the realm, the IP-literal check is skipped so loopback and in-cluster
// deployments continue to work.
func validateRealm(realm string, registryURL *url.URL) error {
if realm == "" {
return nil
}
realmURL, err := url.Parse(realm)
if err != nil {
return fmt.Errorf("failed to parse bearer realm %q: %w", realm, err)
}
switch realmURL.Scheme {
case "https":
// always allowed
case "http":
if registryURL != nil && registryURL.Scheme == "https" {
return fmt.Errorf("bearer realm %q uses http but registry was contacted over https", realm)
}
default:
return fmt.Errorf("bearer realm %q uses unsupported scheme %q", realm, realmURL.Scheme)
}
if ip := net.ParseIP(realmURL.Hostname()); ip != nil {
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() ||
ip.IsPrivate() || ip.IsUnspecified() {
if registryURL == nil || realmURL.Hostname() != registryURL.Hostname() {
return fmt.Errorf("bearer realm host %q is a loopback, link-local, private, or unspecified address", realmURL.Hostname())
}
}
}
return nil
}
// SetUserAgent sets the user agent for all out-going requests.
func (c *Client) SetUserAgent(userAgent string) {
if c.Header == nil {
@@ -182,6 +269,9 @@ func (c *Client) Do(originalReq *http.Request) (*http.Response, error) {
var attemptedKey string
cache := c.cache()
host := originalReq.Host
if host == "" {
host = originalReq.URL.Host
}
scheme, err := cache.GetScheme(ctx, host)
if err == nil {
switch scheme {
@@ -207,6 +297,13 @@ func (c *Client) Do(originalReq *http.Request) (*http.Response, error) {
if resp.StatusCode != http.StatusUnauthorized {
return resp, nil
}
// If the challenge came from a different origin than originally requested
// (e.g. the request was redirected to another host or port), do not resolve
// or send the registry credentials to that origin.
// Reference: https://github.com/oras-project/oras-go/security/advisories/GHSA-vh4v-2xq2-g5cg
if resp.Request != nil && !sameHTTPOrigin(originalReq.URL, resp.Request.URL) {
return resp, nil
}
// attempt again with credentials for recognized schemes
challenge := resp.Header.Get("Www-Authenticate")
@@ -257,6 +354,9 @@ func (c *Client) Do(originalReq *http.Request) (*http.Response, error) {
// attempt with credentials
realm := params["realm"]
if err := validateRealm(realm, originalReq.URL); err != nil {
return nil, fmt.Errorf("%s %q: %w", resp.Request.Method, resp.Request.URL, err)
}
service := params["service"]
token, err := cache.Set(ctx, host, SchemeBearer, key, func(ctx context.Context) (string, error) {
return c.fetchBearerToken(ctx, host, realm, service, scopes)
+1 -1
View File
@@ -254,7 +254,7 @@ func CleanScopes(scopes []string) []string {
actionSet = make(map[string]struct{})
namedActions[resourceName] = actionSet
}
for _, action := range strings.Split(actions, ",") {
for action := range strings.SplitSeq(actions, ",") {
if action != "" {
actionSet[action] = struct{}{}
}
@@ -21,6 +21,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
@@ -128,7 +129,13 @@ func Load(configPath string) (*Config, error) {
// decode config content if the config file exists
if err := json.NewDecoder(configFile).Decode(&cfg.content); err != nil {
return nil, fmt.Errorf("failed to decode config file at %s: %w: %v", configPath, ErrInvalidConfigFormat, err)
if errors.Is(err, io.EOF) {
// empty or whitespace only file
cfg.content = make(map[string]json.RawMessage)
cfg.authsCache = make(map[string]json.RawMessage)
return cfg, nil
}
return nil, fmt.Errorf("failed to decode config file %s: %w: %v", configPath, ErrInvalidConfigFormat, err)
}
if credsStoreBytes, ok := cfg.content[configFieldCredentialsStore]; ok {
@@ -81,10 +81,12 @@ func Credential(store Store) auth.CredentialFunc {
// ServerAddressFromRegistry maps a registry to a server address, which is used as
// a key for credentials store. The Docker CLI expects that the credentials of
// the registry 'docker.io' will be added under the key "https://index.docker.io/v1/".
// the registry 'registry-1.docker.io' or the alias 'docker.io' will be added
// under the key "https://index.docker.io/v1/".
// See: https://github.com/moby/moby/blob/v24.0.2/registry/config.go#L25-L48
func ServerAddressFromRegistry(registry string) string {
if registry == "docker.io" {
if registry == "docker.io" ||
registry == "registry-1.docker.io" {
return "https://index.docker.io/v1/"
}
return registry
+2 -6
View File
@@ -16,6 +16,7 @@ limitations under the License.
package remote
import (
"slices"
"strings"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -41,12 +42,7 @@ func isManifest(manifestMediaTypes []string, desc ocispec.Descriptor) bool {
if len(manifestMediaTypes) == 0 {
manifestMediaTypes = defaultManifestMediaTypes
}
for _, mediaType := range manifestMediaTypes {
if desc.MediaType == mediaType {
return true
}
}
return false
return slices.Contains(manifestMediaTypes, desc.MediaType)
}
// manifestAcceptHeader generates the set in the `Accept` header for resolving
+2 -2
View File
@@ -118,8 +118,8 @@ func isReferrersFilterApplied(applied, requested string) bool {
if applied == "" || requested == "" {
return false
}
filters := strings.Split(applied, ",")
for _, f := range filters {
filters := strings.SplitSeq(applied, ",")
for f := range filters {
if f == requested {
return true
}
@@ -24,6 +24,7 @@ import (
"io"
"mime"
"net/http"
"net/url"
"slices"
"strconv"
"strings"
@@ -872,6 +873,25 @@ func (s *blobStore) Push(ctx context.Context, expected ocispec.Descriptor, conte
return s.completePushAfterInitialPost(ctx, req, resp, expected, content)
}
// sameUploadHost reports whether location and reqURL refer to the same host,
// normalizing implicit default ports (80 for http, 443 for https) so that
// e.g. "example.com" and "example.com:443" compare equal over HTTPS.
func sameUploadHost(location, reqURL *url.URL) bool {
if location.Hostname() != reqURL.Hostname() {
return false
}
canonicalPort := func(u *url.URL) string {
if p := u.Port(); p != "" {
return p
}
if u.Scheme == "https" {
return "443"
}
return "80"
}
return canonicalPort(location) == canonicalPort(reqURL)
}
// completePushAfterInitialPost implements step 2 of the push protocol. This can be invoked either by
// Push or by Mount when the receiving repository does not implement the
// mount endpoint.
@@ -894,6 +914,15 @@ func (s *blobStore) completePushAfterInitialPost(ctx context.Context, req *http.
if reqPort == "443" && locationHostname == reqHostname && locationPort == "" {
location.Host = locationHostname + ":" + reqPort
}
// Validate the Location stays on the same host to prevent credentials from
// being forwarded to an attacker-controlled endpoint.
// Reference: https://github.com/oras-project/oras-go/security/advisories/GHSA-jxpm-75mh-9fp7
if !sameUploadHost(location, req.URL) {
return fmt.Errorf("blob upload Location %q is on a different host than the registry %q", location.Host, req.URL.Host)
}
if req.URL.Scheme == "https" && location.Scheme != "https" {
return fmt.Errorf("blob upload Location %q downgrades scheme from https", location.Host)
}
url := location.String()
req, err = http.NewRequestWithContext(ctx, http.MethodPut, url, content)
if err != nil {