Skip to content

Commit

Permalink
feat(auth): add logging support (#11079)
Browse files Browse the repository at this point in the history
- add logging to all outgoing requests
- accept a logger from public constructors
- left a TODO to cleanup in a followup metadata package PR
  • Loading branch information
codyoss authored Dec 12, 2024
1 parent 416ed78 commit c80e31d
Show file tree
Hide file tree
Showing 30 changed files with 231 additions and 27 deletions.
12 changes: 11 additions & 1 deletion auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"net/url"
"strings"
Expand All @@ -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 (
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions auth/credentials/compute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions auth/credentials/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"os"
"time"
Expand All @@ -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 (
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions auth/credentials/downscope/downscope.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ import (
"context"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"net/url"
"strings"
"time"

"cloud.google.com/go/auth"
"cloud.google.com/go/auth/internal"
"github.com/googleapis/gax-go/v2/internallog"
)

const (
Expand All @@ -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 {
Expand Down Expand Up @@ -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),
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand Down
8 changes: 8 additions & 0 deletions auth/credentials/externalaccount/externalaccount.go
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions auth/credentials/filetypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand All @@ -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 {
Expand All @@ -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)
}
Expand All @@ -214,12 +218,14 @@ func handleImpersonatedServiceAccount(f *credsfile.ImpersonatedServiceAccountFil
Tp: tp,
Delegates: f.Delegates,
Client: opts.client(),
Logger: opts.logger(),
})
}

func handleGDCHServiceAccount(f *credsfile.GDCHServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
return gdch.NewTokenProvider(f, &gdch.Options{
STSAudience: opts.STSAudience,
Client: opts.client(),
Logger: opts.logger(),
})
}
12 changes: 9 additions & 3 deletions auth/credentials/idtoken/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ import (
"context"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"strconv"
"strings"
"sync"
"time"

"cloud.google.com/go/auth/internal"
"github.com/googleapis/gax-go/v2/internallog"
)

type cachingClient struct {
Expand All @@ -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,
}
}

Expand All @@ -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)
}
Expand Down
4 changes: 3 additions & 1 deletion auth/credentials/idtoken/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"sync"
"testing"
"time"

"github.com/googleapis/gax-go/v2/internallog"
)

type fakeClock struct {
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion auth/credentials/idtoken/compute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
4 changes: 3 additions & 1 deletion auth/credentials/idtoken/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions auth/credentials/idtoken/idtoken.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package idtoken

import (
"errors"
"log/slog"
"net/http"
"os"

Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit c80e31d

Please sign in to comment.