diff --git a/go.mod b/go.mod index 0e1fc28d68..395c61d17d 100644 --- a/go.mod +++ b/go.mod @@ -118,7 +118,7 @@ require ( golang.org/x/text v0.43.0 golang.org/x/time v0.16.0 google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc - google.golang.org/grpc v1.84.0 + google.golang.org/grpc v1.85.0 google.golang.org/protobuf v1.36.12 gotest.tools/v3 v3.5.2 pgregory.net/rapid v1.3.0 diff --git a/go.sum b/go.sum index 839f7836f7..c6f6bc310c 100644 --- a/go.sum +++ b/go.sum @@ -231,8 +231,8 @@ github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4/go.mod h1:cyGadeNE github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= -github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= -github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= +github.com/envoyproxy/go-control-plane/envoy v1.39.1-0.20260819172001-e6e3fd93e4be h1:SWe0x6yfglnxuvOiYgTTnNq7QD/yvqthOh1RBI8Bj8w= +github.com/envoyproxy/go-control-plane/envoy v1.39.1-0.20260819172001-e6e3fd93e4be/go.mod h1:PYEOlng9XcrulfyWpm49jECTPV0LT4q8cO7fLW/xwgk= github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= github.com/erofs/go-erofs v0.3.1 h1:Sux82Jq9yvyYhIoLgSHDp741p/+370HsOj9dAh1+VVs= @@ -867,8 +867,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc h1: google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc/go.mod h1:K/n0XLazCJHjD73YuMyNJsN38BPPy1kfFnKxUXaThFU= google.golang.org/genproto/googleapis/rpc v0.0.0-20260928230214-8a89bd6388cc h1:4bNTbnb44EqGVy9HaQxSY2jnafSUdgV3qHtedvNpDKg= google.golang.org/genproto/googleapis/rpc v0.0.0-20260928230214-8a89bd6388cc/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= -google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= -google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= +google.golang.org/grpc v1.85.0 h1:BPBCfuVRQ1qvBhKDOq9uvQllMvO59rKsYhXpp7Dd8vw= +google.golang.org/grpc v1.85.0/go.mod h1:0rQrOD58yjal/0EaVbGBANindixsCZNQPDV3VKtaKuQ= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/vendor/cloud.google.com/go/auth/credentials/idtoken/cache.go b/vendor/cloud.google.com/go/auth/credentials/idtoken/cache.go deleted file mode 100644 index 2fbbdb8072..0000000000 --- a/vendor/cloud.google.com/go/auth/credentials/idtoken/cache.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package idtoken - -import ( - "context" - "encoding/json" - "fmt" - "log/slog" - "net/http" - "strconv" - "strings" - "sync" - "time" - - "cloud.google.com/go/auth/internal" - "github.com/googleapis/gax-go/v2/internallog" -) - -type cachingClient struct { - client *http.Client - - // clock optionally specifies a func to return the current time. - // If nil, time.Now is used. - clock func() time.Time - - mu sync.Mutex - certs map[string]*cachedResponse - logger *slog.Logger -} - -func newCachingClient(client *http.Client, logger *slog.Logger) *cachingClient { - return &cachingClient{ - client: client, - certs: make(map[string]*cachedResponse, 2), - logger: logger, - } -} - -type cachedResponse struct { - resp *certResponse - exp time.Time -} - -func (c *cachingClient) getCert(ctx context.Context, url string) (*certResponse, error) { - if response, ok := c.get(url); ok { - return response, nil - } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, err - } - c.logger.DebugContext(ctx, "cert request", "request", internallog.HTTPRequest(req, nil)) - resp, body, err := internal.DoRequest(c.client, req) - if err != nil { - return nil, err - } - c.logger.DebugContext(ctx, "cert response", "response", internallog.HTTPResponse(resp, body)) - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("idtoken: unable to retrieve cert, got status code %d", resp.StatusCode) - } - - certResp := &certResponse{} - if err := json.Unmarshal(body, &certResp); err != nil { - return nil, err - - } - c.set(url, certResp, resp.Header) - return certResp, nil -} - -func (c *cachingClient) now() time.Time { - if c.clock != nil { - return c.clock() - } - return time.Now() -} - -func (c *cachingClient) get(url string) (*certResponse, bool) { - c.mu.Lock() - defer c.mu.Unlock() - cachedResp, ok := c.certs[url] - if !ok { - return nil, false - } - if c.now().After(cachedResp.exp) { - return nil, false - } - return cachedResp.resp, true -} - -func (c *cachingClient) set(url string, resp *certResponse, headers http.Header) { - exp := c.calculateExpireTime(headers) - c.mu.Lock() - c.certs[url] = &cachedResponse{resp: resp, exp: exp} - c.mu.Unlock() -} - -// calculateExpireTime will determine the expire time for the cache based on -// HTTP headers. If there is any difficulty reading the headers the fallback is -// to set the cache to expire now. -func (c *cachingClient) calculateExpireTime(headers http.Header) time.Time { - var maxAge int - cc := strings.Split(headers.Get("cache-control"), ",") - for _, v := range cc { - if strings.Contains(v, "max-age") { - ss := strings.Split(v, "=") - if len(ss) < 2 { - return c.now() - } - ma, err := strconv.Atoi(ss[1]) - if err != nil { - return c.now() - } - maxAge = ma - } - } - a := headers.Get("age") - if a == "" { - return c.now().Add(time.Duration(maxAge) * time.Second) - } - age, err := strconv.Atoi(a) - if err != nil { - return c.now() - } - return c.now().Add(time.Duration(maxAge-age) * time.Second) -} diff --git a/vendor/cloud.google.com/go/auth/credentials/idtoken/compute.go b/vendor/cloud.google.com/go/auth/credentials/idtoken/compute.go deleted file mode 100644 index 86f6cd77ac..0000000000 --- a/vendor/cloud.google.com/go/auth/credentials/idtoken/compute.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package idtoken - -import ( - "context" - "fmt" - "net/url" - "time" - - "cloud.google.com/go/auth" - "cloud.google.com/go/auth/internal" - "cloud.google.com/go/compute/metadata" - "github.com/googleapis/gax-go/v2/internallog" -) - -const identitySuffix = "instance/service-accounts/default/identity" - -// computeCredentials checks if this code is being run on GCE. If it is, it -// will use the metadata service to build a Credentials that fetches ID -// tokens. -func computeCredentials(opts *Options) (*auth.Credentials, error) { - if opts.CustomClaims != nil { - return nil, fmt.Errorf("idtoken: Options.CustomClaims can't be used with the metadata service, please provide a service account if you would like to use this feature") - } - metadataClient := metadata.NewWithOptions(&metadata.Options{ - Logger: internallog.New(opts.Logger), - }) - tp := &computeIDTokenProvider{ - audience: opts.Audience, - format: opts.ComputeTokenFormat, - client: metadataClient, - } - return auth.NewCredentials(&auth.CredentialsOptions{ - TokenProvider: auth.NewCachedTokenProvider(tp, &auth.CachedTokenProviderOptions{ - ExpireEarly: 5 * time.Minute, - }), - ProjectIDProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) { - return metadataClient.ProjectIDWithContext(ctx) - }), - UniverseDomainProvider: &internal.ComputeUniverseDomainProvider{ - MetadataClient: metadataClient, - }, - }), nil -} - -type computeIDTokenProvider struct { - audience string - format ComputeTokenFormat - client *metadata.Client -} - -func (c *computeIDTokenProvider) Token(ctx context.Context) (*auth.Token, error) { - v := url.Values{} - v.Set("audience", c.audience) - if c.format != ComputeTokenFormatStandard { - v.Set("format", "full") - } - if c.format == ComputeTokenFormatFullWithLicense { - v.Set("licenses", "TRUE") - } - urlSuffix := identitySuffix + "?" + v.Encode() - res, err := c.client.GetWithContext(ctx, urlSuffix) - if err != nil { - return nil, err - } - if res == "" { - return nil, fmt.Errorf("idtoken: invalid empty response from metadata service") - } - return &auth.Token{ - Value: res, - Type: internal.TokenTypeBearer, - // Compute tokens are valid for one hour: - // https://cloud.google.com/iam/docs/create-short-lived-credentials-direct#create-id - Expiry: time.Now().Add(1 * time.Hour), - }, nil -} diff --git a/vendor/cloud.google.com/go/auth/credentials/idtoken/file.go b/vendor/cloud.google.com/go/auth/credentials/idtoken/file.go deleted file mode 100644 index ac242f9ae2..0000000000 --- a/vendor/cloud.google.com/go/auth/credentials/idtoken/file.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package idtoken - -import ( - "encoding/json" - "fmt" - "path/filepath" - "strings" - - "cloud.google.com/go/auth" - "cloud.google.com/go/auth/credentials" - "cloud.google.com/go/auth/credentials/impersonate" - intimpersonate "cloud.google.com/go/auth/credentials/internal/impersonate" - "cloud.google.com/go/auth/internal" - "cloud.google.com/go/auth/internal/credsfile" - "github.com/googleapis/gax-go/v2/internallog" -) - -const ( - jwtTokenURL = "https://oauth2.googleapis.com/token" - iamCredAud = "https://iamcredentials.googleapis.com/" -) - -func credsFromDefault(creds *auth.Credentials, opts *Options) (*auth.Credentials, error) { - b := creds.JSON() - t, err := credsfile.ParseFileType(b) - if err != nil { - return nil, err - } - switch credentials.CredType(t) { - case credentials.ServiceAccount: - f, err := credsfile.ParseServiceAccount(b) - if err != nil { - return nil, err - } - var tp auth.TokenProvider - if resolveUniverseDomain(f) == internal.DefaultUniverseDomain { - tp, err = new2LOTokenProvider(f, opts) - if err != nil { - return nil, err - } - } else { - // In case of non-GDU universe domain, use IAM. - tp = intimpersonate.IDTokenIAMOptions{ - Client: opts.client(), - Logger: internallog.New(opts.Logger), - // Pass the credentials universe domain to configure the endpoint. - UniverseDomain: auth.CredentialsPropertyFunc(creds.UniverseDomain), - ServiceAccountEmail: f.ClientEmail, - GenerateIDTokenRequest: intimpersonate.GenerateIDTokenRequest{ - Audience: opts.Audience, - }, - } - } - tp = auth.NewCachedTokenProvider(tp, nil) - return auth.NewCredentials(&auth.CredentialsOptions{ - TokenProvider: tp, - JSON: b, - ProjectIDProvider: auth.CredentialsPropertyFunc(creds.ProjectID), - UniverseDomainProvider: auth.CredentialsPropertyFunc(creds.UniverseDomain), - }), nil - case credentials.ImpersonatedServiceAccount, credentials.ExternalAccount: - type url struct { - ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"` - } - var accountURL url - if err := json.Unmarshal(b, &accountURL); err != nil { - return nil, err - } - account := filepath.Base(accountURL.ServiceAccountImpersonationURL) - account = strings.Split(account, ":")[0] - config := impersonate.IDTokenOptions{ - Audience: opts.Audience, - TargetPrincipal: account, - IncludeEmail: true, - Client: opts.client(), - Credentials: creds, - Logger: internallog.New(opts.Logger), - } - idTokenCreds, err := impersonate.NewIDTokenCredentials(&config) - if err != nil { - return nil, err - } - return auth.NewCredentials(&auth.CredentialsOptions{ - TokenProvider: idTokenCreds, - JSON: b, - ProjectIDProvider: auth.CredentialsPropertyFunc(creds.ProjectID), - UniverseDomainProvider: auth.CredentialsPropertyFunc(creds.UniverseDomain), - QuotaProjectIDProvider: auth.CredentialsPropertyFunc(creds.QuotaProjectID), - }), nil - default: - return nil, fmt.Errorf("idtoken: unsupported credentials type: %v", t) - } -} - -func new2LOTokenProvider(f *credsfile.ServiceAccountFile, opts *Options) (auth.TokenProvider, error) { - opts2LO := &auth.Options2LO{ - Email: f.ClientEmail, - PrivateKey: []byte(f.PrivateKey), - PrivateKeyID: f.PrivateKeyID, - TokenURL: f.TokenURL, - UseIDToken: true, - Logger: internallog.New(opts.Logger), - } - if opts2LO.TokenURL == "" { - opts2LO.TokenURL = jwtTokenURL - } - - var customClaims map[string]interface{} - if opts != nil { - customClaims = opts.CustomClaims - } - if customClaims == nil { - customClaims = make(map[string]interface{}) - } - customClaims["target_audience"] = opts.Audience - - opts2LO.PrivateClaims = customClaims - return auth.New2LOTokenProvider(opts2LO) -} - -// resolveUniverseDomain returns the default service domain for a given -// Cloud universe. This is the universe domain configured for the credentials, -// which will be used in endpoint. -func resolveUniverseDomain(f *credsfile.ServiceAccountFile) string { - if f.UniverseDomain != "" { - return f.UniverseDomain - } - return internal.DefaultUniverseDomain -} diff --git a/vendor/cloud.google.com/go/auth/credentials/idtoken/idtoken.go b/vendor/cloud.google.com/go/auth/credentials/idtoken/idtoken.go deleted file mode 100644 index 765bc8777b..0000000000 --- a/vendor/cloud.google.com/go/auth/credentials/idtoken/idtoken.go +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package idtoken provides functionality for generating and validating ID -// tokens, with configurable options for audience, custom claims, and token -// formats. -// -// For more information on ID tokens, see -// https://cloud.google.com/docs/authentication/token-types#id. -package idtoken - -import ( - "errors" - "log/slog" - "net/http" - "os" - - "cloud.google.com/go/auth" - "cloud.google.com/go/auth/credentials" - "cloud.google.com/go/auth/internal" - "cloud.google.com/go/auth/internal/credsfile" - "cloud.google.com/go/compute/metadata" -) - -// ComputeTokenFormat dictates the the token format when requesting an ID token -// from the compute metadata service. -type ComputeTokenFormat int - -const ( - // ComputeTokenFormatDefault means the same as [ComputeTokenFormatFull]. - ComputeTokenFormatDefault ComputeTokenFormat = iota - // ComputeTokenFormatStandard mean only standard JWT fields will be included - // in the token. - ComputeTokenFormatStandard - // ComputeTokenFormatFull means the token will include claims about the - // virtual machine instance and its project. - ComputeTokenFormatFull - // ComputeTokenFormatFullWithLicense means the same as - // [ComputeTokenFormatFull] with the addition of claims about licenses - // associated with the instance. - ComputeTokenFormatFullWithLicense -) - -var ( - defaultScopes = []string{ - "https://iamcredentials.googleapis.com/", - "https://www.googleapis.com/auth/cloud-platform", - } - - errMissingOpts = errors.New("idtoken: opts must be provided") - errMissingAudience = errors.New("idtoken: Audience must be provided") - errBothFileAndJSON = errors.New("idtoken: CredentialsFile and CredentialsJSON must not both be provided") -) - -// Options for the configuration of creation of an ID token with -// [NewCredentials]. -type Options struct { - // Audience is the `aud` field for the token, such as an API endpoint the - // token will grant access to. Required. - Audience string - // ComputeTokenFormat dictates the the token format when requesting an ID - // token from the compute metadata service. Optional. - ComputeTokenFormat ComputeTokenFormat - // CustomClaims specifies private non-standard claims for an ID token. - // Optional. - CustomClaims map[string]interface{} - - // CredentialsFile sources a JSON credential file from the provided - // filepath. If provided, do not provide CredentialsJSON. Optional. - // - // Deprecated: This field is deprecated because of a potential security risk. - // It does not validate the credential configuration. The security risk occurs - // when a credential configuration is accepted from a source that is not - // under your control and used without validation on your side. - // - // If you know that you will be loading credential configurations of a - // specific type, it is recommended to use a credential-type-specific - // NewCredentialsFromFile method. This will ensure that an unexpected - // credential type with potential for malicious intent is not loaded - // unintentionally. You might still have to do validation for certain - // credential types. Please follow the recommendation for that method. For - // example, if you want to load only service accounts, you can use - // - // creds, err := idtoken.NewCredentialsFromFile(ctx, credentials.ServiceAccount, filename, opts) - // - // If you are loading your credential configuration from an untrusted source - // and have not mitigated the risks (e.g. by validating the configuration - // yourself), make these changes as soon as possible to prevent security - // risks to your environment. - // - // Regardless of the method used, it is always your responsibility to - // validate configurations received from external sources. - // - // For more details see: - // https://cloud.google.com/docs/authentication/external/externally-sourced-credentials - CredentialsFile string - // CredentialsJSON sources a JSON credential file from the provided bytes. - // If provided, do not provide CredentialsJSON. Optional. - // - // Deprecated: This field is deprecated because of a potential security risk. - // It does not validate the credential configuration. The security risk occurs - // when a credential configuration is accepted from a source that is not - // under your control and used without validation on your side. - // - // If you know that you will be loading credential configurations of a - // specific type, it is recommended to use a credential-type-specific - // NewCredentialsFromJSON method. This will ensure that an unexpected - // credential type with potential for malicious intent is not loaded - // unintentionally. You might still have to do validation for certain - // credential types. Please follow the recommendation for that method. For - // example, if you want to load only service accounts, you can use - // - // creds, err := idtoken.NewCredentialsFromJSON(ctx, credentials.ServiceAccount, json, opts) - // - // If you are loading your credential configuration from an untrusted source - // and have not mitigated the risks (e.g. by validating the configuration - // yourself), make these changes as soon as possible to prevent security - // risks to your environment. - // - // Regardless of the method used, it is always your responsibility to - // validate configurations received from external sources. - // - // For more details see: - // https://cloud.google.com/docs/authentication/external/externally-sourced-credentials - CredentialsJSON []byte - // Client configures the underlying client used to make network requests - // when fetching tokens. If provided this should be a fully-authenticated - // client. Optional. - Client *http.Client - // UniverseDomain is the default service domain for a given Cloud universe. - // The default value is "googleapis.com". This is the universe domain - // configured for the client, which will be compared to the universe domain - // that is separately configured for the credentials. Optional. - UniverseDomain string - // Logger is used for debug logging. If provided, logging will be enabled - // at the loggers configured level. By default logging is disabled unless - // enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default - // logger will be used. Optional. - Logger *slog.Logger -} - -func (o *Options) client() *http.Client { - if o == nil || o.Client == nil { - return internal.DefaultClient() - } - return o.Client -} - -func (o *Options) validate() error { - if o == nil { - return errMissingOpts - } - if o.Audience == "" { - return errMissingAudience - } - if o.CredentialsFile != "" && len(o.CredentialsJSON) > 0 { - return errBothFileAndJSON - } - return nil -} - -// NewCredentials creates a [cloud.google.com/go/auth.Credentials] that returns -// ID tokens configured by the opts provided. The parameter opts.Audience must -// not be empty. If both opts.CredentialsFile and opts.CredentialsJSON are -// empty, an attempt will be made to detect credentials from the environment -// (see [cloud.google.com/go/auth/credentials.DetectDefault]). Only service -// account, impersonated service account, external account and Compute -// credentials are supported. -func NewCredentials(opts *Options) (*auth.Credentials, error) { - if err := opts.validate(); err != nil { - return nil, err - } - b := opts.jsonBytes() - if b == nil && metadata.OnGCE() { - return computeCredentials(opts) - } - creds, err := credentials.DetectDefault(&credentials.DetectOptions{ - Scopes: defaultScopes, - CredentialsJSON: b, - Client: opts.client(), - UseSelfSignedJWT: true, - }) - if err != nil { - return nil, err - } - return credsFromDefault(creds, opts) -} - -func (o *Options) jsonBytes() []byte { - if len(o.CredentialsJSON) > 0 { - return o.CredentialsJSON - } - var fnOverride string - if o != nil { - fnOverride = o.CredentialsFile - } - filename := credsfile.GetFileNameFromEnv(fnOverride) - if filename != "" { - b, _ := os.ReadFile(filename) - return b - } - return nil -} - -// NewCredentialsFromJSON creates a [cloud.google.com/go/auth.Credentials] that -// returns ID tokens from the provided JSON bytes. The credType argument -// specifies the expected credential type. If the JSON does not match the -// expected type, an error is returned. -// -// This method is safe to use with untrusted credential configurations if the -// expected credType is NOT [credentials.ExternalAccount] or -// [credentials.ImpersonatedServiceAccount]. -// -// **IMPORTANT**: If you use [credentials.ExternalAccount] or -// [credentials.ImpersonatedServiceAccount], you must validate the credential -// configuration before providing it to this method. Providing an unvalidated -// credential configuration to Google APIs can compromise the security of your -// systems and data. For more information, refer to [Validate credential -// configurations from external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials). -func NewCredentialsFromJSON(credType credentials.CredType, b []byte, opts *Options) (*auth.Credentials, error) { - if err := opts.validate(); err != nil { - return nil, err - } - // Use credentials.NewCredentialsFromJSON to validate type and create base credentials. - creds, err := credentials.NewCredentialsFromJSON(credType, b, &credentials.DetectOptions{ - Client: opts.Client, - Logger: opts.Logger, - UniverseDomain: opts.UniverseDomain, - }) - if err != nil { - return nil, err - } - return credsFromDefault(creds, opts) -} - -// NewCredentialsFromFile creates a [cloud.google.com/go/auth.Credentials] that -// returns ID tokens from the provided file. The credType argument specifies the -// expected credential type. If the file content does not match the expected -// type, an error is returned. -// -// This method is safe to use with untrusted credential configurations if the -// expected credType is NOT [credentials.ExternalAccount] or -// [credentials.ImpersonatedServiceAccount]. -// -// **IMPORTANT**: If you use [credentials.ExternalAccount] or -// [credentials.ImpersonatedServiceAccount], you must validate the credential -// configuration before providing it to this method. Providing an unvalidated -// credential configuration to Google APIs can compromise the security of your -// systems and data. For more information, refer to [Validate credential -// configurations from external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials). -func NewCredentialsFromFile(credType credentials.CredType, filename string, opts *Options) (*auth.Credentials, error) { - b, err := os.ReadFile(filename) - if err != nil { - return nil, err - } - return NewCredentialsFromJSON(credType, b, opts) -} diff --git a/vendor/cloud.google.com/go/auth/credentials/idtoken/validate.go b/vendor/cloud.google.com/go/auth/credentials/idtoken/validate.go deleted file mode 100644 index 4dd0dd5360..0000000000 --- a/vendor/cloud.google.com/go/auth/credentials/idtoken/validate.go +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package idtoken - -import ( - "context" - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rsa" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "fmt" - "log/slog" - "math/big" - "net/http" - "strings" - "time" - - "cloud.google.com/go/auth/internal" - "cloud.google.com/go/auth/internal/jwt" - "github.com/googleapis/gax-go/v2/internallog" -) - -const ( - es256KeySize int = 32 - // googleIAPCertsURL is used for ES256 Certs. - googleIAPCertsURL string = "https://www.gstatic.com/iap/verify/public_key-jwk" - // googleSACertsURL is used for RS256 Certs. - googleSACertsURL string = "https://www.googleapis.com/oauth2/v3/certs" -) - -var ( - defaultValidator = &Validator{client: newCachingClient(internal.DefaultClient(), internallog.New(nil))} - // now aliases time.Now for testing. - now = time.Now -) - -// certResponse represents a list jwks. It is the format returned from known -// Google cert endpoints. -type certResponse struct { - Keys []jwk `json:"keys"` -} - -// jwk is a simplified representation of a standard jwk. It only includes the -// fields used by Google's cert endpoints. -type jwk struct { - Alg string `json:"alg"` - Crv string `json:"crv"` - Kid string `json:"kid"` - Kty string `json:"kty"` - Use string `json:"use"` - E string `json:"e"` - N string `json:"n"` - X string `json:"x"` - Y string `json:"y"` -} - -// Validator provides a way to validate Google ID Tokens -type Validator struct { - client *cachingClient - rs256URL string - es256URL string -} - -// Payload represents a decoded payload of an ID token. -type Payload struct { - Issuer string `json:"iss"` - Audience string `json:"aud"` - Expires int64 `json:"exp"` - IssuedAt int64 `json:"iat"` - Subject string `json:"sub,omitempty"` - Claims map[string]interface{} `json:"-"` -} - -// ValidatorOptions provides a way to configure a [Validator]. -type ValidatorOptions struct { - // Client used to make requests to the certs URL. Optional. - Client *http.Client - // Custom certs URL for RS256 JWK to be used. If not provided, the default - // Google oauth2 endpoint will be used. Optional. - RS256CertsURL string - // Custom certs URL for ES256 JWK to be used. If not provided, the default - // Google IAP endpoint will be used. Optional. - ES256CertsURL string - // Logger is used for debug logging. If provided, logging will be enabled - // at the loggers configured level. By default logging is disabled unless - // enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default - // Logger will be used. Optional. - Logger *slog.Logger -} - -// NewValidator creates a Validator that uses the options provided to configure -// a the internal http.Client that will be used to make requests to fetch JWKs. -func NewValidator(opts *ValidatorOptions) (*Validator, error) { - if opts == nil { - opts = &ValidatorOptions{} - } - client := opts.Client - if client == nil { - client = internal.DefaultClient() - } - rs256URL := opts.RS256CertsURL - es256URL := opts.ES256CertsURL - logger := internallog.New(opts.Logger) - return &Validator{client: newCachingClient(client, logger), rs256URL: rs256URL, es256URL: es256URL}, nil -} - -// Validate is used to validate the provided idToken with a known Google cert -// URL. If audience is not empty the audience claim of the Token is validated. -// Upon successful validation a parsed token Payload is returned allowing the -// caller to validate any additional claims. -func (v *Validator) Validate(ctx context.Context, idToken string, audience string) (*Payload, error) { - return v.validate(ctx, idToken, audience) -} - -// Validate is used to validate the provided idToken with a known Google cert -// URL. If audience is not empty the audience claim of the Token is validated. -// Upon successful validation a parsed token Payload is returned allowing the -// caller to validate any additional claims. -func Validate(ctx context.Context, idToken string, audience string) (*Payload, error) { - return defaultValidator.validate(ctx, idToken, audience) -} - -// ParsePayload parses the given token and returns its payload. -// -// Warning: This function does not validate the token prior to parsing it. -// -// ParsePayload is primarily meant to be used to inspect a token's payload. This is -// useful when validation fails and the payload needs to be inspected. -// -// Note: A successful Validate() invocation with the same token will return an -// identical payload. -func ParsePayload(idToken string) (*Payload, error) { - _, payload, _, err := parseToken(idToken) - if err != nil { - return nil, err - } - return payload, nil -} - -func (v *Validator) validate(ctx context.Context, idToken string, audience string) (*Payload, error) { - header, payload, sig, err := parseToken(idToken) - if err != nil { - return nil, err - } - - if audience != "" && payload.Audience != audience { - return nil, fmt.Errorf("idtoken: audience provided does not match aud claim in the JWT") - } - - if now().Unix() > payload.Expires { - return nil, fmt.Errorf("idtoken: token expired: now=%v, expires=%v", now().Unix(), payload.Expires) - } - hashedContent := hashHeaderPayload(idToken) - switch header.Algorithm { - case jwt.HeaderAlgRSA256: - if err := v.validateRS256(ctx, header.KeyID, hashedContent, sig); err != nil { - return nil, err - } - case jwt.HeaderAlgES256: - if err := v.validateES256(ctx, header.KeyID, hashedContent, sig); err != nil { - return nil, err - } - default: - return nil, fmt.Errorf("idtoken: expected JWT signed with RS256 or ES256 but found %q", header.Algorithm) - } - - return payload, nil -} - -func (v *Validator) validateRS256(ctx context.Context, keyID string, hashedContent []byte, sig []byte) error { - certResp, err := v.client.getCert(ctx, v.rs256CertsURL()) - if err != nil { - return err - } - j, err := findMatchingKey(certResp, keyID) - if err != nil { - return err - } - dn, err := decode(j.N) - if err != nil { - return err - } - de, err := decode(j.E) - if err != nil { - return err - } - - pk := &rsa.PublicKey{ - N: new(big.Int).SetBytes(dn), - E: int(new(big.Int).SetBytes(de).Int64()), - } - return rsa.VerifyPKCS1v15(pk, crypto.SHA256, hashedContent, sig) -} - -func (v *Validator) rs256CertsURL() string { - if v.rs256URL == "" { - return googleSACertsURL - } - return v.rs256URL -} - -func (v *Validator) validateES256(ctx context.Context, keyID string, hashedContent []byte, sig []byte) error { - certResp, err := v.client.getCert(ctx, v.es256CertsURL()) - if err != nil { - return err - } - j, err := findMatchingKey(certResp, keyID) - if err != nil { - return err - } - dx, err := decode(j.X) - if err != nil { - return err - } - dy, err := decode(j.Y) - if err != nil { - return err - } - - pk := &ecdsa.PublicKey{ - Curve: elliptic.P256(), - X: new(big.Int).SetBytes(dx), - Y: new(big.Int).SetBytes(dy), - } - r := big.NewInt(0).SetBytes(sig[:es256KeySize]) - s := big.NewInt(0).SetBytes(sig[es256KeySize:]) - if valid := ecdsa.Verify(pk, hashedContent, r, s); !valid { - return fmt.Errorf("idtoken: ES256 signature not valid") - } - return nil -} - -func (v *Validator) es256CertsURL() string { - if v.es256URL == "" { - return googleIAPCertsURL - } - return v.es256URL -} - -func findMatchingKey(response *certResponse, keyID string) (*jwk, error) { - if response == nil { - return nil, fmt.Errorf("idtoken: cert response is nil") - } - for _, v := range response.Keys { - if v.Kid == keyID { - return &v, nil - } - } - return nil, fmt.Errorf("idtoken: could not find matching cert keyId for the token provided") -} - -func parseToken(idToken string) (*jwt.Header, *Payload, []byte, error) { - segments := strings.Split(idToken, ".") - if len(segments) != 3 { - return nil, nil, nil, fmt.Errorf("idtoken: invalid token, token must have three segments; found %d", len(segments)) - } - // Header - dh, err := decode(segments[0]) - if err != nil { - return nil, nil, nil, fmt.Errorf("idtoken: unable to decode JWT header: %v", err) - } - var header *jwt.Header - err = json.Unmarshal(dh, &header) - if err != nil { - return nil, nil, nil, fmt.Errorf("idtoken: unable to unmarshal JWT header: %v", err) - } - - // Payload - dp, err := decode(segments[1]) - if err != nil { - return nil, nil, nil, fmt.Errorf("idtoken: unable to decode JWT claims: %v", err) - } - var payload *Payload - if err := json.Unmarshal(dp, &payload); err != nil { - return nil, nil, nil, fmt.Errorf("idtoken: unable to unmarshal JWT payload: %v", err) - } - if err := json.Unmarshal(dp, &payload.Claims); err != nil { - return nil, nil, nil, fmt.Errorf("idtoken: unable to unmarshal JWT payload claims: %v", err) - } - - // Signature - signature, err := decode(segments[2]) - if err != nil { - return nil, nil, nil, fmt.Errorf("idtoken: unable to decode JWT signature: %v", err) - } - return header, payload, signature, nil -} - -// hashHeaderPayload gets the SHA256 checksum for verification of the JWT. -func hashHeaderPayload(idtoken string) []byte { - // remove the sig from the token - content := idtoken[:strings.LastIndex(idtoken, ".")] - hashed := sha256.Sum256([]byte(content)) - return hashed[:] -} - -func decode(s string) ([]byte, error) { - return base64.RawURLEncoding.DecodeString(s) -} diff --git a/vendor/cloud.google.com/go/auth/credentials/impersonate/doc.go b/vendor/cloud.google.com/go/auth/credentials/impersonate/doc.go deleted file mode 100644 index 0dc45f13fb..0000000000 --- a/vendor/cloud.google.com/go/auth/credentials/impersonate/doc.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package impersonate is used to impersonate Google Credentials. If you need -// to impersonate some credentials to use with a client library see -// [NewCredentials]. If instead you would like to create an Open -// Connect ID token using impersonation see [NewIDTokenCredentials]. -// -// # Required IAM roles -// -// In order to impersonate a service account the base service account must have -// the Service Account Token Creator role, roles/iam.serviceAccountTokenCreator, -// on the service account being impersonated. See -// https://cloud.google.com/iam/docs/understanding-service-accounts. -// -// Optionally, delegates can be used during impersonation if the base service -// account lacks the token creator role on the target. When using delegates, -// each service account must be granted roles/iam.serviceAccountTokenCreator -// on the next service account in the delgation chain. -// -// For example, if a base service account of SA1 is trying to impersonate target -// service account SA2 while using delegate service accounts DSA1 and DSA2, -// the following must be true: -// -// 1. Base service account SA1 has roles/iam.serviceAccountTokenCreator on -// DSA1. -// 2. DSA1 has roles/iam.serviceAccountTokenCreator on DSA2. -// 3. DSA2 has roles/iam.serviceAccountTokenCreator on target SA2. -// -// If the base credential is an authorized user and not a service account, or if -// the option WithQuotaProject is set, the target service account must have a -// role that grants the serviceusage.services.use permission such as -// roles/serviceusage.serviceUsageConsumer. -package impersonate diff --git a/vendor/cloud.google.com/go/auth/credentials/impersonate/idtoken.go b/vendor/cloud.google.com/go/auth/credentials/impersonate/idtoken.go deleted file mode 100644 index 6c470822bd..0000000000 --- a/vendor/cloud.google.com/go/auth/credentials/impersonate/idtoken.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package impersonate - -import ( - "errors" - "log/slog" - "net/http" - - "cloud.google.com/go/auth" - "cloud.google.com/go/auth/credentials" - "cloud.google.com/go/auth/credentials/internal/impersonate" - "cloud.google.com/go/auth/httptransport" - "cloud.google.com/go/auth/internal" - "github.com/googleapis/gax-go/v2/internallog" -) - -// IDTokenOptions for generating an impersonated ID token. -type IDTokenOptions struct { - // Audience is the `aud` field for the token, such as an API endpoint the - // token will grant access to. Required. - Audience string - // TargetPrincipal is the email address of the service account to - // impersonate. Required. - TargetPrincipal string - // IncludeEmail includes the target service account's email in the token. - // The resulting token will include both an `email` and `email_verified` - // claim. Optional. - IncludeEmail bool - // Delegates are the ordered service account email addresses in a delegation - // chain. Each service account must be granted - // roles/iam.serviceAccountTokenCreator on the next service account in the - // chain. Optional. - Delegates []string - - // Credentials used in generating the impersonated ID token. If empty, an - // attempt will be made to detect credentials from the environment (see - // [cloud.google.com/go/auth/credentials.DetectDefault]). Optional. - Credentials *auth.Credentials - // Client configures the underlying client used to make network requests - // when fetching tokens. If provided this should be a fully-authenticated - // client. Optional. - Client *http.Client - // UniverseDomain is the default service domain for a given Cloud universe. - // The default value is "googleapis.com". This is the universe domain - // configured for the client, which will be compared to the universe domain - // that is separately configured for the credentials. Optional. - UniverseDomain string - // Logger is used for debug logging. If provided, logging will be enabled - // at the loggers configured level. By default logging is disabled unless - // enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default - // logger will be used. Optional. - Logger *slog.Logger -} - -func (o *IDTokenOptions) validate() error { - if o == nil { - return errors.New("impersonate: options must be provided") - } - if o.Audience == "" { - return errors.New("impersonate: audience must be provided") - } - if o.TargetPrincipal == "" { - return errors.New("impersonate: target service account must be provided") - } - return nil -} - -var ( - defaultScope = "https://www.googleapis.com/auth/cloud-platform" -) - -// NewIDTokenCredentials creates an impersonated -// [cloud.google.com/go/auth/Credentials] that returns ID tokens configured -// with the provided config and using credentials loaded from Application -// Default Credentials as the base credentials if not provided with the opts. -// The tokens produced are valid for one hour and are automatically refreshed. -func NewIDTokenCredentials(opts *IDTokenOptions) (*auth.Credentials, error) { - if err := opts.validate(); err != nil { - return nil, err - } - client := opts.Client - creds := opts.Credentials - logger := internallog.New(opts.Logger) - if client == nil { - var err error - if creds == nil { - creds, err = credentials.DetectDefault(&credentials.DetectOptions{ - Scopes: []string{defaultScope}, - UseSelfSignedJWT: true, - Logger: logger, - }) - if err != nil { - return nil, err - } - } - client, err = httptransport.NewClient(&httptransport.Options{ - Credentials: creds, - UniverseDomain: opts.UniverseDomain, - Logger: logger, - }) - if err != nil { - return nil, err - } - } - - universeDomainProvider := resolveUniverseDomainProvider(creds) - var delegates []string - for _, v := range opts.Delegates { - delegates = append(delegates, internal.FormatIAMServiceAccountResource(v)) - } - - iamOpts := impersonate.IDTokenIAMOptions{ - Client: client, - Logger: logger, - // Pass the credentials universe domain provider to configure the endpoint. - UniverseDomain: universeDomainProvider, - ServiceAccountEmail: opts.TargetPrincipal, - GenerateIDTokenRequest: impersonate.GenerateIDTokenRequest{ - Audience: opts.Audience, - IncludeEmail: opts.IncludeEmail, - Delegates: delegates, - }, - } - return auth.NewCredentials(&auth.CredentialsOptions{ - TokenProvider: auth.NewCachedTokenProvider(iamOpts, nil), - UniverseDomainProvider: universeDomainProvider, - }), nil -} diff --git a/vendor/cloud.google.com/go/auth/credentials/impersonate/impersonate.go b/vendor/cloud.google.com/go/auth/credentials/impersonate/impersonate.go deleted file mode 100644 index 7d8efd54ef..0000000000 --- a/vendor/cloud.google.com/go/auth/credentials/impersonate/impersonate.go +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package impersonate - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "log/slog" - "net/http" - "strings" - "time" - - "cloud.google.com/go/auth" - "cloud.google.com/go/auth/credentials" - "cloud.google.com/go/auth/httptransport" - "cloud.google.com/go/auth/internal" - "github.com/googleapis/gax-go/v2/internallog" -) - -var ( - universeDomainPlaceholder = "UNIVERSE_DOMAIN" - iamCredentialsUniverseDomainEndpoint = "https://iamcredentials.UNIVERSE_DOMAIN" - oauth2Endpoint = "https://oauth2.googleapis.com" - errMissingTargetPrincipal = errors.New("impersonate: target service account must be provided") - errMissingScopes = errors.New("impersonate: scopes must be provided") - errLifetimeOverMax = errors.New("impersonate: max lifetime is 12 hours") - errUniverseNotSupportedDomainWideDelegation = errors.New("impersonate: service account user is configured for the credential. " + - "Domain-wide delegation is not supported in universes other than googleapis.com") -) - -// TODO(codyoss): plumb through base for this and idtoken - -// NewCredentials returns an impersonated -// [cloud.google.com/go/auth/NewCredentials] configured with the provided options -// and using credentials loaded from Application Default Credentials as the base -// credentials if not provided with the opts. -func NewCredentials(opts *CredentialsOptions) (*auth.Credentials, error) { - if err := opts.validate(); err != nil { - return nil, err - } - - var isStaticToken bool - // Default to the longest acceptable value of one hour as the token will - // be refreshed automatically if not set. - lifetime := 1 * time.Hour - if opts.Lifetime != 0 { - lifetime = opts.Lifetime - // Don't auto-refresh token if a lifetime is configured. - isStaticToken = true - } - - client := opts.Client - creds := opts.Credentials - logger := internallog.New(opts.Logger) - if client == nil { - var err error - if creds == nil { - creds, err = credentials.DetectDefault(&credentials.DetectOptions{ - Scopes: []string{defaultScope}, - UseSelfSignedJWT: true, - Logger: logger, - }) - if err != nil { - return nil, err - } - } - - client, err = httptransport.NewClient(transportOpts(opts, creds, logger)) - if err != nil { - return nil, err - } - } - - universeDomainProvider := resolveUniverseDomainProvider(creds) - // If a subject is specified a domain-wide delegation auth-flow is initiated - // to impersonate as the provided subject (user). - if opts.Subject != "" { - tp, err := user(opts, client, lifetime, isStaticToken, universeDomainProvider) - if err != nil { - return nil, err - } - return auth.NewCredentials(&auth.CredentialsOptions{ - TokenProvider: tp, - UniverseDomainProvider: universeDomainProvider, - }), nil - } - - its := impersonatedTokenProvider{ - client: client, - targetPrincipal: opts.TargetPrincipal, - lifetime: fmt.Sprintf("%.fs", lifetime.Seconds()), - universeDomainProvider: universeDomainProvider, - logger: logger, - } - for _, v := range opts.Delegates { - its.delegates = append(its.delegates, internal.FormatIAMServiceAccountResource(v)) - } - its.scopes = make([]string, len(opts.Scopes)) - copy(its.scopes, opts.Scopes) - - var tpo *auth.CachedTokenProviderOptions - if isStaticToken { - tpo = &auth.CachedTokenProviderOptions{ - DisableAutoRefresh: true, - } - } - - return auth.NewCredentials(&auth.CredentialsOptions{ - TokenProvider: auth.NewCachedTokenProvider(its, tpo), - UniverseDomainProvider: universeDomainProvider, - }), nil -} - -// transportOpts returns options for httptransport.NewClient. If opts.UniverseDomain -// is provided, it will be used in the transport for a validation ensuring that it -// matches the universe domain in the base credentials. If opts.UniverseDomain -// is not provided, this validation will be skipped. -func transportOpts(opts *CredentialsOptions, creds *auth.Credentials, logger *slog.Logger) *httptransport.Options { - tOpts := &httptransport.Options{ - Credentials: creds, - Logger: logger, - } - if opts.UniverseDomain == "" { - tOpts.InternalOptions = &httptransport.InternalOptions{ - SkipUniverseDomainValidation: true, - } - } else { - tOpts.UniverseDomain = opts.UniverseDomain - } - return tOpts -} - -// resolveUniverseDomainProvider returns the default service domain for a given -// Cloud universe. This is the universe domain configured for the credentials, -// which will be used in endpoint(s), and compared to the universe domain that -// is separately configured for the client. -func resolveUniverseDomainProvider(creds *auth.Credentials) auth.CredentialsPropertyProvider { - if creds != nil { - return auth.CredentialsPropertyFunc(creds.UniverseDomain) - } - return internal.StaticCredentialsProperty(internal.DefaultUniverseDomain) -} - -// CredentialsOptions for generating an impersonated credential token. -type CredentialsOptions struct { - // TargetPrincipal is the email address of the service account to - // impersonate. Required. - TargetPrincipal string - // Scopes that the impersonated credential should have. Required. - Scopes []string - // Delegates are the service account email addresses in a delegation chain. - // Each service account must be granted roles/iam.serviceAccountTokenCreator - // on the next service account in the chain. Optional. - Delegates []string - // Lifetime is the amount of time until the impersonated token expires. If - // unset the token's lifetime will be one hour and be automatically - // refreshed. If set the token may have a max lifetime of one hour and will - // not be refreshed. Service accounts that have been added to an org policy - // with constraints/iam.allowServiceAccountCredentialLifetimeExtension may - // request a token lifetime of up to 12 hours. Optional. - Lifetime time.Duration - // Subject is the sub field of a JWT. This field should only be set if you - // wish to impersonate as a user. This feature is useful when using domain - // wide delegation. Optional. - Subject string - - // Credentials used in generating the impersonated token. If empty, an - // attempt will be made to detect credentials from the environment (see - // [cloud.google.com/go/auth/credentials.DetectDefault]). Optional. - Credentials *auth.Credentials - // Client configures the underlying client used to make network requests - // when fetching tokens. If provided this should be a fully-authenticated - // client. Optional. - Client *http.Client - // UniverseDomain is the default service domain for a given Cloud universe. - // This field has no default value, and only if provided will it be used to - // verify the universe domain from the credentials. Optional. - UniverseDomain string - // Logger is used for debug logging. If provided, logging will be enabled - // at the loggers configured level. By default logging is disabled unless - // enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default - // logger will be used. Optional. - Logger *slog.Logger -} - -func (o *CredentialsOptions) validate() error { - if o == nil { - return errors.New("impersonate: options must be provided") - } - if o.TargetPrincipal == "" { - return errMissingTargetPrincipal - } - if len(o.Scopes) == 0 { - return errMissingScopes - } - if o.Lifetime.Hours() > 12 { - return errLifetimeOverMax - } - return nil -} - -type generateAccessTokenRequest struct { - Delegates []string `json:"delegates,omitempty"` - Lifetime string `json:"lifetime,omitempty"` - Scope []string `json:"scope,omitempty"` -} - -type generateAccessTokenResponse struct { - AccessToken string `json:"accessToken"` - ExpireTime string `json:"expireTime"` -} - -type impersonatedTokenProvider struct { - client *http.Client - // universeDomain is used for endpoint construction. - universeDomainProvider auth.CredentialsPropertyProvider - logger *slog.Logger - - targetPrincipal string - lifetime string - scopes []string - delegates []string -} - -// Token returns an impersonated Token. -func (i impersonatedTokenProvider) Token(ctx context.Context) (*auth.Token, error) { - reqBody := generateAccessTokenRequest{ - Delegates: i.delegates, - Lifetime: i.lifetime, - Scope: i.scopes, - } - b, err := json.Marshal(reqBody) - if err != nil { - return nil, fmt.Errorf("impersonate: unable to marshal request: %w", err) - } - universeDomain, err := i.universeDomainProvider.GetProperty(ctx) - if err != nil { - return nil, err - } - endpoint := strings.Replace(iamCredentialsUniverseDomainEndpoint, universeDomainPlaceholder, universeDomain, 1) - url := fmt.Sprintf("%s/v1/%s:generateAccessToken", endpoint, internal.FormatIAMServiceAccountResource(i.targetPrincipal)) - req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(b)) - if err != nil { - return nil, fmt.Errorf("impersonate: unable to create request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - i.logger.DebugContext(ctx, "impersonated token request", "request", internallog.HTTPRequest(req, b)) - resp, body, err := internal.DoRequest(i.client, req) - if err != nil { - return nil, fmt.Errorf("impersonate: unable to generate access token: %w", err) - } - i.logger.DebugContext(ctx, "impersonated token response", "response", internallog.HTTPResponse(resp, body)) - if c := resp.StatusCode; c < 200 || c > 299 { - return nil, fmt.Errorf("impersonate: status code %d: %s", c, body) - } - - var accessTokenResp generateAccessTokenResponse - if err := json.Unmarshal(body, &accessTokenResp); err != nil { - return nil, fmt.Errorf("impersonate: unable to parse response: %w", err) - } - expiry, err := time.Parse(time.RFC3339, accessTokenResp.ExpireTime) - if err != nil { - return nil, fmt.Errorf("impersonate: unable to parse expiry: %w", err) - } - return &auth.Token{ - Value: accessTokenResp.AccessToken, - Expiry: expiry, - }, nil -} diff --git a/vendor/cloud.google.com/go/auth/credentials/impersonate/user.go b/vendor/cloud.google.com/go/auth/credentials/impersonate/user.go deleted file mode 100644 index 432427fa88..0000000000 --- a/vendor/cloud.google.com/go/auth/credentials/impersonate/user.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package impersonate - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "log/slog" - "net/http" - "net/url" - "strings" - "time" - - "cloud.google.com/go/auth" - "cloud.google.com/go/auth/internal" - "github.com/googleapis/gax-go/v2/internallog" -) - -var ( - iamCredentialsEndpoint = "https://iamcredentials.googleapis.com" -) - -// user provides an auth flow for domain-wide delegation, setting -// CredentialsConfig.Subject to be the impersonated user. -func user(opts *CredentialsOptions, client *http.Client, lifetime time.Duration, isStaticToken bool, universeDomainProvider auth.CredentialsPropertyProvider) (auth.TokenProvider, error) { - if opts.Subject == "" { - return nil, errors.New("CredentialsConfig.Subject must not be empty") - } - u := userTokenProvider{ - client: client, - targetPrincipal: opts.TargetPrincipal, - subject: opts.Subject, - lifetime: lifetime, - universeDomainProvider: universeDomainProvider, - logger: internallog.New(opts.Logger), - } - u.delegates = make([]string, len(opts.Delegates)) - for i, v := range opts.Delegates { - u.delegates[i] = internal.FormatIAMServiceAccountResource(v) - } - u.scopes = make([]string, len(opts.Scopes)) - copy(u.scopes, opts.Scopes) - var tpo *auth.CachedTokenProviderOptions - if isStaticToken { - tpo = &auth.CachedTokenProviderOptions{ - DisableAutoRefresh: true, - } - } - return auth.NewCachedTokenProvider(u, tpo), nil -} - -type claimSet struct { - Iss string `json:"iss"` - Scope string `json:"scope,omitempty"` - Sub string `json:"sub,omitempty"` - Aud string `json:"aud"` - Iat int64 `json:"iat"` - Exp int64 `json:"exp"` -} - -type signJWTRequest struct { - Payload string `json:"payload"` - Delegates []string `json:"delegates,omitempty"` -} - -type signJWTResponse struct { - // KeyID is the key used to sign the JWT. - KeyID string `json:"keyId"` - // SignedJwt contains the automatically generated header; the - // client-supplied payload; and the signature, which is generated using - // the key referenced by the `kid` field in the header. - SignedJWT string `json:"signedJwt"` -} - -type exchangeTokenResponse struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - ExpiresIn int64 `json:"expires_in"` -} - -type userTokenProvider struct { - client *http.Client - logger *slog.Logger - - targetPrincipal string - subject string - scopes []string - lifetime time.Duration - delegates []string - universeDomainProvider auth.CredentialsPropertyProvider -} - -func (u userTokenProvider) Token(ctx context.Context) (*auth.Token, error) { - // Because a subject is specified a domain-wide delegation auth-flow is initiated - // to impersonate as the provided subject (user). - // Return error if users try to use domain-wide delegation in a non-GDU universe. - ud, err := u.universeDomainProvider.GetProperty(ctx) - if err != nil { - return nil, err - } - if ud != internal.DefaultUniverseDomain { - return nil, errUniverseNotSupportedDomainWideDelegation - } - signedJWT, err := u.signJWT(ctx) - if err != nil { - return nil, err - } - return u.exchangeToken(ctx, signedJWT) -} - -func (u userTokenProvider) signJWT(ctx context.Context) (string, error) { - now := time.Now() - exp := now.Add(u.lifetime) - claims := claimSet{ - Iss: u.targetPrincipal, - Scope: strings.Join(u.scopes, " "), - Sub: u.subject, - Aud: fmt.Sprintf("%s/token", oauth2Endpoint), - Iat: now.Unix(), - Exp: exp.Unix(), - } - payloadBytes, err := json.Marshal(claims) - if err != nil { - return "", fmt.Errorf("impersonate: unable to marshal claims: %w", err) - } - signJWTReq := signJWTRequest{ - Payload: string(payloadBytes), - Delegates: u.delegates, - } - - bodyBytes, err := json.Marshal(signJWTReq) - if err != nil { - return "", fmt.Errorf("impersonate: unable to marshal request: %w", err) - } - reqURL := fmt.Sprintf("%s/v1/%s:signJwt", iamCredentialsEndpoint, internal.FormatIAMServiceAccountResource(u.targetPrincipal)) - req, err := http.NewRequestWithContext(ctx, "POST", reqURL, bytes.NewReader(bodyBytes)) - if err != nil { - return "", fmt.Errorf("impersonate: unable to create request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - u.logger.DebugContext(ctx, "impersonated user sign JWT request", "request", internallog.HTTPRequest(req, bodyBytes)) - resp, body, err := internal.DoRequest(u.client, req) - if err != nil { - return "", fmt.Errorf("impersonate: unable to sign JWT: %w", err) - } - u.logger.DebugContext(ctx, "impersonated user sign JWT response", "response", internallog.HTTPResponse(resp, body)) - if c := resp.StatusCode; c < 200 || c > 299 { - return "", fmt.Errorf("impersonate: status code %d: %s", c, body) - } - - var signJWTResp signJWTResponse - if err := json.Unmarshal(body, &signJWTResp); err != nil { - return "", fmt.Errorf("impersonate: unable to parse response: %w", err) - } - return signJWTResp.SignedJWT, nil -} - -func (u userTokenProvider) exchangeToken(ctx context.Context, signedJWT string) (*auth.Token, error) { - v := url.Values{} - v.Set("grant_type", "assertion") - v.Set("assertion_type", "http://oauth.net/grant_type/jwt/1.0/bearer") - v.Set("assertion", signedJWT) - req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/token", oauth2Endpoint), strings.NewReader(v.Encode())) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - u.logger.DebugContext(ctx, "impersonated user token exchange request", "request", internallog.HTTPRequest(req, []byte(v.Encode()))) - resp, body, err := internal.DoRequest(u.client, req) - if err != nil { - return nil, fmt.Errorf("impersonate: unable to exchange token: %w", err) - } - u.logger.DebugContext(ctx, "impersonated user token exchange response", "response", internallog.HTTPResponse(resp, body)) - if c := resp.StatusCode; c < 200 || c > 299 { - return nil, fmt.Errorf("impersonate: status code %d: %s", c, body) - } - - var tokenResp exchangeTokenResponse - if err := json.Unmarshal(body, &tokenResp); err != nil { - return nil, fmt.Errorf("impersonate: unable to parse response: %w", err) - } - - return &auth.Token{ - Value: tokenResp.AccessToken, - Type: tokenResp.TokenType, - Expiry: time.Now().Add(time.Second * time.Duration(tokenResp.ExpiresIn)), - }, nil -} diff --git a/vendor/google.golang.org/grpc/CONTRIBUTING.md b/vendor/google.golang.org/grpc/CONTRIBUTING.md index 2079de7b0e..bd80b1f92c 100644 --- a/vendor/google.golang.org/grpc/CONTRIBUTING.md +++ b/vendor/google.golang.org/grpc/CONTRIBUTING.md @@ -23,6 +23,29 @@ wanted"](https://github.com/grpc/grpc-go/issues?q=sort%3Aupdated-desc%20is%3Aiss are especially nice for first-time contributors, as they should be well-defined problems that already have agreed-upon solutions. +## Generative AI Policy + +AI tools have the ability to produce more code than is possible for the gRPC +team to read, understand, review, and accept into the repository. For this +reason, we request that all contributions adhere to the following rules: + +1. **No AI-Generated Interactions:** All communication in the repo must be + authored by a human. _Exception: AIs may be used for directed writing + assistance or translation._ Absolutely no automated agents are allowed to + directly publish to GitHub. + +2. **Author Ownership and Accountability:** Code contributions are expected to + be fully owned and understood by the human contributor. If the code was + produced by generative AI, the author is expected to have reviewed and + understood it in its entirety before submitting it for review. This includes + all content: production code, tests, examples, tools, etc. + +In addition to the above requirements, any AI-assisted contributions must also +comply with the [Linux Foundation Generative AI +Policy](https://www.linuxfoundation.org/legal/generative-ai). This includes +confirming that all contributions are legally allowed to be contributed to the +gRPC project under the applicable license terms. + ## Code Style We follow [Google's published Go style diff --git a/vendor/google.golang.org/grpc/balancer/grpclb/grpc_lb_v1/load_balancer.pb.go b/vendor/google.golang.org/grpc/balancer/grpclb/grpc_lb_v1/load_balancer.pb.go index 0a0ac51f2e..2004b12777 100644 --- a/vendor/google.golang.org/grpc/balancer/grpclb/grpc_lb_v1/load_balancer.pb.go +++ b/vendor/google.golang.org/grpc/balancer/grpclb/grpc_lb_v1/load_balancer.pb.go @@ -19,7 +19,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v5.27.1 // source: grpc/lb/v1/load_balancer.proto diff --git a/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go b/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go index 6c249fdaf2..d22d1c3f10 100644 --- a/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go +++ b/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go @@ -18,7 +18,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v5.27.1 // source: grpc/binlog/v1/binarylog.proto diff --git a/vendor/google.golang.org/grpc/call.go b/vendor/google.golang.org/grpc/call.go index 2079bf4ed7..788c89c16f 100644 --- a/vendor/google.golang.org/grpc/call.go +++ b/vendor/google.golang.org/grpc/call.go @@ -20,10 +20,6 @@ package grpc import ( "context" - "io" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" ) // Invoke sends the RPC request on the wire and returns after response is @@ -71,27 +67,8 @@ func invoke(ctx context.Context, method string, req, reply any, cc *ClientConn, if err != nil { return err } - // In case of nil or io.EOF error, call RecvMsg. - if err := cs.SendMsg(req); err != nil && err != io.EOF { + if err := cs.SendMsg(req); err != nil { return err } - // CloseSend is needed because in some scenarios (e.g., xDS), the same - // interceptors are used to process both unary and streaming RPCs. Calling - // CloseSend signals to those interceptors that no more messages are on the - // way. - if err := cs.CloseSend(); err != nil && err != io.EOF { - return err - } - if err := cs.RecvMsg(reply); err != nil { - return err - } - // Call RecvMsg again to get the trailers. - err = cs.RecvMsg(reply) - if err == io.EOF { - return nil - } - if err == nil { - return status.Error(codes.Internal, "cardinality violation: expected for non server-streaming RPCs, but received another message") - } - return err + return cs.RecvMsg(reply) } diff --git a/vendor/google.golang.org/grpc/clientconn.go b/vendor/google.golang.org/grpc/clientconn.go index f5ce071970..7a25d251e7 100644 --- a/vendor/google.golang.org/grpc/clientconn.go +++ b/vendor/google.golang.org/grpc/clientconn.go @@ -553,7 +553,6 @@ func chainStreamClientInterceptors(cc *ClientConn) { if cc.dopts.streamInt != nil { interceptors = append([]StreamClientInterceptor{cc.dopts.streamInt}, interceptors...) } - interceptors = append(interceptors, defaultStreamInterceptor) var chainedInt StreamClientInterceptor if len(interceptors) == 0 { chainedInt = nil @@ -1212,6 +1211,12 @@ func (cc *ClientConn) Close() error { cc.mu.Unlock() cc.resolverWrapper.close() + // Swap in a default ConfigSelector so the resolver-provided one (which may + // transitively retain resolver-owned state such as parsed service configs + // and cluster maps) is released once in-flight RPCs finish selecting on + // it. Done after the resolver has closed so no further UpdateState calls + // race with this swap. + cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{nil}) // The order of closing matters here since the balancer wrapper assumes the // picker is closed before it is closed. cc.pickerWrapper.close() diff --git a/vendor/google.golang.org/grpc/credentials/alts/alts.go b/vendor/google.golang.org/grpc/credentials/alts/alts.go index b8afa917ce..9fb7170a86 100644 --- a/vendor/google.golang.org/grpc/credentials/alts/alts.go +++ b/vendor/google.golang.org/grpc/credentials/alts/alts.go @@ -266,10 +266,11 @@ func (g *altsTC) Clone() credentials.TransportCredentials { copy(accounts, g.accounts) } return &altsTC{ - info: &info, - side: g.side, - hsAddress: g.hsAddress, - accounts: accounts, + info: &info, + side: g.side, + hsAddress: g.hsAddress, + accounts: accounts, + boundAccessToken: g.boundAccessToken, } } diff --git a/vendor/google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp/altscontext.pb.go b/vendor/google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp/altscontext.pb.go index a9b926cc37..bf31168507 100644 --- a/vendor/google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp/altscontext.pb.go +++ b/vendor/google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp/altscontext.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v5.27.1 // source: grpc/gcp/altscontext.proto diff --git a/vendor/google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp/handshaker.pb.go b/vendor/google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp/handshaker.pb.go index f818df8d5d..0350eb31d2 100644 --- a/vendor/google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp/handshaker.pb.go +++ b/vendor/google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp/handshaker.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v5.27.1 // source: grpc/gcp/handshaker.proto diff --git a/vendor/google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp/transport_security_common.pb.go b/vendor/google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp/transport_security_common.pb.go index 0e9f95a5bd..b3d29b1b61 100644 --- a/vendor/google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp/transport_security_common.pb.go +++ b/vendor/google.golang.org/grpc/credentials/alts/internal/proto/grpc_gcp/transport_security_common.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v5.27.1 // source: grpc/gcp/transport_security_common.proto diff --git a/vendor/google.golang.org/grpc/credentials/google/gcp_service_account_identity_credentials.go b/vendor/google.golang.org/grpc/credentials/google/gcp_service_account_identity_credentials.go index 160ec81dc3..8ca53eb181 100644 --- a/vendor/google.golang.org/grpc/credentials/google/gcp_service_account_identity_credentials.go +++ b/vendor/google.golang.org/grpc/credentials/google/gcp_service_account_identity_credentials.go @@ -20,19 +20,14 @@ package google import ( "context" - "errors" "fmt" "sync" "time" - "cloud.google.com/go/auth" - "cloud.google.com/go/auth/credentials/idtoken" - "cloud.google.com/go/compute/metadata" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/google/internal" "google.golang.org/grpc/internal/backoff" - "google.golang.org/grpc/internal/transport" "google.golang.org/grpc/status" ) @@ -53,14 +48,14 @@ const ( type gcpServiceAccountIdentityCallCreds struct { // The following fields are initialized at creation time and are read-only // after that. - ctx context.Context - audience string - creds *auth.Credentials - backoff backoff.Strategy + ctx context.Context + audience string + backoff backoff.Strategy + fetchIDToken func(context.Context, string) (string, time.Time, error) // The following fields are protected by mu. mu sync.Mutex - token *auth.Token + token string // cached raw JWT token value tokenExpiry time.Time // timestamp after which the cached token is considered invalid preemptiveTokenRefresh time.Time // timestamp after which background preemptive refresh is triggered fetching chan struct{} // used to ensure a single in-progress background token fetch @@ -71,8 +66,8 @@ type gcpServiceAccountIdentityCallCreds struct { func init() { internal.BackoffStrategy = backoff.DefaultExponential - internal.NewIDTokenCredentials = func(opts *idtoken.Options) (*auth.Credentials, error) { - return idtoken.NewCredentials(opts) + internal.IDTokenFetcher = func() func(context.Context, string) (string, time.Time, error) { + return newIDTokenFetcher().fetchIDToken } } @@ -103,16 +98,11 @@ func NewServiceAccountIdentityCredentials(ctx context.Context, audience string) return nil, fmt.Errorf("credentials: audience cannot be empty") } - creds, err := internal.NewIDTokenCredentials(&idtoken.Options{Audience: audience}) - if err != nil { - return nil, fmt.Errorf("credentials: failed to create ID token credentials: %v", err) - } - return &gcpServiceAccountIdentityCallCreds{ - ctx: ctx, - audience: audience, - creds: creds, - backoff: internal.BackoffStrategy, + ctx: ctx, + audience: audience, + backoff: internal.BackoffStrategy, + fetchIDToken: internal.IDTokenFetcher(), }, nil } @@ -183,13 +173,13 @@ func (c *gcpServiceAccountIdentityCallCreds) cachedRequestMetadata(attemptPreemp // // It must be called with mu locked. func (c *gcpServiceAccountIdentityCallCreds) cachedRequestMetadataLocked(attemptPreemptiveRefresh bool) (map[string]string, error) { - if c.token != nil && c.isTokenValidLocked() { + if c.token != "" && c.isTokenValidLocked() { if attemptPreemptiveRefresh && c.isTokenStaleLocked() && c.fetching == nil { c.fetching = make(chan struct{}) go c.startFetch() } - return map[string]string{"authorization": "Bearer " + c.token.Value}, nil + return map[string]string{"authorization": "Bearer " + c.token}, nil } if c.lastErr != nil && time.Now().Before(c.nextRetryTime) { @@ -222,46 +212,27 @@ func (c *gcpServiceAccountIdentityCallCreds) isTokenValidLocked() bool { func (c *gcpServiceAccountIdentityCallCreds) startFetch() { ctx, cancel := context.WithTimeout(c.ctx, metadataTimeout) defer cancel() - token, err := c.creds.TokenProvider.Token(ctx) + token, exp, err := c.fetchIDToken(ctx, c.audience) c.mu.Lock() defer c.mu.Unlock() close(c.fetching) c.fetching = nil - c.updateStateLocked(token, err) + c.updateStateLocked(token, exp, err) } // updateStateLocked updates the credentials local token cache and // backoff state based on the outcome of a background fetch attempt. // // If the fetch succeeded, the cached token is updated, and the backoff timers -// and error are reset. -// -// If the fetch failed, backoff attempts are calculated and the error is mapped -// to a gRPC status. -// - If the HTTP request fails with a status that maps to gRPC UNAVAILABLE -// according to HTTP to gRPC status code mappings, it returns UNAVAILABLE. -// - All other HTTP error status codes map to UNAUTHENTICATED. -// - Non-HTTP request failures are mapped to UNAVAILABLE. +// and error are reset. If the fetch failed, the error is cached and the next +// retry time is calculated using the backoff strategy. // // It must be called with mu locked. -func (c *gcpServiceAccountIdentityCallCreds) updateStateLocked(token *auth.Token, err error) { +func (c *gcpServiceAccountIdentityCallCreds) updateStateLocked(token string, exp time.Time, err error) { if err != nil { - var mappedErr error - var metadataErr *metadata.Error - if errors.As(err, &metadataErr) { - switch transport.HTTPStatusConvTab[metadataErr.Code] { - case codes.Unavailable: - mappedErr = status.Errorf(codes.Unavailable, "credentials: failed to fetch token from metadata server: %v", err) - default: - mappedErr = status.Errorf(codes.Unauthenticated, "credentials: failed to fetch token from metadata server: %v", err) - } - } else { - mappedErr = status.Errorf(codes.Unavailable, "credentials: failed to fetch ID token: %v", err) - } - - c.lastErr = mappedErr + c.lastErr = err backoffDelay := c.backoff.Backoff(c.retryAttempt) c.retryAttempt++ c.nextRetryTime = time.Now().Add(backoffDelay) @@ -273,6 +244,6 @@ func (c *gcpServiceAccountIdentityCallCreds) updateStateLocked(token *auth.Token c.token = token // Per gRFC A83, the cached token is considered invalid 30 seconds before its // actual expiration time to accommodate for clock skew. - c.tokenExpiry = token.Expiry.Add(-30 * time.Second) + c.tokenExpiry = exp.Add(-30 * time.Second) c.preemptiveTokenRefresh = c.tokenExpiry.Add(-preemptiveRefresh) } diff --git a/vendor/google.golang.org/grpc/credentials/google/idtoken.go b/vendor/google.golang.org/grpc/credentials/google/idtoken.go new file mode 100644 index 0000000000..62e357f482 --- /dev/null +++ b/vendor/google.golang.org/grpc/credentials/google/idtoken.go @@ -0,0 +1,178 @@ +/* + * + * Copyright 2026 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package google + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + "strings" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/internal/transport" + "google.golang.org/grpc/status" +) + +// The HTTP transport timeout values match newDefaultHTTPClient in the +// GCE metadata client package (cloud.google.com/go/compute/metadata): +// +// Proxy is nil to bypass environment proxies per gRFC A83, a 2s dial +// timeout fails fast if the local metadata server is unreachable, TCP +// keep-alive is set to 30s, and idle pooled connections close after 60s. +var defaultMetadataHTTPClient = &http.Client{ + Transport: &http.Transport{ + Proxy: nil, + DialContext: (&net.Dialer{ + Timeout: 2 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + IdleConnTimeout: 60 * time.Second, + }, +} + +// idTokenFetcher fetches ID tokens from the GCE Metadata Server. +type idTokenFetcher struct { + client *http.Client // unproxied HTTP client for metadata server communication + metadataHost string // hostname of the GCE metadata server +} + +// newIDTokenFetcher creates a new idTokenFetcher configured to send direct, +// unproxied HTTP requests to the GCE metadata server. +func newIDTokenFetcher() *idTokenFetcher { + host := os.Getenv("GCE_METADATA_HOST") + if host == "" { + host = "metadata.google.internal" + } + + return &idTokenFetcher{ + metadataHost: host, + client: defaultMetadataHTTPClient, + } +} + +// fetchIDToken fetches an ID token for the given audience from the GCE +// Metadata Server. +// +// It takes a context controlling HTTP request cancellation and an audience +// string for the requested token. It returns the raw JWT token string, +// expiration timestamp parsed from the token's "exp" claim, and an error +// mapped to a gRPC status if the HTTP request or JWT parsing fails: +// - If the HTTP request fails with a status that maps to gRPC UNAVAILABLE +// according to HTTP to gRPC status code mappings, it returns UNAVAILABLE. +// - All other HTTP error status codes and JWT parsing failures map to +// UNAUTHENTICATED. +// - Non-HTTP request failures are mapped to UNAVAILABLE. +func (f *idTokenFetcher) fetchIDToken(ctx context.Context, audience string) (string, time.Time, error) { + reqURL := fmt.Sprintf("http://%s/computeMetadata/v1/instance/service-accounts/default/identity?audience=%s&format=full", f.metadataHost, url.QueryEscape(audience)) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil) + if err != nil { + return "", time.Time{}, status.Errorf(codes.Unavailable, "credentials: failed to fetch ID token: %v", err) + } + req.Header.Set("Metadata-Flavor", "Google") + + resp, err := f.client.Do(req) + if err != nil { + return "", time.Time{}, status.Errorf(codes.Unavailable, "credentials: failed to fetch ID token: %v", err) + } + defer resp.Body.Close() + + // Limit the response body read to 1 MB to prevent unbounded memory + // allocation if the server returns an unexpectedly large response, matching + // the limit used by the golang.org/x/oauth2 package when reading token + // responses. + body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + if err != nil { + return "", time.Time{}, status.Errorf(codes.Unavailable, "credentials: failed to fetch ID token: %v", err) + } + + // Per gRFC A83, if the returned HTTP status maps to UNAVAILABLE in the + // standard HTTP to gRPC status code mapping, return UNAVAILABLE; otherwise, + // return UNAUTHENTICATED. + if resp.StatusCode != http.StatusOK { + switch transport.HTTPStatusConvTab[resp.StatusCode] { + case codes.Unavailable: + return "", time.Time{}, status.Errorf(codes.Unavailable, "credentials: failed to fetch ID token: HTTP status %d: %s", resp.StatusCode, strings.TrimSpace(string(body))) + default: + return "", time.Time{}, status.Errorf(codes.Unauthenticated, "credentials: failed to fetch ID token: HTTP status %d: %s", resp.StatusCode, strings.TrimSpace(string(body))) + } + } + + // Handle the HTTP 200 OK case. + rawJWT := strings.TrimSpace(string(body)) + expiry, err := parseJWTExpiry(rawJWT) + if err != nil { + return "", time.Time{}, status.Errorf(codes.Unauthenticated, "credentials: failed to fetch ID token: %v", err) + } + + return rawJWT, expiry, nil +} + +type jwtPayload struct { + // Per RFC 7519 Section 2, JWT NumericDate values (such as "exp") are JSON + // numeric values that may contain non-integer fractional seconds. Using + // float64 ensures json.Unmarshal succeeds even if fractional seconds are + // present. + Exp float64 `json:"exp"` +} + +// parseJWTExpiry parses a JWT string to extract its expiration timestamp +// ("exp" claim). +// +// A compact JWT consists of three dot-separated, Base64URL-encoded parts: +// 1. Header: Contains metadata like algorithm and token type. Ignored here. +// 2. Payload: Contains token claims in JSON format. Decoded to extract the +// expiration timestamp ("exp"). +// 3. Signature: Cryptographic signature. Ignored here. +// +// Per gRFC A83, if the JWT format is invalid or the "exp" claim cannot be +// extracted, an error is returned. +func parseJWTExpiry(jwtStr string) (time.Time, error) { + // A valid JWT must have 3 parts: header, payload, and signature. + parts := strings.Split(jwtStr, ".") + if len(parts) != 3 { + return time.Time{}, fmt.Errorf("invalid JWT format: expected 3 parts, got %d", len(parts)) + } + + // Per the JWT specification (RFC 7519 / RFC 7515), compact JWT segments use + // Base64URL encoding with all trailing '=' padding characters omitted. + // base64.RawURLEncoding is unpadded Base64URL encoding and decodes these + // strings directly without requiring manual padding. + data, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return time.Time{}, fmt.Errorf("failed to decode JWT payload: %v", err) + } + + var payload jwtPayload + if err := json.Unmarshal(data, &payload); err != nil { + return time.Time{}, fmt.Errorf("failed to unmarshal JWT payload: %v", err) + } + + if payload.Exp <= 0 { + return time.Time{}, fmt.Errorf("missing or invalid 'exp' claim in JWT payload") + } + + return time.Unix(int64(payload.Exp), 0), nil +} diff --git a/vendor/google.golang.org/grpc/credentials/google/internal/internal.go b/vendor/google.golang.org/grpc/credentials/google/internal/internal.go index 9557d264d2..fe27827a4a 100644 --- a/vendor/google.golang.org/grpc/credentials/google/internal/internal.go +++ b/vendor/google.golang.org/grpc/credentials/google/internal/internal.go @@ -20,8 +20,9 @@ package internal import ( - "cloud.google.com/go/auth" - "cloud.google.com/go/auth/credentials/idtoken" + "context" + "time" + "google.golang.org/grpc/internal/backoff" ) @@ -30,6 +31,9 @@ var ( // BackoffStrategy is the backoff strategy to use when token fetch fails. BackoffStrategy backoff.Strategy - // NewIDTokenCredentials builds idtoken credentials using specified options. - NewIDTokenCredentials func(opts *idtoken.Options) (*auth.Credentials, error) + // IDTokenFetcher returns a function used to fetch an ID token for the + // given audience. It takes a ctx and audience string as parameter and + // returns the raw JWT token string, token's expiration timestamp and + // error if encountered. + IDTokenFetcher func() func(context.Context, string) (string, time.Time, error) ) diff --git a/vendor/google.golang.org/grpc/health/grpc_health_v1/health.pb.go b/vendor/google.golang.org/grpc/health/grpc_health_v1/health.pb.go index dcb98cdbc1..35d4c83035 100644 --- a/vendor/google.golang.org/grpc/health/grpc_health_v1/health.pb.go +++ b/vendor/google.golang.org/grpc/health/grpc_health_v1/health.pb.go @@ -17,7 +17,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v5.27.1 // source: grpc/health/v1/health.proto diff --git a/vendor/google.golang.org/grpc/health/server.go b/vendor/google.golang.org/grpc/health/server.go index d8eebe99d3..0c0669dff2 100644 --- a/vendor/google.golang.org/grpc/health/server.go +++ b/vendor/google.golang.org/grpc/health/server.go @@ -107,6 +107,9 @@ func (s *Server) Watch(in *healthpb.HealthCheckRequest, stream healthgrpc.Health defer func() { s.mu.Lock() delete(s.updates[service], stream) + if len(s.updates[service]) == 0 { + delete(s.updates, service) + } s.mu.Unlock() }() s.mu.Unlock() diff --git a/vendor/google.golang.org/grpc/internal/binarylog/method_logger.go b/vendor/google.golang.org/grpc/internal/binarylog/method_logger.go index 9669328914..ca0e586dff 100644 --- a/vendor/google.golang.org/grpc/internal/binarylog/method_logger.go +++ b/vendor/google.golang.org/grpc/internal/binarylog/method_logger.go @@ -395,7 +395,7 @@ func (c *Cancel) toProto() *binlogpb.GrpcLogEntry { // omitted. func metadataKeyOmit(key string) bool { switch key { - case "lb-token", ":path", ":authority", "content-encoding", "content-type", "user-agent", "te": + case "lb-token", ":path", ":authority", "content-encoding", "content-type", "user-agent", "te", "authorization": return true case "grpc-trace-bin": // grpc-trace-bin is special because it's visible to users. return false diff --git a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go index dc6fc0c4cb..2f4ae2fada 100644 --- a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go +++ b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go @@ -144,14 +144,14 @@ var ( // ControlBufferThrottleLimit is the maximum number of control frames that can // be queued in the control buffer before throttling is applied. The value - // must be between 1 and 10,000, and is set to 100 by default. + // must be between 1 and 100,000, and is set to 10,000 by default. // // This environment variable serves as an escape hatch to increase the // throttling limit if unforeseen issues arise, and it will be removed in a // future release. // - // TODO: Remove this env var once v1.83.0 is released. - ControlBufferThrottleLimit = uint64FromEnv("GRPC_GO_EXPERIMENTAL_CONTROL_BUFFER_THROTTLE_LIMIT", 100, 1, 10000) + // TODO: Remove this env var once v1.86.0 is released. + ControlBufferThrottleLimit = uint64FromEnv("GRPC_GO_EXPERIMENTAL_CONTROL_BUFFER_THROTTLE_LIMIT", 10000, 1, 100000) // ALTSMaxFrameSize is the maximum frame size for ALTS in bytes. // This can be overridden by setting the environment variable diff --git a/vendor/google.golang.org/grpc/internal/transport/bdp_estimator.go b/vendor/google.golang.org/grpc/internal/transport/bdp_estimator.go index 070680edba..4718576edb 100644 --- a/vendor/google.golang.org/grpc/internal/transport/bdp_estimator.go +++ b/vendor/google.golang.org/grpc/internal/transport/bdp_estimator.go @@ -47,10 +47,9 @@ const ( var bdpPing = &ping{data: [8]byte{2, 4, 16, 16, 9, 14, 7, 7}} type bdpEstimator struct { + mu sync.Mutex // sentAt is the time when the ping was sent. sentAt time.Time - - mu sync.Mutex // bdp is the current bdp estimate. bdp uint32 // sample is the number of bytes received in one measurement cycle. @@ -75,7 +74,9 @@ func (b *bdpEstimator) timesnap(d [8]byte) { if bdpPing.data != d { return } + b.mu.Lock() b.sentAt = time.Now() + b.mu.Unlock() } // add adds bytes to the current sample for calculating bdp. diff --git a/vendor/google.golang.org/grpc/internal/transport/controlbuf.go b/vendor/google.golang.org/grpc/internal/transport/controlbuf.go index 89c938fdb5..e33ac3a5fc 100644 --- a/vendor/google.golang.org/grpc/internal/transport/controlbuf.go +++ b/vendor/google.golang.org/grpc/internal/transport/controlbuf.go @@ -26,6 +26,7 @@ import ( "runtime" "sync" "sync/atomic" + "time" "golang.org/x/net/http2" "golang.org/x/net/http2/hpack" @@ -97,22 +98,35 @@ func (il *itemList) isEmpty() bool { return il.head == nil } -// maxQueuedControlBufferItems is the maximum number of frames (other than -// HEADERS and DATA) that we will buffer before preventing new reads from -// occurring on the transport. These are control frames sent in response to -// client requests, or frames that result in work being scheduled, such as -// RST_STREAM due to bad headers or settings acks. -var maxQueuedControlBufferItems = int(envconfig.ControlBufferThrottleLimit) +// maxQueuedTransportResponseFrames is the maximum number of "transport +// response" frames that we will buffer before preventing new reads from +// occurring on the transport. See cbItem.isTransportResponseFrame for the +// items that count towards this limit. +var maxQueuedTransportResponseFrames = int(envconfig.ControlBufferThrottleLimit) type cbItem interface { - isThrottled() bool + // isTransportResponseFrame reports whether the item counts against the + // maxQueuedTransportResponseFrames limit, when queued in the + // controlBuffer of a transport on the given side. + // + // Items that the peer can make the transport queue in unbounded + // numbers, for example by sending PING or SETTINGS frames, must be + // counted, so that the transport stops reading from a peer that sends + // such frames without reading what the transport writes. Items whose + // number grows with the local application's load (for example, one + // per stream it creates) must not be counted. Otherwise, under high + // concurrency, both peers could stop reading at the same time and + // deadlock (see https://github.com/grpc/grpc-go/issues/9330). + isTransportResponseFrame(s side) bool } -// throttledItem represents every item in the controlBuffer to which the overall -// throttling limit applies, other than outgoing HEADERS and DATA frames. -type throttledItem struct{} +// nonTransportResponseFrame represents items in the controlBuffer that are not +// queued in response to peer control/reset frames (for example, +// locally-initiated or flow-controlled items). These items are not counted +// against the maxQueuedTransportResponseFrames limit. +type nonTransportResponseFrame struct{} -func (throttledItem) isThrottled() bool { return true } +func (nonTransportResponseFrame) isTransportResponseFrame(side) bool { return false } // The following defines various control items which could flow through // the control buffer of transport. They represent different aspects of @@ -120,12 +134,13 @@ func (throttledItem) isThrottled() bool { return true } // registerStream is used to register an incoming stream with loopy writer. type registerStream struct { - throttledItem + nonTransportResponseFrame streamID uint32 wq *writeQuota } type clientHeaders struct { + nonTransportResponseFrame streamID uint32 hf []hpack.HeaderField initStream func(uint32) error @@ -134,9 +149,8 @@ type clientHeaders struct { onOrphaned func(error) } -func (*clientHeaders) isThrottled() bool { return false } - type serverHeaders struct { + nonTransportResponseFrame streamID uint32 hf []hpack.HeaderField endStream bool @@ -144,24 +158,37 @@ type serverHeaders struct { cleanup *cleanupStream } -func (h *serverHeaders) isThrottled() bool { return false } - type cleanupStream struct { - throttledItem streamID uint32 rst bool rstCode http2.ErrCode onWrite func() } +// isTransportResponseFrame returns true on the server since cleanupStream +// either results in a RST_STREAM frame being sent on the wire or in +// non-trivial work being performed when under a Rapid Reset attack. +// +// It returns false on the client, where a cleanupStream is queued at most once +// per stream, and the number of streams is controlled by the client and not by +// the server. Counting them on the client makes the client stop reading under +// high concurrency, which deadlocks with a server that has also stopped +// reading (see https://github.com/grpc/grpc-go/issues/9330). +func (*cleanupStream) isTransportResponseFrame(s side) bool { return s == serverSide } + type earlyAbortStream struct { - throttledItem streamID uint32 rst bool hf []hpack.HeaderField // Pre-built header fields } +// isTransportResponseFrame returns true on the server since earlyAbortStream +// results in a HEADERS frame being sent on the wire, and possibly a RST_STREAM +// frame. It is never queued on the client. +func (*earlyAbortStream) isTransportResponseFrame(s side) bool { return s == serverSide } + type dataFrame struct { + nonTransportResponseFrame streamID uint32 endStream bool h []byte @@ -172,36 +199,40 @@ type dataFrame struct { onEachWrite func() } -func (*dataFrame) isThrottled() bool { return false } - type incomingWindowUpdate struct { - throttledItem streamID uint32 increment uint32 } +// isTransportResponseFrame returns true to protect against a WINDOW_UPDATE +// flood attack from the peer. +func (*incomingWindowUpdate) isTransportResponseFrame(side) bool { return true } + type outgoingWindowUpdate struct { - throttledItem + nonTransportResponseFrame streamID uint32 increment uint32 } type incomingSettings struct { - throttledItem ss []http2.Setting } +// isTransportResponseFrame returns true since incomingSettings results in a +// SETTINGS ACK frame being sent on the wire. +func (*incomingSettings) isTransportResponseFrame(side) bool { return true } + type outgoingSettings struct { - throttledItem + nonTransportResponseFrame ss []http2.Setting } type incomingGoAway struct { - throttledItem + nonTransportResponseFrame } type goAway struct { - throttledItem + nonTransportResponseFrame code http2.ErrCode debugData []byte headsUp bool @@ -209,13 +240,23 @@ type goAway struct { } type ping struct { - throttledItem ack bool data [8]byte } +// isTransportResponseFrame returns true for all pings, and not only for ping +// acks sent in response to pings from the peer. This is because a BDP ping is +// queued in response to a DATA frame (of any size) from the peer, whenever +// there is no BDP ping outstanding. And since the BDP ping's payload is fixed, +// the peer can make it look like the BDP ping was acked at any time, by +// sending a ping ack with that payload. A peer alternating between empty DATA +// frames and such ping acks would otherwise be able to queue an unbounded +// number of BDP pings. Pings initiated locally (keepalive and BDP) are rare +// enough to not cause throttling of well-behaved peers. +func (*ping) isTransportResponseFrame(side) bool { return true } + type outFlowControlSizeRequest struct { - throttledItem + nonTransportResponseFrame resp chan uint32 } @@ -225,7 +266,7 @@ type outFlowControlSizeRequest struct { // can inspect settled accounting values (such as bytesOutStanding) after loopyWriter // has run to completion without any data races. type outStreamRequestForTesting struct { - throttledItem + nonTransportResponseFrame streamID uint32 resp chan *outStream } @@ -235,7 +276,7 @@ type outStreamRequestForTesting struct { // (by the client or server). The transport itself will close after the reader // encounters the EOF caused by the connection closure. type closeConnection struct { - throttledItem + nonTransportResponseFrame } type outStreamState int @@ -316,8 +357,9 @@ func (l *outStreamList) dequeue() *outStream { // shouldn't be confused with an HTTP2 frame, although some of the control // frames like dataFrame and headerFrame do go out on wire as HTTP2 frames. type controlBuffer struct { - wakeupCh chan struct{} // Unblocks readers waiting for something to read. + wakeupCh chan struct{} // Unblocks readers waiting for new items. done <-chan struct{} // Closed when the transport is done. + side side // Side of the transport owning this buffer. // Mutex guards all the fields below, except trfChan which can be read // atomically without holding mu. @@ -326,26 +368,30 @@ type controlBuffer struct { closed bool // True when the controlbuf is finished. list *itemList // List of queued control frames. - // transportResponseFrames counts the number of queued items that represent - // the response of an action initiated by the peer. trfChan is created - // when transportResponseFrames >= maxQueuedTransportResponseFrames and is - // closed and nilled when transportResponseFrames drops below the - // threshold. Both fields are protected by mu. + // transportResponseFrames counts the number of queued items that + // represent the response of an action initiated by the peer (as + // determined by cbItem.isTransportResponseFrame for this side). + // trfChan is created when transportResponseFrames >= + // maxQueuedTransportResponseFrames and is closed and nilled when + // transportResponseFrames drops below the threshold. Both fields are + // protected by mu. transportResponseFrames int trfChan atomic.Pointer[chan struct{}] } -func newControlBuffer(done <-chan struct{}) *controlBuffer { +func newControlBuffer(done <-chan struct{}, s side) *controlBuffer { return &controlBuffer{ wakeupCh: make(chan struct{}, 1), list: &itemList{}, done: done, + side: s, } } -// throttle blocks if there are too many frames in the control buf that -// represent the response of an action initiated by the peer, like -// incomingSettings cleanupStreams etc. +// throttle blocks if there are too many items in the control buffer that count +// towards the maxQueuedTransportResponseFrames limit (see +// cbItem.isTransportResponseFrame), until their number drops below the limit, +// the control buffer is finished, or the transport is done. func (c *controlBuffer) throttle() { if ch := c.trfChan.Load(); ch != nil { select { @@ -355,6 +401,25 @@ func (c *controlBuffer) throttle() { } } +// throttleWithTimeout is like throttle, but it stops waiting and returns an +// error if the control buffer is still throttled after the given duration. +func (c *controlBuffer) throttleWithTimeout(timeout time.Duration) error { + ch := c.trfChan.Load() + if ch == nil { + return nil + } + timer := time.NewTimer(timeout) + defer timer.Stop() + select { + case <-(*ch): + return nil + case <-c.done: + return nil + case <-timer.C: + return fmt.Errorf("still throttled after %v, with at least %d queued frames", timeout, maxQueuedTransportResponseFrames) + } +} + // put adds an item to the controlbuf. func (c *controlBuffer) put(it cbItem) error { _, err := c.executeAndPut(nil, it) @@ -390,9 +455,9 @@ func (c *controlBuffer) executeAndPut(f func() bool, it cbItem) (bool, error) { c.consumerWaiting = false } c.list.enqueue(it) - if it.isThrottled() { + if it.isTransportResponseFrame(c.side) { c.transportResponseFrames++ - if c.transportResponseFrames == maxQueuedControlBufferItems { + if c.transportResponseFrames == maxQueuedTransportResponseFrames { // We are adding the frame that puts us over the threshold; create // a throttling channel. ch := make(chan struct{}) @@ -447,8 +512,8 @@ func (c *controlBuffer) getOnceLocked() (any, error) { return nil, nil } h := c.list.dequeue().(cbItem) - if h.isThrottled() { - if c.transportResponseFrames == maxQueuedControlBufferItems { + if h.isTransportResponseFrame(c.side) { + if c.transportResponseFrames == maxQueuedTransportResponseFrames { // We are removing the frame that put us over the // threshold; close and clear the throttling channel. ch := c.trfChan.Swap(nil) @@ -484,9 +549,10 @@ func (c *controlBuffer) finish() { } } - // In case throttle() is currently in flight, it needs to be unblocked. - // Otherwise, the transport may not close, since the transport is closed by - // the reader encountering the connection error. + // In case throttle() or throttleWithTimeout() is currently in flight, + // it needs to be unblocked. Otherwise, the transport may not close, + // since the transport is closed by the reader encountering the + // connection error. ch := c.trfChan.Swap(nil) if ch != nil { close(*ch) diff --git a/vendor/google.golang.org/grpc/internal/transport/handler_server.go b/vendor/google.golang.org/grpc/internal/transport/handler_server.go index 9cd8d28d33..a701522b49 100644 --- a/vendor/google.golang.org/grpc/internal/transport/handler_server.go +++ b/vendor/google.golang.org/grpc/internal/transport/handler_server.go @@ -268,7 +268,7 @@ func (ht *serverHandlerTransport) writeStatus(s *ServerStream, st *status.Status for _, v := range vv { // http2 ResponseWriter mechanism to send undeclared Trailers after // the headers have possibly been written. - h.Add(http2.TrailerPrefix+k, encodeMetadataHeader(k, v)) + h.Add(http.TrailerPrefix+k, encodeMetadataHeader(k, v)) } } } @@ -494,7 +494,8 @@ func mapRecvMsgError(err error) error { if err == io.EOF || err == io.ErrUnexpectedEOF { return err } - if se, ok := err.(http2.StreamError); ok { + var se http2.StreamError + if errors.As(err, &se) { if code, ok := http2ErrConvTab[se.Code]; ok { return status.Error(code, se.Error()) } diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_client.go b/vendor/google.golang.org/grpc/internal/transport/http2_client.go index 720fc4e5b0..a198bafbd3 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_client.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_client.go @@ -65,6 +65,13 @@ var clientConnectionCounter uint64 var goAwayLoopyWriterTimeout = 5 * time.Second +// clientThrottleTimeout is the maximum amount of time for which the client +// stops reading from the connection because too many frames queued in +// response to the server (see maxQueuedTransportResponseFrames) are waiting +// to be written. After that, the client closes the connection instead of +// waiting forever, since the server is most likely not reading. +var clientThrottleTimeout = 10 * time.Second + var metadataFromOutgoingContextRaw = internal.FromOutgoingContextRaw.(func(context.Context) (metadata.MD, [][]string, bool)) // http2Client implements the ClientTransport interface with HTTP2. @@ -107,6 +114,12 @@ type http2Client struct { kp keepalive.ClientParameters keepaliveEnabled bool + // throttleTimeout is the maximum amount of time for which reads can + // stay throttled before the connection is closed. It is copied from + // clientThrottleTimeout on creation, so that tests can override the + // latter without racing with the readers of other transports. + throttleTimeout time.Duration + statsHandler stats.Handler initialWindowSize int32 @@ -359,6 +372,7 @@ func NewHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts streamQuota: defaultMaxStreamsClient, streamsQuotaAvailable: make(chan struct{}, 1), keepaliveEnabled: keepaliveEnabled, + throttleTimeout: clientThrottleTimeout, bufferPool: opts.BufferPool, onClose: onClose, } @@ -386,7 +400,7 @@ func NewHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts } else if md := imetadata.Get(addr); md != nil { t.md = md } - t.controlBuf = newControlBuffer(t.ctxDone) + t.controlBuf = newControlBuffer(t.ctxDone, clientSide) if opts.InitialWindowSize >= defaultWindowSize { t.initialWindowSize = opts.InitialWindowSize } @@ -952,6 +966,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr, handler s LocalAddr: t.localAddr, Compression: callHdr.SendCompress, Header: header, + Authority: callHdr.Host, }) } if transportDrainRequired { @@ -1726,9 +1741,19 @@ func (t *http2Client) reader(errCh chan<- error) { atomic.StoreInt64(&t.lastRead, time.Now().UnixNano()) } - // loop to keep reading incoming messages on this transport. + // Loop to keep reading incoming messages on this transport. for { - t.controlBuf.throttle() + // Stop reading while too many items that the server can make + // the client queue (see cbItem.isTransportResponseFrame) are + // waiting to be written, to bound the memory that a server + // can make the client use by flooding it without reading. If + // reads stay throttled for t.throttleTimeout, close the + // connection instead of waiting forever. + if err := t.controlBuf.throttleWithTimeout(t.throttleTimeout); err != nil { + t.logger.Warningf("Closing connection since the server is not reading: %v", err) + errClose = connectionErrorf(true, err, "closing connection since the server is not reading: %v", err) + return + } frame, err := t.framer.readFrame() if t.keepaliveEnabled { atomic.StoreInt64(&t.lastRead, time.Now().UnixNano()) diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_server.go b/vendor/google.golang.org/grpc/internal/transport/http2_server.go index f11e88cc5b..5c3fc76589 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_server.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_server.go @@ -297,7 +297,7 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport, ) t.logger = prefixLoggerForServerTransport(t) - t.controlBuf = newControlBuffer(t.done) + t.controlBuf = newControlBuffer(t.done, serverSide) if !config.StaticWindowSize { t.bdpEst = &bdpEstimator{ bdp: initialWindowSize, @@ -380,6 +380,11 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade defer t.maxStreamMu.Unlock() streamID := frame.Header().StreamID + if streamID%2 != 1 || streamID <= t.maxStreamID { + // illegal gRPC stream id. + return fmt.Errorf("received an illegal stream id: %v. headers frame: %+v", streamID, frame) + } + t.maxStreamID = streamID // frame.Truncated is set to true when framer detects that the current header // list size hits MaxHeaderListSize limit. @@ -393,12 +398,6 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade return nil } - if streamID%2 != 1 || streamID <= t.maxStreamID { - // illegal gRPC stream id. - return fmt.Errorf("received an illegal stream id: %v. headers frame: %+v", streamID, frame) - } - t.maxStreamID = streamID - s := &ServerStream{ Stream: Stream{ id: streamID, @@ -1014,7 +1013,17 @@ func (t *http2Server) streamContextErr(s *ServerStream) error { return ErrConnClosing default: } - return ContextErr(s.ctx.Err()) + err := s.ctx.Err() + if err == nil { + // In closeStream and finishStream, the stream state is transitioned to + // streamDone before s.cancel() is invoked so that concurrent operations + // immediately observe the terminal state. A concurrent caller (such as + // write or writeHeader) can see streamDone and invoke streamContextErr + // before s.cancel() runs or propagates to s.ctx. Default to + // context.Canceled rather than passing nil to ContextErr. + err = context.Canceled + } + return ContextErr(err) } // WriteHeader sends the header metadata md back to the client. @@ -1336,10 +1345,6 @@ func (t *http2Server) deleteStream(s *ServerStream, eosReceived bool) { // finishStream closes the stream and puts the trailing headerFrame into controlbuf. func (t *http2Server) finishStream(s *ServerStream, rst bool, rstCode http2.ErrCode, hdr *serverHeaders, eosReceived bool) { - // In case stream sending and receiving are invoked in separate - // goroutines (e.g., bi-directional streaming), cancel needs to be - // called to interrupt the potential blocking on other goroutines. - s.cancel() oldState := s.swapState(streamDone) if oldState == streamDone { @@ -1347,6 +1352,11 @@ func (t *http2Server) finishStream(s *ServerStream, rst bool, rstCode http2.ErrC return } + // In case stream sending and receiving are invoked in separate + // goroutines (e.g., bi-directional streaming), cancel needs to be + // called to interrupt the potential blocking on other goroutines. + s.cancel() + hdr.cleanup = &cleanupStream{ streamID: s.id, rst: rst, @@ -1360,15 +1370,16 @@ func (t *http2Server) finishStream(s *ServerStream, rst bool, rstCode http2.ErrC // closeStream clears the footprint of a stream when the stream is not needed any more. func (t *http2Server) closeStream(s *ServerStream, rst bool, rstCode http2.ErrCode, eosReceived bool) { + // We can't return early even if the stream's state is "done" as the state + // might have been set by the `finishStream` method. Deleting the stream via + // `finishStream` can get blocked on flow control. + s.swapState(streamDone) + // In case stream sending and receiving are invoked in separate // goroutines (e.g., bi-directional streaming), cancel needs to be // called to interrupt the potential blocking on other goroutines. s.cancel() - // We can't return early even if the stream's state is "done" as the state - // might have been set by the `finishStream` method. Deleting the stream via - // `finishStream` can get blocked on flow control. - s.swapState(streamDone) t.deleteStream(s, eosReceived) t.controlBuf.put(&cleanupStream{ diff --git a/vendor/google.golang.org/grpc/internal/transport/transport.go b/vendor/google.golang.org/grpc/internal/transport/transport.go index fc656b36c9..f627adf680 100644 --- a/vendor/google.golang.org/grpc/internal/transport/transport.go +++ b/vendor/google.golang.org/grpc/internal/transport/transport.go @@ -121,7 +121,9 @@ func (b *recvBuffer) put(r recvMsg) { if b.err != nil { // drop the buffer on the floor. Since b.err is not nil, any subsequent reads // will always return an error, making this buffer inaccessible. - r.buffer.Free() + if r.buffer != nil { + r.buffer.Free() + } // An error had occurred earlier, don't accept more // data or errors. return diff --git a/vendor/google.golang.org/grpc/metadata/metadata.go b/vendor/google.golang.org/grpc/metadata/metadata.go index 2b79204b48..a3090ff3d6 100644 --- a/vendor/google.golang.org/grpc/metadata/metadata.go +++ b/vendor/google.golang.org/grpc/metadata/metadata.go @@ -360,6 +360,66 @@ func FromOutgoingContext(ctx context.Context) (MD, bool) { return out, ok } +// ValueFromOutgoingContext returns the metadata value corresponding to the +// metadata key from the outgoing metadata if it exists. Keys are matched in a +// case-insensitive manner. +// +// Unlike FromOutgoingContext, this does not copy the entire outgoing metadata. +func ValueFromOutgoingContext(ctx context.Context, key string) []string { + raw, ok := ctx.Value(mdOutgoingKey{}).(rawMD) + if !ok { + return nil + } + + key = strings.ToLower(key) + // Unlike ValueFromIncomingContext, this can't return as soon as raw.md is + // checked: raw.added still needs to be walked and accumulated regardless. + // The else below prevents that from overwriting an exact match with an + // unrelated case-insensitive one. + var matchedMD []string + if v, ok := raw.md[key]; ok { + matchedMD = v + } else { + for k, v := range raw.md { + // Case insensitive comparison: MD is a map, and there's no + // guarantee that the MD attached to the context is created using + // our helper functions. + if strings.EqualFold(k, key) { + matchedMD = v + break + } + } + } + + // Defer allocating vals until raw.added actually has a match, so a match + // found only in raw.md doesn't pay for both a copyOf and a growing + // append. + var vals []string + for _, added := range raw.added { + if len(added)%2 == 1 { + panic(fmt.Sprintf("metadata: ValueFromOutgoingContext got an odd number of input pairs for metadata: %d", len(added))) + } + + // Case insensitive, like FromOutgoingContext: added isn't guaranteed + // lowercase, though AppendToOutgoingContext already lowercases it in + // practice, so try the cheap exact match first. + for i := 0; i < len(added); i += 2 { + if added[i] == key || strings.EqualFold(added[i], key) { + if vals == nil { + vals = make([]string, 0, len(matchedMD)+1) + vals = append(vals, matchedMD...) + } + vals = append(vals, added[i+1]) + } + } + } + + if vals == nil && matchedMD != nil { + return copyOf(matchedMD) + } + return vals +} + type rawMD struct { md MD added [][]string diff --git a/vendor/google.golang.org/grpc/rpc_util.go b/vendor/google.golang.org/grpc/rpc_util.go index 52f4ea513d..6f1bb2eae4 100644 --- a/vendor/google.golang.org/grpc/rpc_util.go +++ b/vendor/google.golang.org/grpc/rpc_util.go @@ -179,7 +179,6 @@ type callInfo struct { contentSubtype string codec baseCodec maxRetryRPCBufferSize int - onFinish []func(err error) authority string acceptedResponseCompressors []string } @@ -394,8 +393,7 @@ type OnFinishCallOption struct { OnFinish func(error) } -func (o OnFinishCallOption) before(c *callInfo) error { - c.onFinish = append(c.onFinish, o.OnFinish) +func (o OnFinishCallOption) before(*callInfo) error { return nil } diff --git a/vendor/google.golang.org/grpc/server.go b/vendor/google.golang.org/grpc/server.go index 4ab54115b4..5c16597296 100644 --- a/vendor/google.golang.org/grpc/server.go +++ b/vendor/google.golang.org/grpc/server.go @@ -1400,7 +1400,7 @@ func (s *Server) processRPC(ctx context.Context, stream *transport.ServerStream, ss.decompressorV1 = encoding.GetCompressor(rc) if ss.decompressorV1 == nil { st := status.Newf(codes.Unimplemented, "grpc: Decompressor is not installed for grpc-encoding %q", rc) - ss.writeStatus(st) + ss.s.WriteStatus(st) return st.Err() } } @@ -1491,7 +1491,7 @@ func (s *Server) processRPC(ctx context.Context, stream *transport.ServerStream, binlog.Log(ctx, st) } } - if err := ss.writeStatus(appStatus); err != nil { + if err := ss.s.WriteStatus(appStatus); err != nil { channelz.Warningf(logger, s.channelz, "grpc: Server.processRPC failed to write status: %v", err) } return appErr @@ -1510,7 +1510,7 @@ func (s *Server) processRPC(ctx context.Context, stream *transport.ServerStream, binlog.Log(ctx, st) } } - if err := ss.writeStatus(statusOK); err != nil { + if err := ss.s.WriteStatus(statusOK); err != nil { channelz.Warningf(logger, s.channelz, "grpc: Server.processRPC failed to write status: %v", err) } return err @@ -1719,7 +1719,13 @@ func (s *Server) stop(graceful bool) { } if graceful || s.opts.waitForHandlers { + // Release the lock while waiting for handlers to finish. Holding it + // here would deadlock a concurrent Stop() (the recommended way to abort + // a GracefulStop that is taking too long), because Stop() must acquire + // s.mu before it can force the server to shut down. + s.mu.Unlock() s.handlersWG.Wait() + s.mu.Lock() } if s.events != nil { diff --git a/vendor/google.golang.org/grpc/stats/stats.go b/vendor/google.golang.org/grpc/stats/stats.go index 10bf998aa5..c6996724c9 100644 --- a/vendor/google.golang.org/grpc/stats/stats.go +++ b/vendor/google.golang.org/grpc/stats/stats.go @@ -190,6 +190,9 @@ type OutHeader struct { Compression string // Header contains the header metadata sent. Header metadata.MD + // Authority is the :authority pseudo-header sent for the RPC. + // It is valid only if Client is true. + Authority string // The following fields are valid only if Client is true. // FullMethod is the full RPC method string, i.e., /package.service/method. diff --git a/vendor/google.golang.org/grpc/stream.go b/vendor/google.golang.org/grpc/stream.go index 52e59f2c5e..9fd628b8dc 100644 --- a/vendor/google.golang.org/grpc/stream.go +++ b/vendor/google.golang.org/grpc/stream.go @@ -201,9 +201,9 @@ func (w *clientStreamWrapper) SendMsg(m any) error { } -// RecvMsg receives message m from the stream. For RPCs that call RecvMsg only -// once i.e. only client streaming RPCs, it calls the underlying RecvMsg a -// second time after receiving the first message to get the trailers. +// RecvMsg receives message m from the stream. For non-server-streaming RPCs +// (unary and client-streaming), it calls the underlying RecvMsg a second time +// after receiving the first message to get the trailers. func (w *clientStreamWrapper) RecvMsg(m any) error { err := w.ClientStream.RecvMsg(m) if err != nil { @@ -224,19 +224,6 @@ func (w *clientStreamWrapper) RecvMsg(m any) error { return err } -// defaultStreamInterceptor is a StreamClientInterceptor which wraps the -// ClientStream and is always invoked as the first interceptor. It consolidates -// behavior for different RPC types at the level closest to the application, -// which simplifies the underlying stream implementation and other interceptors -// by avoiding duplicate or scattered handling. -func defaultStreamInterceptor(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ...CallOption) (ClientStream, error) { - cs, err := streamer(ctx, desc, cc, method, opts...) - if err != nil { - return nil, err - } - return &clientStreamWrapper{ClientStream: cs, desc: desc}, nil -} - // NewStream creates a new Stream for the client side. This is typically // called by generated code. ctx is used for the lifetime of the stream. // @@ -392,7 +379,11 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth } } - return newStream(ctx, opts...) + cs, err := newStream(ctx, opts...) + if err != nil { + return nil, err + } + return &clientStreamWrapper{ClientStream: cs, desc: desc}, nil } func newClientStreamWithParams(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, mc *serviceconfig.MethodConfig, onCommit func(), nameResolutionDelayed bool, opts ...CallOption) (_ ClientStream, err error) { @@ -632,7 +623,12 @@ func (a *csAttempt) getTransport() error { func (a *csAttempt) newStream() error { cs := a.cs - cs.callHdr.PreviousAttempts = cs.numRetries + // The header is copied because the fields set below, notably the authority + // override taken from the pick result, describe the endpoint picked for + // this attempt only. Mutating the clientStream's header would carry them + // into a later attempt. + callHdr := *cs.callHdr + callHdr.PreviousAttempts = cs.numRetries // Merge metadata stored in PickResult, if any, with existing call metadata. // It is safe to overwrite the csAttempt's context here, since all state @@ -659,11 +655,11 @@ func (a *csAttempt) newStream() error { // apply it, as specified in gRFC A81. if cs.callInfo.authority == "" { if authMD := a.pickResult.Metadata.Get(":authority"); len(authMD) > 0 { - cs.callHdr.Authority = authMD[0] + callHdr.Authority = authMD[0] } } } - s, err := a.transport.NewStream(a.ctx, cs.callHdr, a.statsHandler) + s, err := a.transport.NewStream(a.ctx, &callHdr, a.statsHandler) if err != nil { nse, ok := err.(*transport.NewStreamError) if !ok { @@ -698,10 +694,6 @@ type clientStream struct { cancel context.CancelFunc // cancels all attempts - sentLast bool // sent an end stream - - receivedFirstMsg bool // set after the first message is received - methodConfig *MethodConfig ctx context.Context // the application's context, wrapped by stats/tracing @@ -709,19 +701,10 @@ type clientStream struct { retryThrottler *retryThrottler // The throttler active when the RPC began. binlogs []binarylog.MethodLogger - // serverHeaderBinlogged is a boolean for whether server header has been - // logged. Server header will be logged when the first time one of those - // happens: stream.Header(), stream.Recv(). - // - // It's only read and used by Recv() and Header(), so it doesn't need to be - // synchronized. - serverHeaderBinlogged bool mu sync.Mutex - firstAttempt bool // if true, transparent retry is valid - numRetries int // exclusive of transparent retry attempt(s) - numRetriesSincePushback int // retries since pushback; to reset backoff - finished bool // TODO: replace with atomic cmpxchg or sync.Once? + numRetries int // exclusive of transparent retry attempt(s) + numRetriesSincePushback int // retries since pushback; to reset backoff // attempt is the active client stream attempt. // The only place where it is written is the newAttemptLocked method and this method never writes nil. // So, attempt can be nil only inside newClientStream function when clientStream is first created. @@ -731,13 +714,33 @@ type clientStream struct { // place where we need to check if the attempt is nil. attempt *csAttempt // TODO(hedging): hedging will have multiple attempts simultaneously. - committed bool // active attempt committed for retry? onCommit func() replayBuffer []replayOp // operations to replay on retry replayBufferSize int // current size of replayBuffer + + // Bool fields are grouped at the tail to eliminate the alignment padding + // that would otherwise follow each bool when the next field is pointer- or + // int-sized. See https://github.com/grpc/grpc-go/issues/9280 for benchmarks. + // Add new bool fields here, not inline above. + + // Not guarded by mu. + sentLast bool // sent an end stream + receivedFirstMsg bool // set after the first message is received + // serverHeaderBinlogged is a boolean for whether server header has been + // logged. Server header will be logged when the first time one of those + // happens: stream.Header(), stream.Recv(). + // + // It's only read and used by Recv() and Header(), so it doesn't need to be + // synchronized. + serverHeaderBinlogged bool // nameResolutionDelay indicates if there was a delay in the name resolution. // This field is only valid on client side, it's always false on server side. nameResolutionDelay bool + + // Guarded by mu. + firstAttempt bool // if true, transparent retry is valid + finished bool // TODO: replace with atomic cmpxchg or sync.Once? + committed bool // active attempt committed for retry? } type replayOp struct { @@ -755,10 +758,8 @@ type csAttempt struct { parser parser pickResult balancer.PickResult - finished bool - decompressorV0 Decompressor - decompressorV1 encoding.Compressor - decompressorSet bool + decompressorV0 Decompressor + decompressorV1 encoding.Compressor mu sync.Mutex // guards trInfo.tr // trInfo may be nil (if EnableTracing is false). @@ -769,10 +770,18 @@ type csAttempt struct { statsHandler stats.Handler beginTime time.Time - // set for newStream errors that may be transparently retried - allowTransparentRetry bool - // set for pick errors that are returned as a status - drop bool + // Bool fields are grouped at the tail to eliminate the alignment padding + // that would otherwise follow each bool when the next field is pointer- or + // int-sized. See https://github.com/grpc/grpc-go/issues/9347 for benchmarks. + // Add new bool fields here, not inline above. + + // Not guarded by mu. + decompressorSet bool + allowTransparentRetry bool // set for newStream errors that may be transparently retried + drop bool // set for pick errors that are returned as a status + + // Guarded by mu. + finished bool } func (cs *clientStream) commitAttemptLocked() { @@ -1500,20 +1509,28 @@ type addrConnStream struct { callInfo *callInfo transport transport.ClientTransport ctx context.Context - sentLast bool - receivedFirstMsg bool desc *StreamDesc codec baseCodec sendCompressorV0 Compressor sendCompressorV1 encoding.Compressor - decompressorSet bool decompressorV0 Decompressor decompressorV1 encoding.Compressor - parser parser // mu guards finished and is held for the entire finish method. - mu sync.Mutex - finished bool + mu sync.Mutex + parser parser + + // Bool fields are grouped at the tail to eliminate the alignment padding + // that would otherwise follow each bool when the next field is pointer- or + // int-sized. See https://github.com/grpc/grpc-go/issues/9348 for benchmarks. + // Add new bool fields here, not inline above. + + // Not guarded by mu. + sentLast bool + receivedFirstMsg bool + decompressorSet bool + + finished bool // guarded by mu } func (as *addrConnStream) Header() (metadata.MD, error) { @@ -1728,15 +1745,23 @@ type serverStream struct { sendCompressorName string - recvFirstMsg bool // set after the first message is received - maxReceiveMessageSize int maxSendMessageSize int - trInfo *traceInfo + + // mu guards trInfo.tr after the service handler runs. + mu sync.Mutex + trInfo *traceInfo statsHandler stats.Handler binlogs []binarylog.MethodLogger + + // Bool fields are grouped at the tail to eliminate the alignment padding + // that would otherwise follow each bool when the next field is pointer- or + // int-sized. See https://github.com/grpc/grpc-go/issues/9349 for benchmarks. + // Add new bool fields here, not inline above. + + recvFirstMsg bool // set after the first message is received // serverHeaderBinlogged indicates whether server header has been logged. It // will happen when one of the following two happens: stream.SendHeader(), // stream.Send(). @@ -1744,10 +1769,6 @@ type serverStream struct { // It's only checked in send and sendHeader, doesn't need to be // synchronized. serverHeaderBinlogged bool - - statusWritten atomic.Bool // True if status has been written to the transport. - - mu sync.Mutex // protects trInfo.tr after the service handler runs. } func (ss *serverStream) Context() context.Context { @@ -1795,16 +1816,6 @@ func (ss *serverStream) SetTrailer(md metadata.MD) { ss.s.SetTrailer(md) } -// writeStatus sends the status of a stream to the client. It uses an atomic -// CAS to guarantee that the status is written to the transport exactly once, -// even if called concurrently. -func (ss *serverStream) writeStatus(st *status.Status) error { - if !ss.statusWritten.CompareAndSwap(false, true) { - return nil - } - return ss.s.WriteStatus(st) -} - func (ss *serverStream) SendMsg(m any) (err error) { defer func() { if ss.trInfo != nil { @@ -1821,7 +1832,7 @@ func (ss *serverStream) SendMsg(m any) (err error) { } if err != nil && err != io.EOF { st, _ := status.FromError(toRPCErr(err)) - ss.writeStatus(st) + ss.s.WriteStatus(st) // Non-user specified status was sent out. This should be an error // case (as a server side Cancel maybe). // @@ -1904,7 +1915,7 @@ func (ss *serverStream) RecvMsg(m any) (err error) { } if err != nil && err != io.EOF { st, _ := status.FromError(toRPCErr(err)) - ss.writeStatus(st) + ss.s.WriteStatus(st) // Non-user specified status was sent out. This should be an error // case (as a server side Cancel maybe). // diff --git a/vendor/google.golang.org/grpc/version.go b/vendor/google.golang.org/grpc/version.go index 75f8ef50d4..d41341bcfe 100644 --- a/vendor/google.golang.org/grpc/version.go +++ b/vendor/google.golang.org/grpc/version.go @@ -19,4 +19,4 @@ package grpc // Version is the current grpc version. -const Version = "1.84.0" +const Version = "1.85.0" diff --git a/vendor/modules.txt b/vendor/modules.txt index b39a9f6708..f3bc0b63c6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -5,8 +5,6 @@ cloud.google.com/go ## explicit; go 1.25.0 cloud.google.com/go/auth cloud.google.com/go/auth/credentials -cloud.google.com/go/auth/credentials/idtoken -cloud.google.com/go/auth/credentials/impersonate cloud.google.com/go/auth/credentials/internal/externalaccount cloud.google.com/go/auth/credentials/internal/externalaccountuser cloud.google.com/go/auth/credentials/internal/gdch @@ -2104,8 +2102,8 @@ google.golang.org/genproto/googleapis/api/monitoredres google.golang.org/genproto/googleapis/rpc/code google.golang.org/genproto/googleapis/rpc/errdetails google.golang.org/genproto/googleapis/rpc/status -# google.golang.org/grpc v1.84.0 -## explicit; go 1.25.0 +# google.golang.org/grpc v1.85.0 +## explicit; go 1.26.0 google.golang.org/grpc google.golang.org/grpc/attributes google.golang.org/grpc/backoff