build(deps): bump deps specifically CVE-2024-45338

This commit is contained in:
Lukas Zapletal 2025-01-03 12:41:57 +01:00 committed by Achilleas Koutsou
parent bdc755f71b
commit f41c764ca7
308 changed files with 72185 additions and 132790 deletions

View file

@ -1,5 +1,74 @@
# Changelog
## [0.13.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.12.1...auth/v0.13.0) (2024-12-13)
### Features
* **auth:** Add logging support ([#11079](https://github.com/googleapis/google-cloud-go/issues/11079)) ([c80e31d](https://github.com/googleapis/google-cloud-go/commit/c80e31df5ecb33a810be3dfb9d9e27ac531aa91d))
* **auth:** Pass logger from auth layer to metadata package ([#11288](https://github.com/googleapis/google-cloud-go/issues/11288)) ([b552efd](https://github.com/googleapis/google-cloud-go/commit/b552efd6ab34e5dfded18438e0fbfd925805614f))
### Bug Fixes
* **auth:** Check compute cred type before non-default flag for DP ([#11255](https://github.com/googleapis/google-cloud-go/issues/11255)) ([4347ca1](https://github.com/googleapis/google-cloud-go/commit/4347ca141892be8ae813399b4b437662a103bc90))
## [0.12.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.12.0...auth/v0.12.1) (2024-12-10)
### Bug Fixes
* **auth:** Correct typo in link ([#11160](https://github.com/googleapis/google-cloud-go/issues/11160)) ([af6fb46](https://github.com/googleapis/google-cloud-go/commit/af6fb46d7cd694ddbe8c9d63bc4cdcd62b9fb2c1))
## [0.12.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.11.0...auth/v0.12.0) (2024-12-04)
### Features
* **auth:** Add support for providing custom certificate URL ([#11006](https://github.com/googleapis/google-cloud-go/issues/11006)) ([ebf3657](https://github.com/googleapis/google-cloud-go/commit/ebf36579724afb375d3974cf1da38f703e3b7dbc)), refs [#11005](https://github.com/googleapis/google-cloud-go/issues/11005)
### Bug Fixes
* **auth:** Ensure endpoints are present in Validator ([#11209](https://github.com/googleapis/google-cloud-go/issues/11209)) ([106cd53](https://github.com/googleapis/google-cloud-go/commit/106cd53309facaef1b8ea78376179f523f6912b9)), refs [#11006](https://github.com/googleapis/google-cloud-go/issues/11006) [#11190](https://github.com/googleapis/google-cloud-go/issues/11190) [#11189](https://github.com/googleapis/google-cloud-go/issues/11189) [#11188](https://github.com/googleapis/google-cloud-go/issues/11188)
## [0.11.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.10.2...auth/v0.11.0) (2024-11-21)
### Features
* **auth:** Add universe domain support to mTLS ([#11159](https://github.com/googleapis/google-cloud-go/issues/11159)) ([117748b](https://github.com/googleapis/google-cloud-go/commit/117748ba1cfd4ae62a6a4feb7e30951cb2bc9344))
## [0.10.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.10.1...auth/v0.10.2) (2024-11-12)
### Bug Fixes
* **auth:** Restore use of grpc.Dial ([#11118](https://github.com/googleapis/google-cloud-go/issues/11118)) ([2456b94](https://github.com/googleapis/google-cloud-go/commit/2456b943b7b8aaabd4d8bfb7572c0f477ae0db45)), refs [#7556](https://github.com/googleapis/google-cloud-go/issues/7556)
## [0.10.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.10.0...auth/v0.10.1) (2024-11-06)
### Bug Fixes
* **auth:** Restore Application Default Credentials support to idtoken ([#11083](https://github.com/googleapis/google-cloud-go/issues/11083)) ([8771f2e](https://github.com/googleapis/google-cloud-go/commit/8771f2ea9807ab822083808e0678392edff3b4f2))
* **auth:** Skip impersonate universe domain check if empty ([#11086](https://github.com/googleapis/google-cloud-go/issues/11086)) ([87159c1](https://github.com/googleapis/google-cloud-go/commit/87159c1059d4a18d1367ce62746a838a94964ab6))
## [0.10.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.9...auth/v0.10.0) (2024-10-30)
### Features
* **auth:** Add universe domain support to credentials/impersonate ([#10953](https://github.com/googleapis/google-cloud-go/issues/10953)) ([e06cb64](https://github.com/googleapis/google-cloud-go/commit/e06cb6499f7eda3aef08ab18ff197016f667684b))
## [0.9.9](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.8...auth/v0.9.9) (2024-10-22)
### Bug Fixes
* **auth:** Fallback cert lookups for missing files ([#11013](https://github.com/googleapis/google-cloud-go/issues/11013)) ([bd76695](https://github.com/googleapis/google-cloud-go/commit/bd766957ec238b7c40ddbabb369e612dc9b07313)), refs [#10844](https://github.com/googleapis/google-cloud-go/issues/10844)
* **auth:** Replace MDS endpoint universe_domain with universe-domain ([#11000](https://github.com/googleapis/google-cloud-go/issues/11000)) ([6a1586f](https://github.com/googleapis/google-cloud-go/commit/6a1586f2ce9974684affaea84e7b629313b4d114))
## [0.9.8](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.7...auth/v0.9.8) (2024-10-09)

View file

@ -24,6 +24,7 @@ import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"net/url"
"strings"
@ -32,6 +33,7 @@ import (
"cloud.google.com/go/auth/internal"
"cloud.google.com/go/auth/internal/jwt"
"github.com/googleapis/gax-go/v2/internallog"
)
const (
@ -227,9 +229,7 @@ type CredentialsOptions struct {
UniverseDomainProvider CredentialsPropertyProvider
}
// NewCredentials returns new [Credentials] from the provided options. Most users
// will want to build this object a function from the
// [cloud.google.com/go/auth/credentials] package.
// NewCredentials returns new [Credentials] from the provided options.
func NewCredentials(opts *CredentialsOptions) *Credentials {
creds := &Credentials{
TokenProvider: opts.TokenProvider,
@ -242,8 +242,8 @@ func NewCredentials(opts *CredentialsOptions) *Credentials {
return creds
}
// CachedTokenProviderOptions provided options for configuring a
// CachedTokenProvider.
// CachedTokenProviderOptions provides options for configuring a cached
// [TokenProvider].
type CachedTokenProviderOptions struct {
// DisableAutoRefresh makes the TokenProvider always return the same token,
// even if it is expired. The default is false. Optional.
@ -253,7 +253,7 @@ type CachedTokenProviderOptions struct {
// seconds. Optional.
ExpireEarly time.Duration
// DisableAsyncRefresh configures a synchronous workflow that refreshes
// stale tokens while blocking. The default is false. Optional.
// tokens in a blocking manner. The default is false. Optional.
DisableAsyncRefresh bool
}
@ -280,12 +280,7 @@ func (ctpo *CachedTokenProviderOptions) blockingRefresh() bool {
// NewCachedTokenProvider wraps a [TokenProvider] to cache the tokens returned
// by the underlying provider. By default it will refresh tokens asynchronously
// (non-blocking mode) within a window that starts 3 minutes and 45 seconds
// before they expire. The asynchronous (non-blocking) refresh can be changed to
// a synchronous (blocking) refresh using the
// CachedTokenProviderOptions.DisableAsyncRefresh option. The time-before-expiry
// duration can be configured using the CachedTokenProviderOptions.ExpireEarly
// option.
// a few minutes before they expire.
func NewCachedTokenProvider(tp TokenProvider, opts *CachedTokenProviderOptions) TokenProvider {
if ctp, ok := tp.(*cachedTokenProvider); ok {
return ctp
@ -497,6 +492,11 @@ type Options2LO struct {
// UseIDToken requests that the token returned be an ID token if one is
// returned from the server. Optional.
UseIDToken bool
// 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 *Options2LO) client() *http.Client {
@ -527,12 +527,13 @@ func New2LOTokenProvider(opts *Options2LO) (TokenProvider, error) {
if err := opts.validate(); err != nil {
return nil, err
}
return tokenProvider2LO{opts: opts, Client: opts.client()}, nil
return tokenProvider2LO{opts: opts, Client: opts.client(), logger: internallog.New(opts.Logger)}, nil
}
type tokenProvider2LO struct {
opts *Options2LO
Client *http.Client
logger *slog.Logger
}
func (tp tokenProvider2LO) Token(ctx context.Context) (*Token, error) {
@ -567,10 +568,12 @@ func (tp tokenProvider2LO) Token(ctx context.Context) (*Token, error) {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
tp.logger.DebugContext(ctx, "2LO token request", "request", internallog.HTTPRequest(req, []byte(v.Encode())))
resp, body, err := internal.DoRequest(tp.Client, req)
if err != nil {
return nil, fmt.Errorf("auth: cannot fetch token: %w", err)
}
tp.logger.DebugContext(ctx, "2LO token response", "response", internallog.HTTPResponse(resp, body))
if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
return nil, &Error{
Response: resp,

View file

@ -37,8 +37,11 @@ var (
// computeTokenProvider creates a [cloud.google.com/go/auth.TokenProvider] that
// uses the metadata service to retrieve tokens.
func computeTokenProvider(opts *DetectOptions) auth.TokenProvider {
return auth.NewCachedTokenProvider(computeProvider{scopes: opts.Scopes}, &auth.CachedTokenProviderOptions{
func computeTokenProvider(opts *DetectOptions, client *metadata.Client) auth.TokenProvider {
return auth.NewCachedTokenProvider(&computeProvider{
scopes: opts.Scopes,
client: client,
}, &auth.CachedTokenProviderOptions{
ExpireEarly: opts.EarlyTokenRefresh,
DisableAsyncRefresh: opts.DisableAsyncRefresh,
})
@ -47,6 +50,7 @@ func computeTokenProvider(opts *DetectOptions) auth.TokenProvider {
// computeProvider fetches tokens from the google cloud metadata service.
type computeProvider struct {
scopes []string
client *metadata.Client
}
type metadataTokenResp struct {
@ -55,7 +59,7 @@ type metadataTokenResp struct {
TokenType string `json:"token_type"`
}
func (cs computeProvider) Token(ctx context.Context) (*auth.Token, error) {
func (cs *computeProvider) Token(ctx context.Context) (*auth.Token, error) {
tokenURI, err := url.Parse(computeTokenURI)
if err != nil {
return nil, err
@ -65,7 +69,7 @@ func (cs computeProvider) Token(ctx context.Context) (*auth.Token, error) {
v.Set("scopes", strings.Join(cs.scopes, ","))
tokenURI.RawQuery = v.Encode()
}
tokenJSON, err := metadata.GetWithContext(ctx, tokenURI.String())
tokenJSON, err := cs.client.GetWithContext(ctx, tokenURI.String())
if err != nil {
return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
}

View file

@ -19,6 +19,7 @@ import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"os"
"time"
@ -27,6 +28,7 @@ import (
"cloud.google.com/go/auth/internal"
"cloud.google.com/go/auth/internal/credsfile"
"cloud.google.com/go/compute/metadata"
"github.com/googleapis/gax-go/v2/internallog"
)
const (
@ -96,12 +98,17 @@ func DetectDefault(opts *DetectOptions) (*auth.Credentials, error) {
}
if OnGCE() {
metadataClient := metadata.NewWithOptions(&metadata.Options{
Logger: opts.logger(),
})
return auth.NewCredentials(&auth.CredentialsOptions{
TokenProvider: computeTokenProvider(opts),
TokenProvider: computeTokenProvider(opts, metadataClient),
ProjectIDProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
return metadata.ProjectIDWithContext(ctx)
return metadataClient.ProjectIDWithContext(ctx)
}),
UniverseDomainProvider: &internal.ComputeUniverseDomainProvider{},
UniverseDomainProvider: &internal.ComputeUniverseDomainProvider{
MetadataClient: metadataClient,
},
}), nil
}
@ -158,6 +165,11 @@ type DetectOptions struct {
// The default value is "googleapis.com". This option is ignored for
// authentication flows that do not support universe domain. 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 *DetectOptions) validate() error {
@ -193,6 +205,10 @@ func (o *DetectOptions) client() *http.Client {
return internal.DefaultClient()
}
func (o *DetectOptions) logger() *slog.Logger {
return internallog.New(o.Logger)
}
func readCredentialsFile(filename string, opts *DetectOptions) (*auth.Credentials, error) {
b, err := os.ReadFile(filename)
if err != nil {
@ -253,6 +269,7 @@ func clientCredConfigFromJSON(b []byte, opts *DetectOptions) *auth.Options3LO {
AuthURL: c.AuthURI,
TokenURL: c.TokenURI,
Client: opts.client(),
Logger: opts.logger(),
EarlyTokenExpiry: opts.EarlyTokenRefresh,
AuthHandlerOpts: handleOpts,
// TODO(codyoss): refactor this out. We need to add in auto-detection

View file

@ -141,6 +141,7 @@ func handleServiceAccount(f *credsfile.ServiceAccountFile, opts *DetectOptions)
TokenURL: f.TokenURL,
Subject: opts.Subject,
Client: opts.client(),
Logger: opts.logger(),
}
if opts2LO.TokenURL == "" {
opts2LO.TokenURL = jwtTokenURL
@ -159,6 +160,7 @@ func handleUserCredential(f *credsfile.UserCredentialsFile, opts *DetectOptions)
EarlyTokenExpiry: opts.EarlyTokenRefresh,
RefreshToken: f.RefreshToken,
Client: opts.client(),
Logger: opts.logger(),
}
return auth.New3LOTokenProvider(opts3LO)
}
@ -177,6 +179,7 @@ func handleExternalAccount(f *credsfile.ExternalAccountFile, opts *DetectOptions
Scopes: opts.scopes(),
WorkforcePoolUserProject: f.WorkforcePoolUserProject,
Client: opts.client(),
Logger: opts.logger(),
IsDefaultClient: opts.Client == nil,
}
if f.ServiceAccountImpersonation != nil {
@ -195,6 +198,7 @@ func handleExternalAccountAuthorizedUser(f *credsfile.ExternalAccountAuthorizedU
ClientSecret: f.ClientSecret,
Scopes: opts.scopes(),
Client: opts.client(),
Logger: opts.logger(),
}
return externalaccountuser.NewTokenProvider(externalOpts)
}
@ -214,6 +218,7 @@ func handleImpersonatedServiceAccount(f *credsfile.ImpersonatedServiceAccountFil
Tp: tp,
Delegates: f.Delegates,
Client: opts.client(),
Logger: opts.logger(),
})
}
@ -221,5 +226,6 @@ func handleGDCHServiceAccount(f *credsfile.GDCHServiceAccountFile, opts *DetectO
return gdch.NewTokenProvider(f, &gdch.Options{
STSAudience: opts.STSAudience,
Client: opts.client(),
Logger: opts.logger(),
})
}

View file

@ -23,6 +23,7 @@ import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"net/url"
"os"
@ -32,6 +33,7 @@ import (
"time"
"cloud.google.com/go/auth/internal"
"github.com/googleapis/gax-go/v2/internallog"
)
var (
@ -87,6 +89,7 @@ type awsSubjectProvider struct {
reqOpts *RequestOptions
Client *http.Client
logger *slog.Logger
}
func (sp *awsSubjectProvider) subjectToken(ctx context.Context) (string, error) {
@ -192,10 +195,12 @@ func (sp *awsSubjectProvider) getAWSSessionToken(ctx context.Context) (string, e
}
req.Header.Set(awsIMDSv2SessionTTLHeader, awsIMDSv2SessionTTL)
sp.logger.DebugContext(ctx, "aws session token request", "request", internallog.HTTPRequest(req, nil))
resp, body, err := internal.DoRequest(sp.Client, req)
if err != nil {
return "", err
}
sp.logger.DebugContext(ctx, "aws session token response", "response", internallog.HTTPResponse(resp, body))
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("credentials: unable to retrieve AWS session token: %s", body)
}
@ -225,10 +230,12 @@ func (sp *awsSubjectProvider) getRegion(ctx context.Context, headers map[string]
for name, value := range headers {
req.Header.Add(name, value)
}
sp.logger.DebugContext(ctx, "aws region request", "request", internallog.HTTPRequest(req, nil))
resp, body, err := internal.DoRequest(sp.Client, req)
if err != nil {
return "", err
}
sp.logger.DebugContext(ctx, "aws region response", "response", internallog.HTTPResponse(resp, body))
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("credentials: unable to retrieve AWS region - %s", body)
}
@ -283,10 +290,12 @@ func (sp *awsSubjectProvider) getMetadataSecurityCredentials(ctx context.Context
for name, value := range headers {
req.Header.Add(name, value)
}
sp.logger.DebugContext(ctx, "aws security credential request", "request", internallog.HTTPRequest(req, nil))
resp, body, err := internal.DoRequest(sp.Client, req)
if err != nil {
return result, err
}
sp.logger.DebugContext(ctx, "aws security credential response", "response", internallog.HTTPResponse(resp, body))
if resp.StatusCode != http.StatusOK {
return result, fmt.Errorf("credentials: unable to retrieve AWS security credentials - %s", body)
}
@ -308,10 +317,12 @@ func (sp *awsSubjectProvider) getMetadataRoleName(ctx context.Context, headers m
req.Header.Add(name, value)
}
sp.logger.DebugContext(ctx, "aws metadata role request", "request", internallog.HTTPRequest(req, nil))
resp, body, err := internal.DoRequest(sp.Client, req)
if err != nil {
return "", err
}
sp.logger.DebugContext(ctx, "aws metadata role response", "response", internallog.HTTPResponse(resp, body))
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("credentials: unable to retrieve AWS role name - %s", body)
}

View file

@ -18,6 +18,7 @@ import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
"regexp"
"strconv"
@ -28,6 +29,7 @@ import (
"cloud.google.com/go/auth/credentials/internal/impersonate"
"cloud.google.com/go/auth/credentials/internal/stsexchange"
"cloud.google.com/go/auth/internal/credsfile"
"github.com/googleapis/gax-go/v2/internallog"
)
const (
@ -104,6 +106,11 @@ type Options struct {
// This is important for X509 credentials which should create a new client if the default was used
// but should respect a client explicitly passed in by the user.
IsDefaultClient bool
// 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
}
// SubjectTokenProvider can be used to supply a subject token to exchange for a
@ -224,6 +231,7 @@ func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
return nil, err
}
opts.resolveTokenURL()
logger := internallog.New(opts.Logger)
stp, err := newSubjectTokenProvider(opts)
if err != nil {
return nil, err
@ -238,6 +246,7 @@ func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
client: client,
opts: opts,
stp: stp,
logger: logger,
}
if opts.ServiceAccountImpersonationURL == "" {
@ -254,6 +263,7 @@ func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
Scopes: scopes,
Tp: auth.NewCachedTokenProvider(tp, nil),
TokenLifetimeSeconds: opts.ServiceAccountImpersonationLifetimeSeconds,
Logger: logger,
})
if err != nil {
return nil, err
@ -269,6 +279,7 @@ type subjectTokenProvider interface {
// tokenProvider is the provider that handles external credentials. It is used to retrieve Tokens.
type tokenProvider struct {
client *http.Client
logger *slog.Logger
opts *Options
stp subjectTokenProvider
}
@ -310,6 +321,7 @@ func (tp *tokenProvider) Token(ctx context.Context) (*auth.Token, error) {
Authentication: clientAuth,
Headers: header,
ExtraOpts: options,
Logger: tp.logger,
})
if err != nil {
return nil, err
@ -330,12 +342,14 @@ func (tp *tokenProvider) Token(ctx context.Context) (*auth.Token, error) {
// newSubjectTokenProvider determines the type of credsfile.CredentialSource needed to create a
// subjectTokenProvider
func newSubjectTokenProvider(o *Options) (subjectTokenProvider, error) {
logger := internallog.New(o.Logger)
reqOpts := &RequestOptions{Audience: o.Audience, SubjectTokenType: o.SubjectTokenType}
if o.AwsSecurityCredentialsProvider != nil {
return &awsSubjectProvider{
securityCredentialsProvider: o.AwsSecurityCredentialsProvider,
TargetResource: o.Audience,
reqOpts: reqOpts,
logger: logger,
}, nil
} else if o.SubjectTokenProvider != nil {
return &programmaticProvider{stp: o.SubjectTokenProvider, opts: reqOpts}, nil
@ -352,6 +366,7 @@ func newSubjectTokenProvider(o *Options) (subjectTokenProvider, error) {
CredVerificationURL: o.CredentialSource.URL,
TargetResource: o.Audience,
Client: o.Client,
logger: logger,
}
if o.CredentialSource.IMDSv2SessionTokenURL != "" {
awsProvider.IMDSv2SessionTokenURL = o.CredentialSource.IMDSv2SessionTokenURL
@ -362,7 +377,13 @@ func newSubjectTokenProvider(o *Options) (subjectTokenProvider, error) {
} else if o.CredentialSource.File != "" {
return &fileSubjectProvider{File: o.CredentialSource.File, Format: o.CredentialSource.Format}, nil
} else if o.CredentialSource.URL != "" {
return &urlSubjectProvider{URL: o.CredentialSource.URL, Headers: o.CredentialSource.Headers, Format: o.CredentialSource.Format, Client: o.Client}, nil
return &urlSubjectProvider{
URL: o.CredentialSource.URL,
Headers: o.CredentialSource.Headers,
Format: o.CredentialSource.Format,
Client: o.Client,
Logger: logger,
}, nil
} else if o.CredentialSource.Executable != nil {
ec := o.CredentialSource.Executable
if ec.Command == "" {

View file

@ -19,10 +19,12 @@ import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"cloud.google.com/go/auth/internal"
"cloud.google.com/go/auth/internal/credsfile"
"github.com/googleapis/gax-go/v2/internallog"
)
const (
@ -38,6 +40,7 @@ type urlSubjectProvider struct {
Headers map[string]string
Format *credsfile.Format
Client *http.Client
Logger *slog.Logger
}
func (sp *urlSubjectProvider) subjectToken(ctx context.Context) (string, error) {
@ -49,10 +52,12 @@ func (sp *urlSubjectProvider) subjectToken(ctx context.Context) (string, error)
for key, val := range sp.Headers {
req.Header.Add(key, val)
}
sp.Logger.DebugContext(ctx, "url subject token request", "request", internallog.HTTPRequest(req, nil))
resp, body, err := internal.DoRequest(sp.Client, req)
if err != nil {
return "", fmt.Errorf("credentials: invalid response when retrieving subject token: %w", err)
}
sp.Logger.DebugContext(ctx, "url subject token response", "response", internallog.HTTPResponse(resp, body))
if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
return "", fmt.Errorf("credentials: status code %d: %s", c, body)
}

View file

@ -17,12 +17,14 @@ package externalaccountuser
import (
"context"
"errors"
"log/slog"
"net/http"
"time"
"cloud.google.com/go/auth"
"cloud.google.com/go/auth/credentials/internal/stsexchange"
"cloud.google.com/go/auth/internal"
"github.com/googleapis/gax-go/v2/internallog"
)
// Options stores the configuration for fetching tokens with external authorized
@ -51,6 +53,8 @@ type Options struct {
// Client for token request.
Client *http.Client
// Logger for logging.
Logger *slog.Logger
}
func (c *Options) validate() bool {
@ -90,6 +94,7 @@ func (tp *tokenProvider) Token(ctx context.Context) (*auth.Token, error) {
RefreshToken: opts.RefreshToken,
Authentication: clientAuth,
Headers: headers,
Logger: internallog.New(tp.o.Logger),
})
if err != nil {
return nil, err

View file

@ -16,12 +16,13 @@ package gdch
import (
"context"
"crypto/rsa"
"crypto"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"net/url"
"os"
@ -32,6 +33,7 @@ import (
"cloud.google.com/go/auth/internal"
"cloud.google.com/go/auth/internal/credsfile"
"cloud.google.com/go/auth/internal/jwt"
"github.com/googleapis/gax-go/v2/internallog"
)
const (
@ -51,6 +53,7 @@ var (
type Options struct {
STSAudience string
Client *http.Client
Logger *slog.Logger
}
// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider] from a
@ -62,7 +65,7 @@ func NewTokenProvider(f *credsfile.GDCHServiceAccountFile, o *Options) (auth.Tok
if o.STSAudience == "" {
return nil, errors.New("credentials: STSAudience must be set for the GDCH auth flows")
}
pk, err := internal.ParseKey([]byte(f.PrivateKey))
signer, err := internal.ParseKey([]byte(f.PrivateKey))
if err != nil {
return nil, err
}
@ -75,10 +78,11 @@ func NewTokenProvider(f *credsfile.GDCHServiceAccountFile, o *Options) (auth.Tok
serviceIdentity: fmt.Sprintf("system:serviceaccount:%s:%s", f.Project, f.Name),
tokenURL: f.TokenURL,
aud: o.STSAudience,
pk: pk,
signer: signer,
pkID: f.PrivateKeyID,
certPool: certPool,
client: o.Client,
logger: internallog.New(o.Logger),
}
return tp, nil
}
@ -97,11 +101,12 @@ type gdchProvider struct {
serviceIdentity string
tokenURL string
aud string
pk *rsa.PrivateKey
signer crypto.Signer
pkID string
certPool *x509.CertPool
client *http.Client
logger *slog.Logger
}
func (g gdchProvider) Token(ctx context.Context) (*auth.Token, error) {
@ -120,7 +125,7 @@ func (g gdchProvider) Token(ctx context.Context) (*auth.Token, error) {
Type: jwt.HeaderType,
KeyID: string(g.pkID),
}
payload, err := jwt.EncodeJWS(&h, &claims, g.pk)
payload, err := jwt.EncodeJWS(&h, &claims, g.signer)
if err != nil {
return nil, err
}
@ -136,10 +141,12 @@ func (g gdchProvider) Token(ctx context.Context) (*auth.Token, error) {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
g.logger.DebugContext(ctx, "gdch token request", "request", internallog.HTTPRequest(req, []byte(v.Encode())))
resp, body, err := internal.DoRequest(g.client, req)
if err != nil {
return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
}
g.logger.DebugContext(ctx, "gdch token response", "response", internallog.HTTPResponse(resp, body))
if c := resp.StatusCode; c < http.StatusOK || c > http.StatusMultipleChoices {
return nil, &auth.Error{
Response: resp,

View file

@ -20,11 +20,13 @@ import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"time"
"cloud.google.com/go/auth"
"cloud.google.com/go/auth/internal"
"github.com/googleapis/gax-go/v2/internallog"
)
const (
@ -74,6 +76,11 @@ type Options struct {
// Client configures the underlying client used to make network requests
// when fetching tokens. Required.
Client *http.Client
// 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) validate() error {
@ -88,6 +95,7 @@ func (o *Options) validate() error {
// Token performs the exchange to get a temporary service account token to allow access to GCP.
func (o *Options) Token(ctx context.Context) (*auth.Token, error) {
logger := internallog.New(o.Logger)
lifetime := defaultTokenLifetime
if o.TokenLifetimeSeconds != 0 {
lifetime = fmt.Sprintf("%ds", o.TokenLifetimeSeconds)
@ -109,10 +117,12 @@ func (o *Options) Token(ctx context.Context) (*auth.Token, error) {
if err := setAuthHeader(ctx, o.Tp, req); err != nil {
return nil, err
}
logger.DebugContext(ctx, "impersonated token request", "request", internallog.HTTPRequest(req, b))
resp, body, err := internal.DoRequest(o.Client, req)
if err != nil {
return nil, fmt.Errorf("credentials: unable to generate access token: %w", err)
}
logger.DebugContext(ctx, "impersonated token response", "response", internallog.HTTPResponse(resp, body))
if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
return nil, fmt.Errorf("credentials: status code %d: %s", c, body)
}

View file

@ -19,6 +19,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"net/url"
"strconv"
@ -26,6 +27,7 @@ import (
"cloud.google.com/go/auth"
"cloud.google.com/go/auth/internal"
"github.com/googleapis/gax-go/v2/internallog"
)
const (
@ -40,6 +42,7 @@ const (
// Options stores the configuration for making an sts exchange request.
type Options struct {
Client *http.Client
Logger *slog.Logger
Endpoint string
Request *TokenRequest
Authentication ClientAuthentication
@ -80,6 +83,7 @@ func ExchangeToken(ctx context.Context, opts *Options) (*TokenResponse, error) {
func doRequest(ctx context.Context, opts *Options, data url.Values) (*TokenResponse, error) {
opts.Authentication.InjectAuthentication(data, opts.Headers)
encodedData := data.Encode()
logger := internallog.New(opts.Logger)
req, err := http.NewRequestWithContext(ctx, "POST", opts.Endpoint, strings.NewReader(encodedData))
if err != nil {
@ -93,10 +97,12 @@ func doRequest(ctx context.Context, opts *Options, data url.Values) (*TokenRespo
}
req.Header.Set("Content-Length", strconv.Itoa(len(encodedData)))
logger.DebugContext(ctx, "sts token request", "request", internallog.HTTPRequest(req, []byte(encodedData)))
resp, body, err := internal.DoRequest(opts.Client, req)
if err != nil {
return nil, fmt.Errorf("credentials: invalid response from Secure Token Server: %w", err)
}
logger.DebugContext(ctx, "sts token response", "response", internallog.HTTPResponse(resp, body))
if c := resp.StatusCode; c < http.StatusOK || c > http.StatusMultipleChoices {
return nil, fmt.Errorf("credentials: status code %d: %s", c, body)
}

View file

@ -16,9 +16,10 @@ package credentials
import (
"context"
"crypto/rsa"
"crypto"
"errors"
"fmt"
"log/slog"
"strings"
"time"
@ -39,7 +40,7 @@ func configureSelfSignedJWT(f *credsfile.ServiceAccountFile, opts *DetectOptions
if len(opts.scopes()) == 0 && opts.Audience == "" {
return nil, errors.New("credentials: both scopes and audience are empty")
}
pk, err := internal.ParseKey([]byte(f.PrivateKey))
signer, err := internal.ParseKey([]byte(f.PrivateKey))
if err != nil {
return nil, fmt.Errorf("credentials: could not parse key: %w", err)
}
@ -47,8 +48,9 @@ func configureSelfSignedJWT(f *credsfile.ServiceAccountFile, opts *DetectOptions
email: f.ClientEmail,
audience: opts.Audience,
scopes: opts.scopes(),
pk: pk,
signer: signer,
pkID: f.PrivateKeyID,
logger: opts.logger(),
}, nil
}
@ -56,8 +58,9 @@ type selfSignedTokenProvider struct {
email string
audience string
scopes []string
pk *rsa.PrivateKey
signer crypto.Signer
pkID string
logger *slog.Logger
}
func (tp *selfSignedTokenProvider) Token(context.Context) (*auth.Token, error) {
@ -77,9 +80,10 @@ func (tp *selfSignedTokenProvider) Token(context.Context) (*auth.Token, error) {
Type: jwt.HeaderType,
KeyID: string(tp.pkID),
}
msg, err := jwt.EncodeJWS(h, c, tp.pk)
tok, err := jwt.EncodeJWS(h, c, tp.signer)
if err != nil {
return nil, fmt.Errorf("credentials: could not encode JWT: %w", err)
}
return &auth.Token{Value: msg, Type: internal.TokenTypeBearer, Expiry: exp}, nil
tp.logger.Debug("created self-signed JWT", "token", tok)
return &auth.Token{Value: tok, Type: internal.TokenTypeBearer, Expiry: exp}, nil
}

View file

@ -66,12 +66,12 @@ func isTokenProviderDirectPathCompatible(tp auth.TokenProvider, o *Options) bool
if tok == nil {
return false
}
if o.InternalOptions != nil && o.InternalOptions.EnableNonDefaultSAForDirectPath {
return true
}
if tok.MetadataString("auth.google.tokenSource") != "compute-metadata" {
return false
}
if o.InternalOptions != nil && o.InternalOptions.EnableNonDefaultSAForDirectPath {
return true
}
if tok.MetadataString("auth.google.serviceAccount") != "default" {
return false
}

View file

@ -21,6 +21,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"log/slog"
"net/http"
"os"
"sync"
@ -29,7 +30,7 @@ import (
"cloud.google.com/go/auth/credentials"
"cloud.google.com/go/auth/internal"
"cloud.google.com/go/auth/internal/transport"
"go.opencensus.io/plugin/ocgrpc"
"github.com/googleapis/gax-go/v2/internallog"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
grpccreds "google.golang.org/grpc/credentials"
@ -117,6 +118,11 @@ type Options struct {
// APIKey specifies an API key to be used as the basis for authentication.
// If set DetectOpts are ignored.
APIKey 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
// InternalOptions are NOT meant to be set directly by consumers of this
// package, they should only be set by generated client code.
@ -132,6 +138,10 @@ func (o *Options) client() *http.Client {
return nil
}
func (o *Options) logger() *slog.Logger {
return internallog.New(o.Logger)
}
func (o *Options) validate() error {
if o == nil {
return errors.New("grpctransport: opts required to be non-nil")
@ -173,6 +183,9 @@ func (o *Options) resolveDetectOptions() *credentials.DetectOptions {
do.Client = transport.DefaultHTTPClientWithTLS(tlsConfig)
do.TokenURL = credentials.GoogleMTLSTokenURL
}
if do.Logger == nil {
do.Logger = o.logger()
}
return do
}
@ -241,6 +254,7 @@ func dial(ctx context.Context, secure bool, opts *Options) (*grpc.ClientConn, er
ClientCertProvider: opts.ClientCertProvider,
Client: opts.client(),
UniverseDomain: opts.UniverseDomain,
Logger: opts.logger(),
}
if io := opts.InternalOptions; io != nil {
tOpts.DefaultEndpointTemplate = io.DefaultEndpointTemplate
@ -318,11 +332,10 @@ func dial(ctx context.Context, secure bool, opts *Options) (*grpc.ClientConn, er
// Add tracing, but before the other options, so that clients can override the
// gRPC stats handler.
// This assumes that gRPC options are processed in order, left to right.
grpcOpts = addOCStatsHandler(grpcOpts, opts)
grpcOpts = addOpenTelemetryStatsHandler(grpcOpts, opts)
grpcOpts = append(grpcOpts, opts.GRPCDialOpts...)
return grpc.NewClient(endpoint, grpcOpts...)
return grpc.Dial(endpoint, grpcOpts...)
}
// grpcKeyProvider satisfies https://pkg.go.dev/google.golang.org/grpc/credentials#PerRPCCredentials.
@ -417,13 +430,6 @@ func (c *grpcCredentialsProvider) RequireTransportSecurity() bool {
return c.secure
}
func addOCStatsHandler(dialOpts []grpc.DialOption, opts *Options) []grpc.DialOption {
if opts.DisableTelemetry {
return dialOpts
}
return append(dialOpts, grpc.WithStatsHandler(&ocgrpc.ClientHandler{}))
}
func addOpenTelemetryStatsHandler(dialOpts []grpc.DialOption, opts *Options) []grpc.DialOption {
if opts.DisableTelemetry {
return dialOpts

View file

@ -20,12 +20,14 @@ import (
"crypto/tls"
"errors"
"fmt"
"log/slog"
"net/http"
"cloud.google.com/go/auth"
detect "cloud.google.com/go/auth/credentials"
"cloud.google.com/go/auth/internal"
"cloud.google.com/go/auth/internal/transport"
"github.com/googleapis/gax-go/v2/internallog"
)
// ClientCertProvider is a function that returns a TLS client certificate to be
@ -69,6 +71,11 @@ type Options struct {
// configured for the client, which will be compared to the universe domain
// that is separately configured for the credentials.
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
// InternalOptions are NOT meant to be set directly by consumers of this
// package, they should only be set by generated client code.
@ -101,6 +108,10 @@ func (o *Options) client() *http.Client {
return nil
}
func (o *Options) logger() *slog.Logger {
return internallog.New(o.Logger)
}
func (o *Options) resolveDetectOptions() *detect.DetectOptions {
io := o.InternalOptions
// soft-clone these so we are not updating a ref the user holds and may reuse
@ -125,6 +136,9 @@ func (o *Options) resolveDetectOptions() *detect.DetectOptions {
do.Client = transport.DefaultHTTPClientWithTLS(tlsConfig)
do.TokenURL = detect.GoogleMTLSTokenURL
}
if do.Logger == nil {
do.Logger = o.logger()
}
return do
}
@ -147,14 +161,21 @@ type InternalOptions struct {
// service.
DefaultScopes []string
// SkipValidation bypasses validation on Options. It should only be used
// internally for clients that needs more control over their transport.
// internally for clients that need more control over their transport.
SkipValidation bool
// SkipUniverseDomainValidation skips the verification that the universe
// domain configured for the client matches the universe domain configured
// for the credentials. It should only be used internally for clients that
// need more control over their transport. The default is false.
SkipUniverseDomainValidation bool
}
// AddAuthorizationMiddleware adds a middleware to the provided client's
// transport that sets the Authorization header with the value produced by the
// provided [cloud.google.com/go/auth.Credentials]. An error is returned only
// if client or creds is nil.
//
// This function does not support setting a universe domain value on the client.
func AddAuthorizationMiddleware(client *http.Client, creds *auth.Credentials) error {
if client == nil || creds == nil {
return fmt.Errorf("httptransport: client and tp must not be nil")
@ -173,7 +194,6 @@ func AddAuthorizationMiddleware(client *http.Client, creds *auth.Credentials) er
client.Transport = &authTransport{
creds: creds,
base: base,
// TODO(quartzmo): Somehow set clientUniverseDomain from impersonate calls.
}
return nil
}
@ -191,6 +211,7 @@ func NewClient(opts *Options) (*http.Client, error) {
ClientCertProvider: opts.ClientCertProvider,
Client: opts.client(),
UniverseDomain: opts.UniverseDomain,
Logger: opts.logger(),
}
if io := opts.InternalOptions; io != nil {
tOpts.DefaultEndpointTemplate = io.DefaultEndpointTemplate

View file

@ -1,93 +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 httptransport
import (
"encoding/binary"
"encoding/hex"
"fmt"
"net/http"
"strconv"
"strings"
"go.opencensus.io/trace"
"go.opencensus.io/trace/propagation"
)
const (
httpHeaderMaxSize = 200
cloudTraceHeader = `X-Cloud-Trace-Context`
)
// asserts the httpFormat fulfills this foreign interface
var _ propagation.HTTPFormat = (*httpFormat)(nil)
// httpFormat implements propagation.httpFormat to propagate
// traces in HTTP headers for Google Cloud Platform and Cloud Trace.
type httpFormat struct{}
// SpanContextFromRequest extracts a Cloud Trace span context from incoming requests.
func (f *httpFormat) SpanContextFromRequest(req *http.Request) (sc trace.SpanContext, ok bool) {
h := req.Header.Get(cloudTraceHeader)
// See https://cloud.google.com/trace/docs/faq for the header HTTPFormat.
// Return if the header is empty or missing, or if the header is unreasonably
// large, to avoid making unnecessary copies of a large string.
if h == "" || len(h) > httpHeaderMaxSize {
return trace.SpanContext{}, false
}
// Parse the trace id field.
slash := strings.Index(h, `/`)
if slash == -1 {
return trace.SpanContext{}, false
}
tid, h := h[:slash], h[slash+1:]
buf, err := hex.DecodeString(tid)
if err != nil {
return trace.SpanContext{}, false
}
copy(sc.TraceID[:], buf)
// Parse the span id field.
spanstr := h
semicolon := strings.Index(h, `;`)
if semicolon != -1 {
spanstr, h = h[:semicolon], h[semicolon+1:]
}
sid, err := strconv.ParseUint(spanstr, 10, 64)
if err != nil {
return trace.SpanContext{}, false
}
binary.BigEndian.PutUint64(sc.SpanID[:], sid)
// Parse the options field, options field is optional.
if !strings.HasPrefix(h, "o=") {
return sc, true
}
o, err := strconv.ParseUint(h[2:], 10, 32)
if err != nil {
return trace.SpanContext{}, false
}
sc.TraceOptions = trace.TraceOptions(o)
return sc, true
}
// SpanContextToRequest modifies the given request to include a Cloud Trace header.
func (f *httpFormat) SpanContextToRequest(sc trace.SpanContext, req *http.Request) {
sid := binary.BigEndian.Uint64(sc.SpanID[:])
header := fmt.Sprintf("%s/%d;o=%d", hex.EncodeToString(sc.TraceID[:]), sid, int64(sc.TraceOptions))
req.Header.Set(cloudTraceHeader, header)
}

View file

@ -27,7 +27,6 @@ import (
"cloud.google.com/go/auth/internal"
"cloud.google.com/go/auth/internal/transport"
"cloud.google.com/go/auth/internal/transport/cert"
"go.opencensus.io/plugin/ochttp"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"golang.org/x/net/http2"
)
@ -43,10 +42,7 @@ func newTransport(base http.RoundTripper, opts *Options) (http.RoundTripper, err
headers: headers,
}
var trans http.RoundTripper = ht
// Give OpenTelemetry precedence over OpenCensus in case user configuration
// causes both to write the same header (`X-Cloud-Trace-Context`).
trans = addOpenTelemetryTransport(trans, opts)
trans = addOCTransport(trans, opts)
switch {
case opts.DisableAuthentication:
// Do nothing.
@ -86,11 +82,16 @@ func newTransport(base http.RoundTripper, opts *Options) (http.RoundTripper, err
headers.Set(quotaProjectHeaderKey, qp)
}
}
var skipUD bool
if iOpts := opts.InternalOptions; iOpts != nil {
skipUD = iOpts.SkipUniverseDomainValidation
}
creds.TokenProvider = auth.NewCachedTokenProvider(creds.TokenProvider, nil)
trans = &authTransport{
base: trans,
creds: creds,
clientUniverseDomain: opts.UniverseDomain,
base: trans,
creds: creds,
clientUniverseDomain: opts.UniverseDomain,
skipUniverseDomainValidation: skipUD,
}
}
return trans, nil
@ -174,20 +175,11 @@ func addOpenTelemetryTransport(trans http.RoundTripper, opts *Options) http.Roun
return otelhttp.NewTransport(trans)
}
func addOCTransport(trans http.RoundTripper, opts *Options) http.RoundTripper {
if opts.DisableTelemetry {
return trans
}
return &ochttp.Transport{
Base: trans,
Propagation: &httpFormat{},
}
}
type authTransport struct {
creds *auth.Credentials
base http.RoundTripper
clientUniverseDomain string
creds *auth.Credentials
base http.RoundTripper
clientUniverseDomain string
skipUniverseDomainValidation bool
}
// getClientUniverseDomain returns the default service domain for a given Cloud
@ -226,7 +218,7 @@ func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if err != nil {
return nil, err
}
if token.MetadataString("auth.google.tokenSource") != "compute-metadata" {
if !t.skipUniverseDomainValidation && token.MetadataString("auth.google.tokenSource") != "compute-metadata" {
credentialsUniverseDomain, err := t.creds.UniverseDomain(req.Context())
if err != nil {
return nil, err

View file

@ -31,8 +31,7 @@ var (
// This is a copy of the gRPC internal googlecloud.OnGCE() func at:
// https://github.com/grpc/grpc-go/blob/master/internal/googlecloud/googlecloud.go
// The functionality is similar to the metadata.OnGCE() func at:
// https://github.com/xmenxk/google-cloud-go/blob/main/compute/metadata/metadata.go
//
// https://github.com/googleapis/google-cloud-go/blob/main/compute/metadata/metadata.go
// The difference is that OnComputeEngine() does not perform HTTP or DNS check on the metadata server.
// In particular, OnComputeEngine() will return false on Serverless.
func OnComputeEngine() bool {

View file

@ -16,7 +16,7 @@ package internal
import (
"context"
"crypto/rsa"
"crypto"
"crypto/x509"
"encoding/json"
"encoding/pem"
@ -72,25 +72,27 @@ func DefaultClient() *http.Client {
}
// ParseKey converts the binary contents of a private key file
// to an *rsa.PrivateKey. It detects whether the private key is in a
// to an crypto.Signer. It detects whether the private key is in a
// PEM container or not. If so, it extracts the the private key
// from PEM container before conversion. It only supports PEM
// containers with no passphrase.
func ParseKey(key []byte) (*rsa.PrivateKey, error) {
func ParseKey(key []byte) (crypto.Signer, error) {
block, _ := pem.Decode(key)
if block != nil {
key = block.Bytes
}
parsedKey, err := x509.ParsePKCS8PrivateKey(key)
var parsedKey crypto.PrivateKey
var err error
parsedKey, err = x509.ParsePKCS8PrivateKey(key)
if err != nil {
parsedKey, err = x509.ParsePKCS1PrivateKey(key)
if err != nil {
return nil, fmt.Errorf("private key should be a PEM or plain PKCS1 or PKCS8: %w", err)
}
}
parsed, ok := parsedKey.(*rsa.PrivateKey)
parsed, ok := parsedKey.(crypto.Signer)
if !ok {
return nil, errors.New("private key is invalid")
return nil, errors.New("private key is not a signer")
}
return parsed, nil
}
@ -179,6 +181,7 @@ func (p StaticProperty) GetProperty(context.Context) (string, error) {
// ComputeUniverseDomainProvider fetches the credentials universe domain from
// the google cloud metadata service.
type ComputeUniverseDomainProvider struct {
MetadataClient *metadata.Client
universeDomainOnce sync.Once
universeDomain string
universeDomainErr error
@ -188,7 +191,7 @@ type ComputeUniverseDomainProvider struct {
// metadata service.
func (c *ComputeUniverseDomainProvider) GetProperty(ctx context.Context) (string, error) {
c.universeDomainOnce.Do(func() {
c.universeDomain, c.universeDomainErr = getMetadataUniverseDomain(ctx)
c.universeDomain, c.universeDomainErr = getMetadataUniverseDomain(ctx, c.MetadataClient)
})
if c.universeDomainErr != nil {
return "", c.universeDomainErr
@ -197,14 +200,14 @@ func (c *ComputeUniverseDomainProvider) GetProperty(ctx context.Context) (string
}
// httpGetMetadataUniverseDomain is a package var for unit test substitution.
var httpGetMetadataUniverseDomain = func(ctx context.Context) (string, error) {
var httpGetMetadataUniverseDomain = func(ctx context.Context, client *metadata.Client) (string, error) {
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
return metadata.GetWithContext(ctx, "universe/universe_domain")
return client.GetWithContext(ctx, "universe/universe-domain")
}
func getMetadataUniverseDomain(ctx context.Context) (string, error) {
universeDomain, err := httpGetMetadataUniverseDomain(ctx)
func getMetadataUniverseDomain(ctx context.Context, client *metadata.Client) (string, error) {
universeDomain, err := httpGetMetadataUniverseDomain(ctx, client)
if err == nil {
return universeDomain, nil
}

View file

@ -111,7 +111,7 @@ func (c *Claims) encode() (string, error) {
}
// EncodeJWS encodes the data using the provided key as a JSON web signature.
func EncodeJWS(header *Header, c *Claims, key *rsa.PrivateKey) (string, error) {
func EncodeJWS(header *Header, c *Claims, signer crypto.Signer) (string, error) {
head, err := header.encode()
if err != nil {
return "", err
@ -123,7 +123,7 @@ func EncodeJWS(header *Header, c *Claims, key *rsa.PrivateKey) (string, error) {
ss := fmt.Sprintf("%s.%s", head, claims)
h := sha256.New()
h.Write([]byte(ss))
sig, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, h.Sum(nil))
sig, err := signer.Sign(rand.Reader, h.Sum(nil), crypto.SHA256)
if err != nil {
return "", err
}

View file

@ -20,6 +20,7 @@ import (
"crypto/x509"
"errors"
"log"
"log/slog"
"net"
"net/http"
"net/url"
@ -51,22 +52,19 @@ const (
mtlsMDSKey = "/run/google-mds-mtls/client.key"
)
var (
errUniverseNotSupportedMTLS = errors.New("mTLS is not supported in any universe other than googleapis.com")
)
// Options is a struct that is duplicated information from the individual
// transport packages in order to avoid cyclic deps. It correlates 1:1 with
// fields on httptransport.Options and grpctransport.Options.
type Options struct {
Endpoint string
DefaultMTLSEndpoint string
DefaultEndpointTemplate string
DefaultMTLSEndpoint string
ClientCertProvider cert.Provider
Client *http.Client
UniverseDomain string
EnableDirectPath bool
EnableDirectPathXds bool
Logger *slog.Logger
}
// getUniverseDomain returns the default service domain for a given Cloud
@ -94,6 +92,16 @@ func (o *Options) defaultEndpoint() string {
return strings.Replace(o.DefaultEndpointTemplate, universeDomainPlaceholder, o.getUniverseDomain(), 1)
}
// defaultMTLSEndpoint returns the DefaultMTLSEndpointTemplate merged with the
// universe domain if the DefaultMTLSEndpointTemplate is set, otherwise returns an
// empty string.
func (o *Options) defaultMTLSEndpoint() string {
if o.DefaultMTLSEndpoint == "" {
return ""
}
return strings.Replace(o.DefaultMTLSEndpoint, universeDomainPlaceholder, o.getUniverseDomain(), 1)
}
// mergedEndpoint merges a user-provided Endpoint of format host[:port] with the
// default endpoint.
func (o *Options) mergedEndpoint() (string, error) {
@ -256,12 +264,9 @@ func getTransportConfig(opts *Options) (*transportConfig, error) {
if !shouldUseS2A(clientCertSource, opts) {
return &defaultTransportConfig, nil
}
if !opts.isUniverseDomainGDU() {
return nil, errUniverseNotSupportedMTLS
}
s2aAddress := GetS2AAddress()
mtlsS2AAddress := GetMTLSS2AAddress()
s2aAddress := GetS2AAddress(opts.Logger)
mtlsS2AAddress := GetMTLSS2AAddress(opts.Logger)
if s2aAddress == "" && mtlsS2AAddress == "" {
return &defaultTransportConfig, nil
}
@ -270,7 +275,7 @@ func getTransportConfig(opts *Options) (*transportConfig, error) {
endpoint: endpoint,
s2aAddress: s2aAddress,
mtlsS2AAddress: mtlsS2AAddress,
s2aMTLSEndpoint: opts.DefaultMTLSEndpoint,
s2aMTLSEndpoint: opts.defaultMTLSEndpoint(),
}, nil
}
@ -316,24 +321,23 @@ type transportConfig struct {
// getEndpoint returns the endpoint for the service, taking into account the
// user-provided endpoint override "settings.Endpoint".
//
// If no endpoint override is specified, we will either return the default endpoint or
// the default mTLS endpoint if a client certificate is available.
// If no endpoint override is specified, we will either return the default
// endpoint or the default mTLS endpoint if a client certificate is available.
//
// You can override the default endpoint choice (mtls vs. regular) by setting the
// GOOGLE_API_USE_MTLS_ENDPOINT environment variable.
// You can override the default endpoint choice (mTLS vs. regular) by setting
// the GOOGLE_API_USE_MTLS_ENDPOINT environment variable.
//
// If the endpoint override is an address (host:port) rather than full base
// URL (ex. https://...), then the user-provided address will be merged into
// the default endpoint. For example, WithEndpoint("myhost:8000") and
// DefaultEndpointTemplate("https://UNIVERSE_DOMAIN/bar/baz") will return "https://myhost:8080/bar/baz"
// DefaultEndpointTemplate("https://UNIVERSE_DOMAIN/bar/baz") will return
// "https://myhost:8080/bar/baz". Note that this does not apply to the mTLS
// endpoint.
func getEndpoint(opts *Options, clientCertSource cert.Provider) (string, error) {
if opts.Endpoint == "" {
mtlsMode := getMTLSMode()
if mtlsMode == mTLSModeAlways || (clientCertSource != nil && mtlsMode == mTLSModeAuto) {
if !opts.isUniverseDomainGDU() {
return "", errUniverseNotSupportedMTLS
}
return opts.DefaultMTLSEndpoint, nil
return opts.defaultMTLSEndpoint(), nil
}
return opts.defaultEndpoint(), nil
}

View file

@ -16,7 +16,6 @@ package cert
import (
"crypto/tls"
"errors"
"github.com/googleapis/enterprise-certificate-proxy/client"
)
@ -37,10 +36,9 @@ type ecpSource struct {
func NewEnterpriseCertificateProxyProvider(configFilePath string) (Provider, error) {
key, err := client.Cred(configFilePath)
if err != nil {
if errors.Is(err, client.ErrCredUnavailable) {
return nil, errSourceUnavailable
}
return nil, err
// TODO(codyoss): once this is fixed upstream can handle this error a
// little better here. But be safe for now and assume unavailable.
return nil, errSourceUnavailable
}
return (&ecpSource{

View file

@ -82,10 +82,7 @@ func (s *workloadSource) getClientCertificate(info *tls.CertificateRequestInfo)
func getCertAndKeyFiles(configFilePath string) (string, string, error) {
jsonFile, err := os.Open(configFilePath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return "", "", errSourceUnavailable
}
return "", "", err
return "", "", errSourceUnavailable
}
byteValue, err := io.ReadAll(jsonFile)

View file

@ -19,6 +19,7 @@ import (
"encoding/json"
"fmt"
"log"
"log/slog"
"os"
"strconv"
"sync"
@ -39,8 +40,8 @@ var (
// GetS2AAddress returns the S2A address to be reached via plaintext connection.
// Returns empty string if not set or invalid.
func GetS2AAddress() string {
getMetadataMTLSAutoConfig()
func GetS2AAddress(logger *slog.Logger) string {
getMetadataMTLSAutoConfig(logger)
if !mtlsConfiguration.valid() {
return ""
}
@ -49,8 +50,8 @@ func GetS2AAddress() string {
// GetMTLSS2AAddress returns the S2A address to be reached via MTLS connection.
// Returns empty string if not set or invalid.
func GetMTLSS2AAddress() string {
getMetadataMTLSAutoConfig()
func GetMTLSS2AAddress(logger *slog.Logger) string {
getMetadataMTLSAutoConfig(logger)
if !mtlsConfiguration.valid() {
return ""
}
@ -74,22 +75,25 @@ type s2aAddresses struct {
MTLSAddress string `json:"mtls_address"`
}
func getMetadataMTLSAutoConfig() {
func getMetadataMTLSAutoConfig(logger *slog.Logger) {
var err error
mtlsOnce.Do(func() {
mtlsConfiguration, err = queryConfig()
mtlsConfiguration, err = queryConfig(logger)
if err != nil {
log.Printf("Getting MTLS config failed: %v", err)
}
})
}
var httpGetMetadataMTLSConfig = func() (string, error) {
return metadata.GetWithContext(context.Background(), configEndpointSuffix)
var httpGetMetadataMTLSConfig = func(logger *slog.Logger) (string, error) {
metadataClient := metadata.NewWithOptions(&metadata.Options{
Logger: logger,
})
return metadataClient.GetWithContext(context.Background(), configEndpointSuffix)
}
func queryConfig() (*mtlsConfig, error) {
resp, err := httpGetMetadataMTLSConfig()
func queryConfig(logger *slog.Logger) (*mtlsConfig, error) {
resp, err := httpGetMetadataMTLSConfig(logger)
if err != nil {
return nil, fmt.Errorf("querying MTLS config from MDS endpoint failed: %w", err)
}

View file

@ -49,6 +49,7 @@ func CloneDetectOptions(oldDo *credentials.DetectOptions) *credentials.DetectOpt
// These fields are are pointer types that we just want to use exactly
// as the user set, copy the ref
Client: oldDo.Client,
Logger: oldDo.Logger,
AuthHandlerOptions: oldDo.AuthHandlerOptions,
}

View file

@ -1,5 +1,19 @@
# Changelog
## [0.2.6](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.5...auth/oauth2adapt/v0.2.6) (2024-11-21)
### Bug Fixes
* **auth/oauth2adapt:** Copy map in tokenSourceAdapter.Token ([#11164](https://github.com/googleapis/google-cloud-go/issues/11164)) ([8cb0cbc](https://github.com/googleapis/google-cloud-go/commit/8cb0cbccdc32886dfb3af49fee04012937d114d2)), refs [#11161](https://github.com/googleapis/google-cloud-go/issues/11161)
## [0.2.5](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.4...auth/oauth2adapt/v0.2.5) (2024-10-30)
### Bug Fixes
* **auth/oauth2adapt:** Convert token metadata where possible ([#11062](https://github.com/googleapis/google-cloud-go/issues/11062)) ([34bf1c1](https://github.com/googleapis/google-cloud-go/commit/34bf1c164465d66745c0cfdf7cd10a8e2da92e52))
## [0.2.4](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.3...auth/oauth2adapt/v0.2.4) (2024-08-08)

View file

@ -26,6 +26,13 @@ import (
"golang.org/x/oauth2/google"
)
const (
oauth2TokenSourceKey = "oauth2.google.tokenSource"
oauth2ServiceAccountKey = "oauth2.google.serviceAccount"
authTokenSourceKey = "auth.google.tokenSource"
authServiceAccountKey = "auth.google.serviceAccount"
)
// TokenProviderFromTokenSource converts any [golang.org/x/oauth2.TokenSource]
// into a [cloud.google.com/go/auth.TokenProvider].
func TokenProviderFromTokenSource(ts oauth2.TokenSource) auth.TokenProvider {
@ -47,10 +54,21 @@ func (tp *tokenProviderAdapter) Token(context.Context) (*auth.Token, error) {
}
return nil, err
}
// Preserve compute token metadata, for both types of tokens.
metadata := map[string]interface{}{}
if val, ok := tok.Extra(oauth2TokenSourceKey).(string); ok {
metadata[authTokenSourceKey] = val
metadata[oauth2TokenSourceKey] = val
}
if val, ok := tok.Extra(oauth2ServiceAccountKey).(string); ok {
metadata[authServiceAccountKey] = val
metadata[oauth2ServiceAccountKey] = val
}
return &auth.Token{
Value: tok.AccessToken,
Type: tok.Type(),
Expiry: tok.Expiry,
Value: tok.AccessToken,
Type: tok.Type(),
Expiry: tok.Expiry,
Metadata: metadata,
}, nil
}
@ -76,11 +94,29 @@ func (ts *tokenSourceAdapter) Token() (*oauth2.Token, error) {
}
return nil, err
}
return &oauth2.Token{
tok2 := &oauth2.Token{
AccessToken: tok.Value,
TokenType: tok.Type,
Expiry: tok.Expiry,
}, nil
}
// Preserve token metadata.
m := tok.Metadata
if m != nil {
// Copy map to avoid concurrent map writes error (#11161).
metadata := make(map[string]interface{}, len(m)+2)
for k, v := range m {
metadata[k] = v
}
// Append compute token metadata in converted form.
if val, ok := metadata[authTokenSourceKey].(string); ok && val != "" {
metadata[oauth2TokenSourceKey] = val
}
if val, ok := metadata[authServiceAccountKey].(string); ok && val != "" {
metadata[oauth2ServiceAccountKey] = val
}
tok2 = tok2.WithExtra(metadata)
}
return tok2, nil
}
// AuthCredentialsFromOauth2Credentials converts a [golang.org/x/oauth2/google.Credentials]

View file

@ -20,6 +20,7 @@ import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"mime"
"net/http"
"net/url"
@ -28,6 +29,7 @@ import (
"time"
"cloud.google.com/go/auth/internal"
"github.com/googleapis/gax-go/v2/internallog"
)
// AuthorizationHandler is a 3-legged-OAuth helper that prompts the user for
@ -69,6 +71,11 @@ type Options3LO struct {
// AuthHandlerOpts provides a set of options for doing a
// 3-legged OAuth2 flow with a custom [AuthorizationHandler]. Optional.
AuthHandlerOpts *AuthorizationHandlerOptions
// 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 *Options3LO) validate() error {
@ -96,6 +103,10 @@ func (o *Options3LO) validate() error {
return nil
}
func (o *Options3LO) logger() *slog.Logger {
return internallog.New(o.Logger)
}
// PKCEOptions holds parameters to support PKCE.
type PKCEOptions struct {
// Challenge is the un-padded, base64-url-encoded string of the encrypted code verifier.
@ -293,12 +304,15 @@ func fetchToken(ctx context.Context, o *Options3LO, v url.Values) (*Token, strin
if o.AuthStyle == StyleInHeader {
req.SetBasicAuth(url.QueryEscape(o.ClientID), url.QueryEscape(o.ClientSecret))
}
logger := o.logger()
logger.DebugContext(ctx, "3LO token request", "request", internallog.HTTPRequest(req, []byte(v.Encode())))
// Make request
resp, body, err := internal.DoRequest(o.client(), req)
if err != nil {
return nil, refreshToken, err
}
logger.DebugContext(ctx, "3LO token response", "response", internallog.HTTPResponse(resp, body))
failureStatus := resp.StatusCode < 200 || resp.StatusCode > 299
tokError := &Error{
Response: resp,