Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): add logging support #11079

Merged
merged 4 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading