diff --git a/auth/auth.go b/auth/auth.go index a7fa84f6f951..cd5e9886848b 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -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 ( @@ -490,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 { @@ -520,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) { @@ -560,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, diff --git a/auth/credentials/compute.go b/auth/credentials/compute.go index 6f70fa353b00..61e4882f38ca 100644 --- a/auth/credentials/compute.go +++ b/auth/credentials/compute.go @@ -65,6 +65,7 @@ func (cs computeProvider) Token(ctx context.Context) (*auth.Token, error) { v.Set("scopes", strings.Join(cs.scopes, ",")) tokenURI.RawQuery = v.Encode() } + // TODO(codyoss): create a metadata client and plumb through logger tokenJSON, err := metadata.GetWithContext(ctx, tokenURI.String()) if err != nil { return nil, fmt.Errorf("credentials: cannot fetch token: %w", err) diff --git a/auth/credentials/detect.go b/auth/credentials/detect.go index 010afc37c8fe..fd0ca0d6ef8a 100644 --- a/auth/credentials/detect.go +++ b/auth/credentials/detect.go @@ -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 ( @@ -158,6 +160,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 +200,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 +264,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 diff --git a/auth/credentials/downscope/downscope.go b/auth/credentials/downscope/downscope.go index 60a104b52f67..aa7060bbc370 100644 --- a/auth/credentials/downscope/downscope.go +++ b/auth/credentials/downscope/downscope.go @@ -18,6 +18,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "net/http" "net/url" "strings" @@ -25,6 +26,7 @@ import ( "cloud.google.com/go/auth" "cloud.google.com/go/auth/internal" + "github.com/googleapis/gax-go/v2/internallog" ) const ( @@ -49,6 +51,11 @@ type Options struct { // UniverseDomain is the default service domain for a given Cloud universe. // The default value is "googleapis.com". 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 { @@ -128,6 +135,7 @@ func NewCredentials(opts *Options) (*auth.Credentials, error) { Options: opts, Client: opts.client(), identityBindingEndpoint: opts.identityBindingEndpoint(), + logger: internallog.New(opts.Logger), }, ProjectIDProvider: auth.CredentialsPropertyFunc(opts.Credentials.ProjectID), QuotaProjectIDProvider: auth.CredentialsPropertyFunc(opts.Credentials.QuotaProjectID), @@ -142,6 +150,7 @@ type downscopedTokenProvider struct { // identityBindingEndpoint is the identity binding endpoint with the // configured universe domain. identityBindingEndpoint string + logger *slog.Logger } type downscopedOptions struct { @@ -187,10 +196,12 @@ func (dts *downscopedTokenProvider) Token(ctx context.Context) (*auth.Token, err return nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + dts.logger.DebugContext(ctx, "downscoped token request", "request", internallog.HTTPRequest(req, []byte(form.Encode()))) resp, body, err := internal.DoRequest(dts.Client, req) if err != nil { return nil, err } + dts.logger.DebugContext(ctx, "downscoped token response", "response", internallog.HTTPResponse(resp, body)) if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("downscope: unable to exchange token, %v: %s", resp.StatusCode, body) } diff --git a/auth/credentials/externalaccount/externalaccount.go b/auth/credentials/externalaccount/externalaccount.go index 62c35bcd5e99..043487d7d2a6 100644 --- a/auth/credentials/externalaccount/externalaccount.go +++ b/auth/credentials/externalaccount/externalaccount.go @@ -17,12 +17,14 @@ package externalaccount import ( "context" "fmt" + "log/slog" "net/http" "cloud.google.com/go/auth" iexacc "cloud.google.com/go/auth/credentials/internal/externalaccount" "cloud.google.com/go/auth/internal" "cloud.google.com/go/auth/internal/credsfile" + "github.com/googleapis/gax-go/v2/internallog" ) // Options for creating a [cloud.google.com/go/auth.Credentials]. @@ -95,6 +97,11 @@ type Options struct { // Client configures the underlying client used to make network requests // when fetching tokens. Optional. 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 } // CredentialSource stores the information necessary to retrieve the credentials for the STS exchange. @@ -243,6 +250,7 @@ func (o *Options) toInternalOpts() *iexacc.Options { AwsSecurityCredentialsProvider: toInternalAwsSecurityCredentialsProvider(o.AwsSecurityCredentialsProvider), Client: o.client(), IsDefaultClient: o.Client == nil, + Logger: internallog.New(o.Logger), } if o.CredentialSource != nil { cs := o.CredentialSource diff --git a/auth/credentials/filetypes.go b/auth/credentials/filetypes.go index 6591b181132f..e5243e6cfbea 100644 --- a/auth/credentials/filetypes.go +++ b/auth/credentials/filetypes.go @@ -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(), }) } diff --git a/auth/credentials/idtoken/cache.go b/auth/credentials/idtoken/cache.go index e6f4ff811608..2fbbdb8072d7 100644 --- a/auth/credentials/idtoken/cache.go +++ b/auth/credentials/idtoken/cache.go @@ -18,6 +18,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "net/http" "strconv" "strings" @@ -25,6 +26,7 @@ import ( "time" "cloud.google.com/go/auth/internal" + "github.com/googleapis/gax-go/v2/internallog" ) type cachingClient struct { @@ -34,14 +36,16 @@ type cachingClient struct { // If nil, time.Now is used. clock func() time.Time - mu sync.Mutex - certs map[string]*cachedResponse + mu sync.Mutex + certs map[string]*cachedResponse + logger *slog.Logger } -func newCachingClient(client *http.Client) *cachingClient { +func newCachingClient(client *http.Client, logger *slog.Logger) *cachingClient { return &cachingClient{ client: client, certs: make(map[string]*cachedResponse, 2), + logger: logger, } } @@ -58,10 +62,12 @@ func (c *cachingClient) getCert(ctx context.Context, url string) (*certResponse, 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) } diff --git a/auth/credentials/idtoken/cache_test.go b/auth/credentials/idtoken/cache_test.go index adfb5d665662..158622266187 100644 --- a/auth/credentials/idtoken/cache_test.go +++ b/auth/credentials/idtoken/cache_test.go @@ -19,6 +19,8 @@ import ( "sync" "testing" "time" + + "github.com/googleapis/gax-go/v2/internallog" ) type fakeClock struct { @@ -47,7 +49,7 @@ func TestCacheHit(t *testing.T) { }, }, } - cache := newCachingClient(nil) + cache := newCachingClient(nil, internallog.New(nil)) cache.clock = clock.Now // Cache should be empty diff --git a/auth/credentials/idtoken/compute.go b/auth/credentials/idtoken/compute.go index dced1ec40442..33d7440582ec 100644 --- a/auth/credentials/idtoken/compute.go +++ b/auth/credentials/idtoken/compute.go @@ -37,7 +37,9 @@ func computeCredentials(opts *Options) (*auth.Credentials, error) { tp := computeIDTokenProvider{ audience: opts.Audience, format: opts.ComputeTokenFormat, - client: *metadata.NewClient(opts.client()), + // TODO(codyoss): connect logger here after metadata options are + // available. + client: *metadata.NewClient(opts.client()), } return auth.NewCredentials(&auth.CredentialsOptions{ TokenProvider: auth.NewCachedTokenProvider(tp, &auth.CachedTokenProviderOptions{ diff --git a/auth/credentials/idtoken/file.go b/auth/credentials/idtoken/file.go index c160c514339a..2cde8164d2b3 100644 --- a/auth/credentials/idtoken/file.go +++ b/auth/credentials/idtoken/file.go @@ -24,6 +24,7 @@ import ( "cloud.google.com/go/auth/credentials/impersonate" "cloud.google.com/go/auth/internal" "cloud.google.com/go/auth/internal/credsfile" + "github.com/googleapis/gax-go/v2/internallog" ) const ( @@ -49,6 +50,7 @@ func credsFromDefault(creds *auth.Credentials, opts *Options) (*auth.Credentials PrivateKeyID: f.PrivateKeyID, TokenURL: f.TokenURL, UseIDToken: true, + Logger: internallog.New(opts.Logger), } if opts2LO.TokenURL == "" { opts2LO.TokenURL = jwtTokenURL @@ -85,13 +87,13 @@ func credsFromDefault(creds *auth.Credentials, opts *Options) (*auth.Credentials } 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 { diff --git a/auth/credentials/idtoken/idtoken.go b/auth/credentials/idtoken/idtoken.go index 37947f90eb8d..2e9a5d3ede39 100644 --- a/auth/credentials/idtoken/idtoken.go +++ b/auth/credentials/idtoken/idtoken.go @@ -22,6 +22,7 @@ package idtoken import ( "errors" + "log/slog" "net/http" "os" @@ -85,6 +86,11 @@ type Options struct { // when fetching tokens. If provided this should be a fully-authenticated // client. Optional. 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) client() *http.Client { diff --git a/auth/credentials/idtoken/validate.go b/auth/credentials/idtoken/validate.go index 7abd1dab3a73..c8175a6a8e67 100644 --- a/auth/credentials/idtoken/validate.go +++ b/auth/credentials/idtoken/validate.go @@ -24,6 +24,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "log/slog" "math/big" "net/http" "strings" @@ -31,6 +32,7 @@ import ( "cloud.google.com/go/auth/internal" "cloud.google.com/go/auth/internal/jwt" + "github.com/googleapis/gax-go/v2/internallog" ) const ( @@ -42,7 +44,7 @@ const ( ) var ( - defaultValidator = &Validator{client: newCachingClient(internal.DefaultClient())} + defaultValidator = &Validator{client: newCachingClient(internal.DefaultClient(), internallog.New(nil))} // now aliases time.Now for testing. now = time.Now ) @@ -84,26 +86,27 @@ type ValidatorOptions struct { // 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) { - var client *http.Client - if opts != nil && opts.Client != nil { - client = opts.Client - } else { - client = internal.DefaultClient() - } - var rs256URL string - if opts != nil { - rs256URL = opts.RS256CertsURL + if opts == nil { + opts = &ValidatorOptions{} } - var es256URL string - if opts != nil { - es256URL = opts.ES256CertsURL + client := opts.Client + if client == nil { + client = internal.DefaultClient() } - return &Validator{client: newCachingClient(client), rs256URL: rs256URL, es256URL: es256URL}, nil + 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 diff --git a/auth/credentials/impersonate/idtoken.go b/auth/credentials/impersonate/idtoken.go index 9d44c91cb0d9..835b8f8d2ddc 100644 --- a/auth/credentials/impersonate/idtoken.go +++ b/auth/credentials/impersonate/idtoken.go @@ -20,6 +20,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "net/http" "time" @@ -27,6 +28,7 @@ import ( "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" ) // IDTokenOptions for generating an impersonated ID token. @@ -55,6 +57,11 @@ type IDTokenOptions struct { // when fetching tokens. If provided this should be a fully-authenticated // client. Optional. 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 *IDTokenOptions) validate() error { @@ -86,6 +93,7 @@ func NewIDTokenCredentials(opts *IDTokenOptions) (*auth.Credentials, error) { client := opts.Client creds := opts.Credentials + logger := internallog.New(opts.Logger) if client == nil { var err error if creds == nil { @@ -93,6 +101,7 @@ func NewIDTokenCredentials(opts *IDTokenOptions) (*auth.Credentials, error) { creds, err = credentials.DetectDefault(&credentials.DetectOptions{ Scopes: []string{defaultScope}, UseSelfSignedJWT: true, + Logger: logger, }) if err != nil { return nil, err @@ -100,6 +109,7 @@ func NewIDTokenCredentials(opts *IDTokenOptions) (*auth.Credentials, error) { } client, err = httptransport.NewClient(&httptransport.Options{ Credentials: creds, + Logger: logger, }) if err != nil { return nil, err @@ -111,6 +121,7 @@ func NewIDTokenCredentials(opts *IDTokenOptions) (*auth.Credentials, error) { targetPrincipal: opts.TargetPrincipal, audience: opts.Audience, includeEmail: opts.IncludeEmail, + logger: logger, } for _, v := range opts.Delegates { itp.delegates = append(itp.delegates, formatIAMServiceAccountName(v)) @@ -138,6 +149,7 @@ type generateIDTokenResponse struct { type impersonatedIDTokenProvider struct { client *http.Client + logger *slog.Logger targetPrincipal string audience string @@ -162,10 +174,12 @@ func (i impersonatedIDTokenProvider) Token(ctx context.Context) (*auth.Token, er return nil, fmt.Errorf("impersonate: unable to create request: %w", err) } req.Header.Set("Content-Type", "application/json") + i.logger.DebugContext(ctx, "impersonated idtoken request", "request", internallog.HTTPRequest(req, bodyBytes)) resp, body, err := internal.DoRequest(i.client, req) if err != nil { return nil, fmt.Errorf("impersonate: unable to generate ID token: %w", err) } + i.logger.DebugContext(ctx, "impersonated idtoken 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) } diff --git a/auth/credentials/impersonate/impersonate.go b/auth/credentials/impersonate/impersonate.go index 04ccaaf0ea5b..715b6b569d88 100644 --- a/auth/credentials/impersonate/impersonate.go +++ b/auth/credentials/impersonate/impersonate.go @@ -20,6 +20,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "net/http" "strings" "time" @@ -28,6 +29,7 @@ import ( "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 ( @@ -65,19 +67,21 @@ func NewCredentials(opts *CredentialsOptions) (*auth.Credentials, error) { 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)) + client, err = httptransport.NewClient(transportOpts(opts, creds, logger)) if err != nil { return nil, err } @@ -102,6 +106,7 @@ func NewCredentials(opts *CredentialsOptions) (*auth.Credentials, error) { targetPrincipal: opts.TargetPrincipal, lifetime: fmt.Sprintf("%.fs", lifetime.Seconds()), universeDomainProvider: universeDomainProvider, + logger: logger, } for _, v := range opts.Delegates { its.delegates = append(its.delegates, formatIAMServiceAccountName(v)) @@ -126,9 +131,10 @@ func NewCredentials(opts *CredentialsOptions) (*auth.Credentials, error) { // 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) *httptransport.Options { +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{ @@ -186,6 +192,11 @@ type CredentialsOptions struct { // 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 { @@ -222,6 +233,7 @@ type generateAccessTokenResponse struct { type impersonatedTokenProvider struct { client *http.Client universeDomainProvider auth.CredentialsPropertyProvider + logger *slog.Logger targetPrincipal string lifetime string @@ -251,10 +263,12 @@ func (i impersonatedTokenProvider) Token(ctx context.Context) (*auth.Token, erro 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) } diff --git a/auth/credentials/impersonate/user.go b/auth/credentials/impersonate/user.go index b5e5fc8f6645..be21d220768e 100644 --- a/auth/credentials/impersonate/user.go +++ b/auth/credentials/impersonate/user.go @@ -20,6 +20,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "net/http" "net/url" "strings" @@ -27,6 +28,7 @@ import ( "cloud.google.com/go/auth" "cloud.google.com/go/auth/internal" + "github.com/googleapis/gax-go/v2/internallog" ) // user provides an auth flow for domain-wide delegation, setting @@ -41,6 +43,7 @@ func user(opts *CredentialsOptions, client *http.Client, lifetime time.Duration, 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 { @@ -88,6 +91,7 @@ type exchangeTokenResponse struct { type userTokenProvider struct { client *http.Client + logger *slog.Logger targetPrincipal string subject string @@ -145,10 +149,12 @@ func (u userTokenProvider) signJWT(ctx context.Context) (string, error) { 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) } @@ -169,10 +175,12 @@ func (u userTokenProvider) exchangeToken(ctx context.Context, signedJWT string) if err != nil { return nil, err } + 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) } diff --git a/auth/credentials/internal/externalaccount/aws_provider.go b/auth/credentials/internal/externalaccount/aws_provider.go index d8b5d4fdeb9e..9ecd1f64bd5d 100644 --- a/auth/credentials/internal/externalaccount/aws_provider.go +++ b/auth/credentials/internal/externalaccount/aws_provider.go @@ -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) } diff --git a/auth/credentials/internal/externalaccount/externalaccount.go b/auth/credentials/internal/externalaccount/externalaccount.go index 112186a9e6ef..a82206423486 100644 --- a/auth/credentials/internal/externalaccount/externalaccount.go +++ b/auth/credentials/internal/externalaccount/externalaccount.go @@ -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 == "" { diff --git a/auth/credentials/internal/externalaccount/url_provider.go b/auth/credentials/internal/externalaccount/url_provider.go index 0a020599e07f..754ecf4fef9b 100644 --- a/auth/credentials/internal/externalaccount/url_provider.go +++ b/auth/credentials/internal/externalaccount/url_provider.go @@ -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) } diff --git a/auth/credentials/internal/externalaccountuser/externalaccountuser.go b/auth/credentials/internal/externalaccountuser/externalaccountuser.go index 0d7885479872..ae39206e5f33 100644 --- a/auth/credentials/internal/externalaccountuser/externalaccountuser.go +++ b/auth/credentials/internal/externalaccountuser/externalaccountuser.go @@ -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 diff --git a/auth/credentials/internal/gdch/gdch.go b/auth/credentials/internal/gdch/gdch.go index f9d494d8da66..c2d320fdf4c7 100644 --- a/auth/credentials/internal/gdch/gdch.go +++ b/auth/credentials/internal/gdch/gdch.go @@ -22,6 +22,7 @@ import ( "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 @@ -79,6 +82,7 @@ func NewTokenProvider(f *credsfile.GDCHServiceAccountFile, o *Options) (auth.Tok pkID: f.PrivateKeyID, certPool: certPool, client: o.Client, + logger: internallog.New(o.Logger), } return tp, nil } @@ -102,6 +106,7 @@ type gdchProvider struct { certPool *x509.CertPool client *http.Client + logger *slog.Logger } func (g gdchProvider) Token(ctx context.Context) (*auth.Token, error) { @@ -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, diff --git a/auth/credentials/internal/impersonate/impersonate.go b/auth/credentials/internal/impersonate/impersonate.go index ed53afa519e7..b3a99261fa90 100644 --- a/auth/credentials/internal/impersonate/impersonate.go +++ b/auth/credentials/internal/impersonate/impersonate.go @@ -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) } diff --git a/auth/credentials/internal/stsexchange/sts_exchange.go b/auth/credentials/internal/stsexchange/sts_exchange.go index 768a9dafc13a..e1d2b15034d5 100644 --- a/auth/credentials/internal/stsexchange/sts_exchange.go +++ b/auth/credentials/internal/stsexchange/sts_exchange.go @@ -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) } diff --git a/auth/credentials/selfsignedjwt.go b/auth/credentials/selfsignedjwt.go index 0dc2ebb39cf9..8d335ccecc9a 100644 --- a/auth/credentials/selfsignedjwt.go +++ b/auth/credentials/selfsignedjwt.go @@ -19,6 +19,7 @@ import ( "crypto" "errors" "fmt" + "log/slog" "strings" "time" @@ -49,6 +50,7 @@ func configureSelfSignedJWT(f *credsfile.ServiceAccountFile, opts *DetectOptions scopes: opts.scopes(), signer: signer, pkID: f.PrivateKeyID, + logger: opts.logger(), }, nil } @@ -58,6 +60,7 @@ type selfSignedTokenProvider struct { scopes []string 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.signer) + 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 } diff --git a/auth/go.mod b/auth/go.mod index 0001933cb615..516d19529886 100644 --- a/auth/go.mod +++ b/auth/go.mod @@ -7,6 +7,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/s2a-go v0.1.8 github.com/googleapis/enterprise-certificate-proxy v0.3.4 + github.com/googleapis/gax-go/v2 v2.14.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 golang.org/x/net v0.32.0 diff --git a/auth/go.sum b/auth/go.sum index c1ba1bfe84d3..796ef2861523 100644 --- a/auth/go.sum +++ b/auth/go.sum @@ -15,6 +15,8 @@ github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= +github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= diff --git a/auth/grpctransport/grpctransport.go b/auth/grpctransport/grpctransport.go index fb82686e57d5..1c6c71e92f33 100644 --- a/auth/grpctransport/grpctransport.go +++ b/auth/grpctransport/grpctransport.go @@ -21,6 +21,7 @@ import ( "crypto/tls" "errors" "fmt" + "log/slog" "net/http" "os" "sync" @@ -116,6 +117,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. diff --git a/auth/httptransport/httptransport.go b/auth/httptransport/httptransport.go index cbe5a7a40a77..6fb7152f4105 100644 --- a/auth/httptransport/httptransport.go +++ b/auth/httptransport/httptransport.go @@ -20,6 +20,7 @@ import ( "crypto/tls" "errors" "fmt" + "log/slog" "net/http" "cloud.google.com/go/auth" @@ -69,6 +70,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. diff --git a/auth/internal/transport/transport.go b/auth/internal/transport/transport.go index cc586ec5b1a5..992ac40df0b7 100644 --- a/auth/internal/transport/transport.go +++ b/auth/internal/transport/transport.go @@ -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, } diff --git a/auth/internal/transport/transport_test.go b/auth/internal/transport/transport_test.go index 69b382c2b734..fa1abd3b43b7 100644 --- a/auth/internal/transport/transport_test.go +++ b/auth/internal/transport/transport_test.go @@ -29,7 +29,7 @@ import ( // future. To make the test pass simply bump the int, but please also clone the // relevant fields. func TestCloneDetectOptions_FieldTest(t *testing.T) { - const WantNumberOfFields = 13 + const WantNumberOfFields = 14 o := credentials.DetectOptions{} got := reflect.TypeOf(o).NumField() if got != WantNumberOfFields { diff --git a/auth/threelegged.go b/auth/threelegged.go index 97a57f4694b0..07804dc162d2 100644 --- a/auth/threelegged.go +++ b/auth/threelegged.go @@ -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,