From 69cb240c530b1f7173a9af2555c19e9a1beb56c5 Mon Sep 17 00:00:00 2001 From: Cody Oss <6331106+codyoss@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:26:16 -0500 Subject: [PATCH] feat(auth): refactor public sigs to use Credentials (#9603) This refactors all public surfaces to use Credentials rather than TokenProvider as the main abstraction. Part 2 of 3 of breaking changes for the auth module. --- auth/credentials/doc.go | 4 +- auth/credentials/example_test.go | 6 +-- auth/downscope/doc.go | 2 +- auth/downscope/downscope.go | 37 ++++++++------ auth/downscope/downscope_test.go | 30 ++++++----- auth/downscope/example_test.go | 6 +-- auth/downscope/integration_test.go | 8 +-- auth/example_test.go | 4 +- auth/grpctransport/dial_socketopt_test.go | 9 ++-- auth/grpctransport/directpath.go | 4 +- auth/grpctransport/grpctransport.go | 37 +++++++------- auth/grpctransport/grpctransport_test.go | 4 +- auth/httptransport/httptransport.go | 18 +++---- auth/httptransport/httptransport_test.go | 20 +++++--- auth/httptransport/transport.go | 9 ++-- auth/idtoken/compute.go | 16 ++++-- auth/idtoken/compute_test.go | 18 +++---- auth/idtoken/examples_test.go | 6 +-- auth/idtoken/file.go | 27 ++++++++-- auth/idtoken/idtoken.go | 10 ++-- auth/idtoken/idtoken_test.go | 12 ++--- auth/idtoken/integration_test.go | 12 ++--- auth/impersonate/doc.go | 4 +- auth/impersonate/example_test.go | 22 ++++---- auth/impersonate/idtoken.go | 45 ++++++++++------ auth/impersonate/idtoken_test.go | 6 +-- auth/impersonate/impersonate.go | 62 ++++++++++++++++------- auth/impersonate/impersonate_test.go | 4 +- auth/impersonate/integration_test.go | 27 +++++----- auth/impersonate/user.go | 2 +- auth/impersonate/user_test.go | 4 +- auth/internal/testutil/testdns/dns.go | 4 +- auth/internal/testutil/testgcs/storage.go | 4 +- 33 files changed, 286 insertions(+), 197 deletions(-) diff --git a/auth/credentials/doc.go b/auth/credentials/doc.go index 4dcc74f48482..f36bebc34498 100644 --- a/auth/credentials/doc.go +++ b/auth/credentials/doc.go @@ -74,8 +74,8 @@ // // # Credentials // -// The [Credentials] type represents Google credentials, including Application -// Default Credentials. +// The [cloud.google.com/go/auth.Credentials] type represents Google +// credentials, including Application Default Credentials. // // Use [DetectDefault] to obtain Application Default Credentials. // diff --git a/auth/credentials/example_test.go b/auth/credentials/example_test.go index 35564c723fb3..897c39641800 100644 --- a/auth/credentials/example_test.go +++ b/auth/credentials/example_test.go @@ -30,7 +30,7 @@ func ExampleDetectDefault() { log.Fatal(err) } client, err := httptransport.NewClient(&httptransport.Options{ - TokenProvider: creds, + Credentials: creds, }) if err != nil { log.Fatal(err) @@ -55,7 +55,7 @@ func ExampleDetectDefault_withFilepath() { log.Fatal(err) } client, err := httptransport.NewClient(&httptransport.Options{ - TokenProvider: creds, + Credentials: creds, }) if err != nil { log.Fatal(err) @@ -76,7 +76,7 @@ func ExampleDetectDefault_withJSON() { log.Fatal(err) } client, err := httptransport.NewClient(&httptransport.Options{ - TokenProvider: creds, + Credentials: creds, }) if err != nil { log.Fatal(err) diff --git a/auth/downscope/doc.go b/auth/downscope/doc.go index 6c268bb3db45..a2143d120697 100644 --- a/auth/downscope/doc.go +++ b/auth/downscope/doc.go @@ -33,7 +33,7 @@ // For example, a token broker can be set up on a server in a private network. // Various workloads (token consumers) in the same network will send // authenticated requests to that broker for downscoped tokens to access or -// modify specific google cloud storage buckets. See the NewTokenProvider example +// modify specific google cloud storage buckets. See the NewCredentials example // for an example of how a token broker would use this package. // // The broker will use the functionality in this package to generate a diff --git a/auth/downscope/downscope.go b/auth/downscope/downscope.go index 26f59256f98e..51b5bafc7e32 100644 --- a/auth/downscope/downscope.go +++ b/auth/downscope/downscope.go @@ -28,17 +28,16 @@ import ( var identityBindingEndpoint = "https://sts.googleapis.com/v1/token" -// Options for configuring [NewTokenProvider]. +// Options for configuring [NewCredentials]. type Options struct { - // BaseProvider is the [cloud.google.com/go/auth.TokenProvider] used to - // create the downscoped provider. The downscoped provider therefore has - // some subset of the accesses of the original BaseProvider. Required. - BaseProvider auth.TokenProvider - // Rules defines the accesses held by the new downscoped provider. One or + // Credentials is the [cloud.google.com/go/auth.Credentials] used to + // create the downscoped credentials. Required. + Credentials *auth.Credentials + // Rules defines the accesses held by the new downscoped credentials. One or // more AccessBoundaryRules are required to define permissions for the new - // downscoped provider. Each one defines an access (or set of accesses) that - // the new provider has to a given resource. There can be a maximum of 10 - // AccessBoundaryRules. Required. + // downscoped credentials. Each one defines an access (or set of accesses) + //that the new credentials has to a given resource. There can be a maximum + // of 10 AccessBoundaryRules. Required. Rules []AccessBoundaryRule // Client configures the underlying client used to make network requests // when fetching tokens. Optional. @@ -84,14 +83,15 @@ type AvailabilityCondition struct { Description string `json:"description,omitempty"` } -// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider] that is -// more restrictive than [Options.BaseProvider] provided. -func NewTokenProvider(opts *Options) (auth.TokenProvider, error) { +// NewCredentials returns a [cloud.google.com/go/auth.Credentials] that is +// more restrictive than [Options.Credentials] provided. The new credentials +// will delegate to the base credentials for all non-token activity. +func NewCredentials(opts *Options) (*auth.Credentials, error) { if opts == nil { return nil, fmt.Errorf("downscope: providing opts is required") } - if opts.BaseProvider == nil { - return nil, fmt.Errorf("downscope: BaseProvider cannot be nil") + if opts.Credentials == nil { + return nil, fmt.Errorf("downscope: Credentials cannot be nil") } if len(opts.Rules) == 0 { return nil, fmt.Errorf("downscope: length of AccessBoundaryRules must be at least 1") @@ -107,7 +107,12 @@ func NewTokenProvider(opts *Options) (auth.TokenProvider, error) { return nil, fmt.Errorf("downscope: all rules must provide at least one permission") } } - return &downscopedTokenProvider{Options: opts, Client: opts.client()}, nil + return auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: &downscopedTokenProvider{Options: opts, Client: opts.client()}, + ProjectIDProvider: auth.CredentialsPropertyFunc(opts.Credentials.ProjectID), + QuotaProjectIDProvider: auth.CredentialsPropertyFunc(opts.Credentials.QuotaProjectID), + UniverseDomainProvider: auth.CredentialsPropertyFunc(opts.Credentials.UniverseDomain), + }), nil } // downscopedTokenProvider is used to retrieve a downscoped tokens. @@ -138,7 +143,7 @@ func (dts *downscopedTokenProvider) Token(ctx context.Context) (*auth.Token, err }, } - tok, err := dts.Options.BaseProvider.Token(ctx) + tok, err := dts.Options.Credentials.Token(ctx) if err != nil { return nil, fmt.Errorf("downscope: unable to obtain root token: %w", err) } diff --git a/auth/downscope/downscope_test.go b/auth/downscope/downscope_test.go index 495224c97181..0dbf442d3459 100644 --- a/auth/downscope/downscope_test.go +++ b/auth/downscope/downscope_test.go @@ -29,6 +29,12 @@ var ( standardRespBody = `{"access_token":"fake_token","expires_in":42,"token_type":"Bearer"}` ) +func staticCredentials(tok string) *auth.Credentials { + return auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: staticTokenProvider(tok), + }) +} + type staticTokenProvider string func (s staticTokenProvider) Token(context.Context) (*auth.Token, error) { @@ -58,8 +64,8 @@ func TestNewTokenProvider(t *testing.T) { oldEndpoint := identityBindingEndpoint identityBindingEndpoint = ts.URL t.Cleanup(func() { identityBindingEndpoint = oldEndpoint }) - tp, err := NewTokenProvider(&Options{ - BaseProvider: staticTokenProvider("token_base"), + creds, err := NewCredentials(&Options{ + Credentials: staticCredentials("token_base"), Rules: []AccessBoundaryRule{ { AvailableResource: "test1", @@ -70,16 +76,16 @@ func TestNewTokenProvider(t *testing.T) { if err != nil { t.Fatalf("NewTokenProvider() = %v", err) } - tok, err := tp.Token(context.Background()) + tok, err := creds.Token(context.Background()) if err != nil { - t.Fatalf("NewDownscopedTokenSource failed with error: %v", err) + t.Fatalf("Token failed with error: %v", err) } if want := "fake_token"; tok.Value != want { t.Fatalf("got %v, want %v", tok.Value, want) } } -func TestTestNewTokenProvider_Validations(t *testing.T) { +func TestTestNewCredentials_Validations(t *testing.T) { tests := []struct { name string opts *Options @@ -95,27 +101,27 @@ func TestTestNewTokenProvider_Validations(t *testing.T) { { name: "no rules", opts: &Options{ - BaseProvider: staticTokenProvider("token_base"), + Credentials: staticCredentials("token_base"), }, }, { name: "too many rules", opts: &Options{ - BaseProvider: staticTokenProvider("token_base"), - Rules: []AccessBoundaryRule{{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}, + Credentials: staticCredentials("token_base"), + Rules: []AccessBoundaryRule{{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}, }, }, { name: "no resource", opts: &Options{ - BaseProvider: staticTokenProvider("token_base"), - Rules: []AccessBoundaryRule{{}}, + Credentials: staticCredentials("token_base"), + Rules: []AccessBoundaryRule{{}}, }, }, { name: "no perm", opts: &Options{ - BaseProvider: staticTokenProvider("token_base"), + Credentials: staticCredentials("token_base"), Rules: []AccessBoundaryRule{{ AvailableResource: "resource", }}, @@ -124,7 +130,7 @@ func TestTestNewTokenProvider_Validations(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - if _, err := NewTokenProvider(test.opts); err == nil { + if _, err := NewCredentials(test.opts); err == nil { t.Fatal("want non-nil err") } }) diff --git a/auth/downscope/example_test.go b/auth/downscope/example_test.go index 8a1567f96e43..6e65a8115522 100644 --- a/auth/downscope/example_test.go +++ b/auth/downscope/example_test.go @@ -22,7 +22,7 @@ import ( "cloud.google.com/go/auth/downscope" ) -func ExampleNewTokenProvider() { +func ExampleNewCredentials() { // This shows how to generate a downscoped token. This code would be run on // the token broker, which holds the root token used to generate the // downscoped token. @@ -43,13 +43,13 @@ func ExampleNewTokenProvider() { baseProvider, err := credentials.DetectDefault(&credentials.DetectOptions{ Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"}, }) - tp, err := downscope.NewTokenProvider(&downscope.Options{BaseProvider: baseProvider, Rules: accessBoundary}) + creds, err := downscope.NewCredentials(&downscope.Options{Credentials: baseProvider, Rules: accessBoundary}) if err != nil { fmt.Printf("failed to generate downscoped token provider: %v", err) return } - tok, err := tp.Token(ctx) + tok, err := creds.Token(ctx) if err != nil { fmt.Printf("failed to generate token: %v", err) return diff --git a/auth/downscope/integration_test.go b/auth/downscope/integration_test.go index e173282d17d9..921568853f98 100644 --- a/auth/downscope/integration_test.go +++ b/auth/downscope/integration_test.go @@ -91,17 +91,17 @@ func TestDownscopedToken(t *testing.T) { } } -func testDownscopedToken(t *testing.T, rule downscope.AccessBoundaryRule, objectName string, tp auth.TokenProvider) error { +func testDownscopedToken(t *testing.T, rule downscope.AccessBoundaryRule, objectName string, creds *auth.Credentials) error { t.Helper() ctx := context.Background() - tp, err := downscope.NewTokenProvider(&downscope.Options{BaseProvider: tp, Rules: []downscope.AccessBoundaryRule{rule}}) + creds, err := downscope.NewCredentials(&downscope.Options{Credentials: creds, Rules: []downscope.AccessBoundaryRule{rule}}) if err != nil { - return fmt.Errorf("downscope.NewTokenProvider() = %v", err) + return fmt.Errorf("downscope.NewCredentials() = %v", err) } ctx, cancel := context.WithTimeout(ctx, time.Second*30) defer cancel() - client := testgcs.NewClient(tp) + client := testgcs.NewClient(creds) resp, err := client.DownloadObject(ctx, bucket, objectName) if err != nil { return err diff --git a/auth/example_test.go b/auth/example_test.go index a1ad0c4dd988..718dab060608 100644 --- a/auth/example_test.go +++ b/auth/example_test.go @@ -53,7 +53,9 @@ func ExampleNew2LOTokenProvider() { log.Fatal(err) } client, err := httptransport.NewClient(&httptransport.Options{ - TokenProvider: tp, + Credentials: auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: tp, + }), }) if err != nil { log.Fatal(err) diff --git a/auth/grpctransport/dial_socketopt_test.go b/auth/grpctransport/dial_socketopt_test.go index 1004df18da3f..9ee7053dd592 100644 --- a/auth/grpctransport/dial_socketopt_test.go +++ b/auth/grpctransport/dial_socketopt_test.go @@ -26,6 +26,7 @@ import ( "testing" "time" + "cloud.google.com/go/auth" "google.golang.org/grpc" ) @@ -107,9 +108,11 @@ func TestDialWithDirectPathEnabled(t *testing.T) { }) pool, err := Dial(ctx, true, &Options{ - TokenProvider: staticTP("hey"), - GRPCDialOpts: []grpc.DialOption{userDialer}, - Endpoint: "example.google.com:443", + Credentials: auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: staticTP("hey"), + }), + GRPCDialOpts: []grpc.DialOption{userDialer}, + Endpoint: "example.google.com:443", InternalOptions: &InternalOptions{ EnableDirectPath: true, }, diff --git a/auth/grpctransport/directpath.go b/auth/grpctransport/directpath.go index b4bbdfc3f69e..8dbfa7ef7e90 100644 --- a/auth/grpctransport/directpath.go +++ b/auth/grpctransport/directpath.go @@ -90,11 +90,11 @@ func isDirectPathXdsUsed(o *Options) bool { // configureDirectPath returns some dial options and an endpoint to use if the // configuration allows the use of direct path. If it does not the provided // grpcOpts and endpoint are returned. -func configureDirectPath(grpcOpts []grpc.DialOption, opts *Options, endpoint string, creds auth.TokenProvider) ([]grpc.DialOption, string) { +func configureDirectPath(grpcOpts []grpc.DialOption, opts *Options, endpoint string, creds *auth.Credentials) ([]grpc.DialOption, string) { if isDirectPathEnabled(endpoint, opts) && metadata.OnGCE() && isTokenProviderDirectPathCompatible(creds, opts) { // Overwrite all of the previously specific DialOptions, DirectPath uses its own set of credentials and certificates. grpcOpts = []grpc.DialOption{ - grpc.WithCredentialsBundle(grpcgoogle.NewDefaultCredentialsWithOptions(grpcgoogle.DefaultCredentialsOptions{PerRPCCreds: &grpcTokenProvider{TokenProvider: creds}}))} + grpc.WithCredentialsBundle(grpcgoogle.NewDefaultCredentialsWithOptions(grpcgoogle.DefaultCredentialsOptions{PerRPCCreds: &grpcCredentialsProvider{creds: creds}}))} if timeoutDialerOption != nil { grpcOpts = append(grpcOpts, timeoutDialerOption) } diff --git a/auth/grpctransport/grpctransport.go b/auth/grpctransport/grpctransport.go index a8bc1ff5cbd2..8a1e31bce4ff 100644 --- a/auth/grpctransport/grpctransport.go +++ b/auth/grpctransport/grpctransport.go @@ -65,9 +65,9 @@ type Options struct { // PoolSize is specifies how many connections to balance between when making // requests. If unset or less than 1, the value defaults to 1. PoolSize int - // TokenProvider specifies the provider used to add Authorization metadata - // to all requests. If set DetectOpts are ignored. - TokenProvider auth.TokenProvider + // Credentials used to add Authorization metadata to all requests. If set + // DetectOpts are ignored. + Credentials *auth.Credentials // DetectOpts configures settings for detect Application Default // Credentials. DetectOpts *credentials.DetectOptions @@ -90,7 +90,7 @@ func (o *Options) validate() error { if o == nil { return errors.New("grpctransport: opts required to be non-nil") } - hasCreds := o.TokenProvider != nil || + hasCreds := o.Credentials != nil || (o.DetectOpts != nil && len(o.DetectOpts.CredentialsJSON) > 0) || (o.DetectOpts != nil && o.DetectOpts.CredentialsFile != "") if o.DisableAuthentication && hasCreds { @@ -204,9 +204,8 @@ func dial(ctx context.Context, secure bool, opts *Options) (*grpc.ClientConn, er if err != nil { return nil, err } - var tp auth.TokenProvider = creds - if opts.TokenProvider != nil { - tp = opts.TokenProvider + if opts.Credentials != nil { + creds = opts.Credentials } qp, err := creds.QuotaProjectID(ctx) @@ -221,9 +220,9 @@ func dial(ctx context.Context, secure bool, opts *Options) (*grpc.ClientConn, er } grpcOpts = append(grpcOpts, - grpc.WithPerRPCCredentials(&grpcTokenProvider{ - TokenProvider: tp, - metadata: metadata, + grpc.WithPerRPCCredentials(&grpcCredentialsProvider{ + creds: creds, + metadata: metadata, }), ) @@ -240,9 +239,9 @@ func dial(ctx context.Context, secure bool, opts *Options) (*grpc.ClientConn, er return grpc.DialContext(ctx, endpoint, grpcOpts...) } -// grpcTokenProvider satisfies https://pkg.go.dev/google.golang.org/grpc/credentials#PerRPCCredentials. -type grpcTokenProvider struct { - auth.TokenProvider +// grpcCredentialsProvider satisfies https://pkg.go.dev/google.golang.org/grpc/credentials#PerRPCCredentials. +type grpcCredentialsProvider struct { + creds *auth.Credentials secure bool @@ -250,27 +249,27 @@ type grpcTokenProvider struct { metadata map[string]string } -func (tp *grpcTokenProvider) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { - token, err := tp.Token(ctx) +func (c *grpcCredentialsProvider) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + token, err := c.creds.Token(ctx) if err != nil { return nil, err } - if tp.secure { + if c.secure { ri, _ := grpccreds.RequestInfoFromContext(ctx) if err = grpccreds.CheckSecurityLevel(ri.AuthInfo, grpccreds.PrivacyAndIntegrity); err != nil { - return nil, fmt.Errorf("unable to transfer TokenProvider PerRPCCredentials: %v", err) + return nil, fmt.Errorf("unable to transfer credentials PerRPCCredentials: %v", err) } } metadata := map[string]string{ "authorization": token.Type + " " + token.Value, } - for k, v := range tp.metadata { + for k, v := range c.metadata { metadata[k] = v } return metadata, nil } -func (tp *grpcTokenProvider) RequireTransportSecurity() bool { +func (tp *grpcCredentialsProvider) RequireTransportSecurity() bool { return tp.secure } diff --git a/auth/grpctransport/grpctransport_test.go b/auth/grpctransport/grpctransport_test.go index bcfa85d081d0..004d49b69bfe 100644 --- a/auth/grpctransport/grpctransport_test.go +++ b/auth/grpctransport/grpctransport_test.go @@ -81,7 +81,9 @@ func TestDial_FailsValidation(t *testing.T) { name: "has creds with disable options, tp", opts: &Options{ DisableAuthentication: true, - TokenProvider: staticTP("fakeToken"), + Credentials: auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: staticTP("fakeToken"), + }), }, }, { diff --git a/auth/httptransport/httptransport.go b/auth/httptransport/httptransport.go index f932b3152ffc..4794e0f87943 100644 --- a/auth/httptransport/httptransport.go +++ b/auth/httptransport/httptransport.go @@ -49,9 +49,9 @@ type Options struct { // APIKey specifies an API key to be used as the basis for authentication. // If set DetectOpts are ignored. APIKey string - // TokenProvider specifies the provider used to add Authorization header to - // all requests. If set DetectOpts are ignored. - TokenProvider auth.TokenProvider + // Credentials used to add Authorization header to all requests. If set + // DetectOpts are ignored. + Credentials *auth.Credentials // ClientCertProvider is a function that returns a TLS client certificate to // be used when opening TLS connections. It follows the same semantics as // crypto/tls.Config.GetClientCertificate. @@ -70,7 +70,7 @@ func (o *Options) validate() error { return errors.New("httptransport: opts required to be non-nil") } hasCreds := o.APIKey != "" || - o.TokenProvider != nil || + o.Credentials != nil || (o.DetectOpts != nil && len(o.DetectOpts.CredentialsJSON) > 0) || (o.DetectOpts != nil && o.DetectOpts.CredentialsFile != "") if o.DisableAuthentication && hasCreds { @@ -129,10 +129,10 @@ type InternalOptions struct { // 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.TokenProvider]. An error is returned only -// if client or tp is nil. -func AddAuthorizationMiddleware(client *http.Client, tp auth.TokenProvider) error { - if client == nil || tp == nil { +// provided [cloud.google.com/go/auth.Credentials]. An error is returned only +// if client or creds is nil. +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") } base := client.Transport @@ -140,7 +140,7 @@ func AddAuthorizationMiddleware(client *http.Client, tp auth.TokenProvider) erro base = http.DefaultTransport.(*http.Transport).Clone() } client.Transport = &authTransport{ - provider: auth.NewCachedTokenProvider(tp, nil), + provider: creds, base: base, } return nil diff --git a/auth/httptransport/httptransport_test.go b/auth/httptransport/httptransport_test.go index 614b81f00db6..63e8d2d37a43 100644 --- a/auth/httptransport/httptransport_test.go +++ b/auth/httptransport/httptransport_test.go @@ -28,11 +28,13 @@ import ( ) func TestAddAuthorizationMiddleware(t *testing.T) { - tp := staticTP("fakeToken") + creds := auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: staticTP("fakeToken"), + }) tests := []struct { name string client *http.Client - tp auth.TokenProvider + creds *auth.Credentials wantErr bool want string }{ @@ -42,30 +44,30 @@ func TestAddAuthorizationMiddleware(t *testing.T) { }, { name: "missing client field", - tp: tp, + creds: creds, wantErr: true, }, { - name: "missing TokenProvider field", + name: "missing creds field", client: internal.CloneDefaultClient(), wantErr: true, }, { name: "works", client: internal.CloneDefaultClient(), - tp: tp, + creds: creds, want: "fakeToken", }, { name: "works, no transport", client: &http.Client{}, - tp: tp, + creds: creds, want: "fakeToken", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := AddAuthorizationMiddleware(tt.client, tt.tp) + err := AddAuthorizationMiddleware(tt.client, tt.creds) if tt.wantErr && err == nil { t.Fatalf("AddAuthorizationMiddleware() = nil, want error") } @@ -97,7 +99,9 @@ func TestNewClient_FailsValidation(t *testing.T) { name: "has creds with disable options, tp", opts: &Options{ DisableAuthentication: true, - TokenProvider: staticTP("fakeToken"), + Credentials: auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: staticTP("fakeToken"), + }), }, }, { diff --git a/auth/httptransport/transport.go b/auth/httptransport/transport.go index ad4019153b29..673a3e51f896 100644 --- a/auth/httptransport/transport.go +++ b/auth/httptransport/transport.go @@ -72,13 +72,12 @@ func newTransport(base http.RoundTripper, opts *Options) (http.RoundTripper, err headers.Set(quotaProjectHeaderKey, qp) } - var tp auth.TokenProvider = creds - if opts.TokenProvider != nil { - tp = opts.TokenProvider + if opts.Credentials != nil { + creds = opts.Credentials } trans = &authTransport{ base: trans, - provider: auth.NewCachedTokenProvider(tp, nil), + provider: creds, } } return trans, nil @@ -162,7 +161,7 @@ func addOCTransport(trans http.RoundTripper, opts *Options) http.RoundTripper { } type authTransport struct { - provider auth.TokenProvider + provider *auth.Credentials base http.RoundTripper } diff --git a/auth/idtoken/compute.go b/auth/idtoken/compute.go index e9ebc1c8162a..4c14b7ce19c7 100644 --- a/auth/idtoken/compute.go +++ b/auth/idtoken/compute.go @@ -27,10 +27,10 @@ import ( const identitySuffix = "instance/service-accounts/default/identity" -// computeTokenProvider checks if this code is being run on GCE. If it is, it -// will use the metadata service to build a TokenProvider that fetches ID +// computeCredentials checks if this code is being run on GCE. If it is, it +// will use the metadata service to build a Credentials that fetches ID // tokens. -func computeTokenProvider(opts *Options) (auth.TokenProvider, error) { +func computeCredentials(opts *Options) (*auth.Credentials, error) { if opts.CustomClaims != nil { return nil, fmt.Errorf("idtoken: Options.CustomClaims can't be used with the metadata service, please provide a service account if you would like to use this feature") } @@ -39,8 +39,14 @@ func computeTokenProvider(opts *Options) (auth.TokenProvider, error) { format: opts.ComputeTokenFormat, client: *metadata.NewClient(opts.client()), } - return auth.NewCachedTokenProvider(tp, &auth.CachedTokenProviderOptions{ - ExpireEarly: 5 * time.Minute, + return auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: auth.NewCachedTokenProvider(tp, &auth.CachedTokenProviderOptions{ + ExpireEarly: 5 * time.Minute, + }), + ProjectIDProvider: auth.CredentialsPropertyFunc(func(context.Context) (string, error) { + return metadata.ProjectID() + }), + // TODO(quartzmo): add universe domain resolver here }), nil } diff --git a/auth/idtoken/compute_test.go b/auth/idtoken/compute_test.go index a0ff4fc8a1d9..c0c879c9b764 100644 --- a/auth/idtoken/compute_test.go +++ b/auth/idtoken/compute_test.go @@ -24,7 +24,7 @@ import ( const metadataHostEnv = "GCE_METADATA_HOST" -func TestComputeTokenSource(t *testing.T) { +func TestComputeCredentials(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.Contains(r.URL.Path, identitySuffix) { t.Errorf("got %q, want contains %q", r.URL.Path, identitySuffix) @@ -42,12 +42,12 @@ func TestComputeTokenSource(t *testing.T) { })) defer ts.Close() t.Setenv(metadataHostEnv, strings.TrimPrefix(ts.URL, "http://")) - tp, err := computeTokenProvider(&Options{ + tp, err := computeCredentials(&Options{ Audience: "aud", ComputeTokenFormat: ComputeTokenFormatFullWithLicense, }) if err != nil { - t.Fatalf("computeTokenProvider() = %v", err) + t.Fatalf("computeCredentials() = %v", err) } tok, err := tp.Token(context.Background()) if err != nil { @@ -58,7 +58,7 @@ func TestComputeTokenSource(t *testing.T) { } } -func TestComputeTokenSource_Standard(t *testing.T) { +func TestComputeCredentials_Standard(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.Contains(r.URL.Path, identitySuffix) { t.Errorf("got %q, want contains %q", r.URL.Path, identitySuffix) @@ -76,12 +76,12 @@ func TestComputeTokenSource_Standard(t *testing.T) { })) defer ts.Close() t.Setenv(metadataHostEnv, strings.TrimPrefix(ts.URL, "http://")) - tp, err := computeTokenProvider(&Options{ + tp, err := computeCredentials(&Options{ Audience: "aud", ComputeTokenFormat: ComputeTokenFormatStandard, }) if err != nil { - t.Fatalf("computeTokenProvider() = %v", err) + t.Fatalf("computeCredentials() = %v", err) } tok, err := tp.Token(context.Background()) if err != nil { @@ -92,11 +92,11 @@ func TestComputeTokenSource_Standard(t *testing.T) { } } -func TestComputeTokenSource_Invalid(t *testing.T) { - if _, err := computeTokenProvider(&Options{ +func TestComputeCredentials_Invalid(t *testing.T) { + if _, err := computeCredentials(&Options{ Audience: "aud", CustomClaims: map[string]interface{}{"foo": "bar"}, }); err == nil { - t.Fatal("computeTokenProvider() = nil, expected non-nil error", err) + t.Fatal("computeCredentials() = nil, expected non-nil error", err) } } diff --git a/auth/idtoken/examples_test.go b/auth/idtoken/examples_test.go index 2e605dc0aa00..e2a364a0b776 100644 --- a/auth/idtoken/examples_test.go +++ b/auth/idtoken/examples_test.go @@ -22,16 +22,16 @@ import ( "cloud.google.com/go/auth/idtoken" ) -func ExampleNewTokenProvider_setAuthorizationHeader() { +func ExampleNewCredentials_setAuthorizationHeader() { ctx := context.Background() audience := "http://example.com" - tp, err := idtoken.NewTokenProvider(&idtoken.Options{ + creds, err := idtoken.NewCredentials(&idtoken.Options{ Audience: audience, }) if err != nil { // Handle error. } - token, err := tp.Token(ctx) + token, err := creds.Token(ctx) if err != nil { // Handle error. } diff --git a/auth/idtoken/file.go b/auth/idtoken/file.go index acc563f75d1f..d0b3c3170622 100644 --- a/auth/idtoken/file.go +++ b/auth/idtoken/file.go @@ -23,6 +23,7 @@ import ( "cloud.google.com/go/auth" "cloud.google.com/go/auth/credentials" "cloud.google.com/go/auth/impersonate" + "cloud.google.com/go/auth/internal" "cloud.google.com/go/auth/internal/internaldetect" ) @@ -38,7 +39,7 @@ var ( } ) -func tokenProviderFromBytes(b []byte, opts *Options) (auth.TokenProvider, error) { +func credsFromBytes(b []byte, opts *Options) (*auth.Credentials, error) { t, err := internaldetect.ParseFileType(b) if err != nil { return nil, err @@ -74,7 +75,13 @@ func tokenProviderFromBytes(b []byte, opts *Options) (auth.TokenProvider, error) if err != nil { return nil, err } - return auth.NewCachedTokenProvider(tp, nil), nil + tp = auth.NewCachedTokenProvider(tp, nil) + return auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: tp, + JSON: b, + ProjectIDProvider: internal.StaticCredentialsProperty(f.ProjectID), + UniverseDomainProvider: internal.StaticCredentialsProperty(f.UniverseDomain), + }), nil case internaldetect.ImpersonatedServiceAccountKey, internaldetect.ExternalAccountKey: type url struct { ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"` @@ -86,7 +93,7 @@ func tokenProviderFromBytes(b []byte, opts *Options) (auth.TokenProvider, error) account := filepath.Base(accountURL.ServiceAccountImpersonationURL) account = strings.Split(account, ":")[0] - creds, err := credentials.DetectDefault(&credentials.DetectOptions{ + baseCreds, err := credentials.DetectDefault(&credentials.DetectOptions{ Scopes: defaultScopes, CredentialsJSON: b, Client: opts.client(), @@ -101,9 +108,19 @@ func tokenProviderFromBytes(b []byte, opts *Options) (auth.TokenProvider, error) TargetPrincipal: account, IncludeEmail: true, Client: opts.client(), - TokenProvider: creds, + Credentials: baseCreds, } - return impersonate.NewIDTokenProvider(&config) + creds, err := impersonate.NewIDTokenCredentials(&config) + if err != nil { + return nil, err + } + return auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: creds, + JSON: b, + ProjectIDProvider: auth.CredentialsPropertyFunc(baseCreds.ProjectID), + UniverseDomainProvider: auth.CredentialsPropertyFunc(baseCreds.UniverseDomain), + QuotaProjectIDProvider: auth.CredentialsPropertyFunc(baseCreds.QuotaProjectID), + }), nil default: return nil, fmt.Errorf("idtoken: unsupported credentials type: %v", t) } diff --git a/auth/idtoken/idtoken.go b/auth/idtoken/idtoken.go index ed081250b058..41dbf7b1a267 100644 --- a/auth/idtoken/idtoken.go +++ b/auth/idtoken/idtoken.go @@ -46,7 +46,7 @@ const ( ) // Options for the configuration of creation of an ID token with -// [NewTokenProvider]. +// [NewCredentials]. type Options struct { // Audience is the `aud` field for the token, such as an API endpoint the // token will grant access to. Required. @@ -87,18 +87,18 @@ func (o *Options) validate() error { return nil } -// NewTokenProvider creates a [cloud.google.com/go/auth.TokenProvider] that +// NewCredentials creates a [cloud.google.com/go/auth.Credentials] that // returns ID tokens configured by the opts provided. The parameter // opts.Audience may not be empty. -func NewTokenProvider(opts *Options) (auth.TokenProvider, error) { +func NewCredentials(opts *Options) (*auth.Credentials, error) { if err := opts.validate(); err != nil { return nil, err } if b := opts.jsonBytes(); b != nil { - return tokenProviderFromBytes(b, opts) + return credsFromBytes(b, opts) } if metadata.OnGCE() { - return computeTokenProvider(opts) + return computeCredentials(opts) } return nil, fmt.Errorf("idtoken: couldn't find any credentials") } diff --git a/auth/idtoken/idtoken_test.go b/auth/idtoken/idtoken_test.go index 2e2a6b12e086..01f14f244eb4 100644 --- a/auth/idtoken/idtoken_test.go +++ b/auth/idtoken/idtoken_test.go @@ -27,7 +27,7 @@ import ( "cloud.google.com/go/auth/internal/internaldetect" ) -func TestNewTokenProvider_ServiceAccount(t *testing.T) { +func TestNewCredentials_ServiceAccount(t *testing.T) { wantTok, _ := createRS256JWT(t) b, err := os.ReadFile("../internal/testdata/sa.json") if err != nil { @@ -48,7 +48,7 @@ func TestNewTokenProvider_ServiceAccount(t *testing.T) { t.Fatal(err) } - tp, err := NewTokenProvider(&Options{ + creds, err := NewCredentials(&Options{ Audience: "aud", CredentialsJSON: b, CustomClaims: map[string]interface{}{ @@ -58,7 +58,7 @@ func TestNewTokenProvider_ServiceAccount(t *testing.T) { if err != nil { t.Fatal(err) } - tok, err := tp.Token(context.Background()) + tok, err := creds.Token(context.Background()) if err != nil { t.Fatalf("tp.Token() = %v", err) } @@ -77,7 +77,7 @@ func (m mockTransport) RoundTrip(r *http.Request) (*http.Response, error) { return rw.Result(), nil } -func TestNewTokenProvider_ImpersonatedServiceAccount(t *testing.T) { +func TestNewCredentials_ImpersonatedServiceAccount(t *testing.T) { wantTok, _ := createRS256JWT(t) client := internal.CloneDefaultClient() client.Transport = mockTransport{ @@ -85,7 +85,7 @@ func TestNewTokenProvider_ImpersonatedServiceAccount(t *testing.T) { w.Write([]byte(fmt.Sprintf(`{"token": %q}`, wantTok))) }), } - tp, err := NewTokenProvider(&Options{ + creds, err := NewCredentials(&Options{ Audience: "aud", CredentialsFile: "../internal/testdata/imp.json", CustomClaims: map[string]interface{}{ @@ -96,7 +96,7 @@ func TestNewTokenProvider_ImpersonatedServiceAccount(t *testing.T) { if err != nil { t.Fatal(err) } - tok, err := tp.Token(context.Background()) + tok, err := creds.Token(context.Background()) if err != nil { t.Fatalf("tp.Token() = %v", err) } diff --git a/auth/idtoken/integration_test.go b/auth/idtoken/integration_test.go index 92dd451c2a57..ddfd6b5e99a7 100644 --- a/auth/idtoken/integration_test.go +++ b/auth/idtoken/integration_test.go @@ -32,15 +32,15 @@ const ( aud = "http://example.com" ) -func TestNewTokenProvider_CredentialsFile(t *testing.T) { +func TestNewCredentials_CredentialsFile(t *testing.T) { testutil.IntegrationTestCheck(t) ctx := context.Background() - ts, err := idtoken.NewTokenProvider(&idtoken.Options{ + ts, err := idtoken.NewCredentials(&idtoken.Options{ Audience: "http://example.com", CredentialsFile: os.Getenv(envCredentialFile), }) if err != nil { - t.Fatalf("unable to create TokenSource: %v", err) + t.Fatalf("unable to create credentials: %v", err) } tok, err := ts.Token(ctx) if err != nil { @@ -60,21 +60,21 @@ func TestNewTokenProvider_CredentialsFile(t *testing.T) { } } -func TestNewTokenProvider_CredentialsJSON(t *testing.T) { +func TestNewCredentials_CredentialsJSON(t *testing.T) { testutil.IntegrationTestCheck(t) ctx := context.Background() b, err := os.ReadFile(os.Getenv(envCredentialFile)) if err != nil { log.Fatal(err) } - tp, err := idtoken.NewTokenProvider(&idtoken.Options{ + creds, err := idtoken.NewCredentials(&idtoken.Options{ Audience: aud, CredentialsJSON: b, }) if err != nil { t.Fatalf("unable to create Client: %v", err) } - tok, err := tp.Token(ctx) + tok, err := creds.Token(ctx) if err != nil { t.Fatalf("unable to retrieve Token: %v", err) } diff --git a/auth/impersonate/doc.go b/auth/impersonate/doc.go index 3e036179c311..0dc45f13fb78 100644 --- a/auth/impersonate/doc.go +++ b/auth/impersonate/doc.go @@ -14,8 +14,8 @@ // Package impersonate is used to impersonate Google Credentials. If you need // to impersonate some credentials to use with a client library see -// [NewCredentialTokenProvider]. If instead you would like to create an Open -// Connect ID token using impersonation see [NewIDTokenProvider]. +// [NewCredentials]. If instead you would like to create an Open +// Connect ID token using impersonation see [NewIDTokenCredentials]. // // # Required IAM roles // diff --git a/auth/impersonate/example_test.go b/auth/impersonate/example_test.go index e7cd0e7c3827..910e34b471cc 100644 --- a/auth/impersonate/example_test.go +++ b/auth/impersonate/example_test.go @@ -21,9 +21,9 @@ import ( "cloud.google.com/go/auth/impersonate" ) -func ExampleNewCredentialTokenProvider_serviceAccount() { +func ExampleNewCredentials_serviceAccount() { // Base credentials sourced from ADC or provided client options - tp, err := impersonate.NewCredentialTokenProvider(&impersonate.CredentialOptions{ + creds, err := impersonate.NewCredentials(&impersonate.CredentialsOptions{ TargetPrincipal: "foo@project-id.iam.gserviceaccount.com", Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"}, // Optionally supply delegates @@ -35,13 +35,13 @@ func ExampleNewCredentialTokenProvider_serviceAccount() { // TODO(codyoss): link to option once it exists. - // Use this TokenProvider with a client library - _ = tp + // Use this Credentials with a client library + _ = creds } -func ExampleNewCredentialTokenProvider_adminUser() { +func ExampleNewCredentials_adminUser() { // Base credentials sourced from ADC or provided client options - tp, err := impersonate.NewCredentialTokenProvider(&impersonate.CredentialOptions{ + creds, err := impersonate.NewCredentials(&impersonate.CredentialsOptions{ TargetPrincipal: "foo@project-id.iam.gserviceaccount.com", Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"}, // Optionally supply delegates @@ -53,14 +53,14 @@ func ExampleNewCredentialTokenProvider_adminUser() { log.Fatal(err) } - // Use this TokenProvider with a client library like + // Use this Credentials with a client library like // "google.golang.org/api/admin/directory/v1" - _ = tp + _ = creds } -func ExampleNewIDTokenProvider() { +func ExampleNewIDTokenCredentials() { // Base credentials sourced from ADC or provided client options. - tp, err := impersonate.NewIDTokenProvider(&impersonate.IDTokenOptions{ + creds, err := impersonate.NewIDTokenCredentials(&impersonate.IDTokenOptions{ Audience: "http://example.com/", TargetPrincipal: "foo@project-id.iam.gserviceaccount.com", IncludeEmail: true, @@ -73,7 +73,7 @@ func ExampleNewIDTokenProvider() { // Create an authenticated client client, err := httptransport.NewClient(&httptransport.Options{ - TokenProvider: tp, + Credentials: creds, }) if err != nil { log.Fatal(err) diff --git a/auth/impersonate/idtoken.go b/auth/impersonate/idtoken.go index 6696f5c0a743..95a4c492ebf6 100644 --- a/auth/impersonate/idtoken.go +++ b/auth/impersonate/idtoken.go @@ -24,6 +24,7 @@ import ( "time" "cloud.google.com/go/auth" + "cloud.google.com/go/auth/credentials" "cloud.google.com/go/auth/httptransport" "cloud.google.com/go/auth/internal" ) @@ -46,10 +47,10 @@ type IDTokenOptions struct { // chain. Optional. Delegates []string - // TokenProvider is the provider of the credentials used to fetch the ID - // token. If not provided, and a Client is also not provided, base - // credentials will try to be detected from the environment. Optional. - TokenProvider auth.TokenProvider + // Credentials used to fetch the ID token. If not provided, and a Client is + // also not provided, base credentials will try to be detected from the + // environment. Optional. + Credentials *auth.Credentials // Client configures the underlying client used to make network requests // when fetching tokens. If provided the client should provide it's own // base credentials at call time. Optional. @@ -70,34 +71,40 @@ func (o *IDTokenOptions) validate() error { } var ( - defaultAud = "https://iamcredentials.googleapis.com/" defaultScope = "https://www.googleapis.com/auth/cloud-platform" ) -// NewIDTokenProvider creates an impersonated -// [cloud.google.com/go/auth/TokenProvider] that returns ID tokens configured +// NewIDTokenCredentials creates an impersonated +// [cloud.google.com/go/auth/Credentials] that returns ID tokens configured // with the provided config and using credentials loaded from Application // Default Credentials as the base credentials if not provided with the opts. // The tokens produced are valid for one hour and are automatically refreshed. -func NewIDTokenProvider(opts *IDTokenOptions) (auth.TokenProvider, error) { +func NewIDTokenCredentials(opts *IDTokenOptions) (*auth.Credentials, error) { if err := opts.validate(); err != nil { return nil, err } var client *http.Client - if opts.Client == nil && opts.TokenProvider == nil { + var creds *auth.Credentials + if opts.Client == nil && opts.Credentials == nil { var err error + // TODO: test not signed jwt more + creds, err = credentials.DetectDefault(&credentials.DetectOptions{ + Scopes: []string{defaultScope}, + UseSelfSignedJWT: true, + }) + if err != nil { + return nil, err + } client, err = httptransport.NewClient(&httptransport.Options{ - InternalOptions: &httptransport.InternalOptions{ - DefaultAudience: defaultAud, - DefaultScopes: []string{defaultScope}, - }, + Credentials: creds, }) if err != nil { return nil, err } } else if opts.Client == nil { + creds = opts.Credentials client = internal.CloneDefaultClient() - if err := httptransport.AddAuthorizationMiddleware(client, opts.TokenProvider); err != nil { + if err := httptransport.AddAuthorizationMiddleware(client, opts.Credentials); err != nil { return nil, err } } else { @@ -113,7 +120,15 @@ func NewIDTokenProvider(opts *IDTokenOptions) (auth.TokenProvider, error) { for _, v := range opts.Delegates { itp.delegates = append(itp.delegates, formatIAMServiceAccountName(v)) } - return auth.NewCachedTokenProvider(itp, nil), nil + + var udp auth.CredentialsPropertyProvider + if creds != nil { + udp = auth.CredentialsPropertyFunc(creds.UniverseDomain) + } + return auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: auth.NewCachedTokenProvider(itp, nil), + UniverseDomainProvider: udp, + }), nil } type generateIDTokenRequest struct { diff --git a/auth/impersonate/idtoken_test.go b/auth/impersonate/idtoken_test.go index 5a7cb7ca80af..70dfbc8d44eb 100644 --- a/auth/impersonate/idtoken_test.go +++ b/auth/impersonate/idtoken_test.go @@ -23,7 +23,7 @@ import ( "testing" ) -func TestIDTokenSource(t *testing.T) { +func TestNewIDTokenCredentials(t *testing.T) { ctx := context.Background() tests := []struct { name string @@ -82,7 +82,7 @@ func TestIDTokenSource(t *testing.T) { } }), } - tp, err := NewIDTokenProvider(&IDTokenOptions{ + creds, err := NewIDTokenCredentials(&IDTokenOptions{ Audience: tt.aud, TargetPrincipal: tt.targetPrincipal, Client: client, @@ -94,7 +94,7 @@ func TestIDTokenSource(t *testing.T) { if err != nil { t.Fatal(err) } - tok, err := tp.Token(ctx) + tok, err := creds.Token(ctx) if err != nil { t.Fatal(err) } diff --git a/auth/impersonate/impersonate.go b/auth/impersonate/impersonate.go index 79386b3b6db6..79eb15b4bdf9 100644 --- a/auth/impersonate/impersonate.go +++ b/auth/impersonate/impersonate.go @@ -24,6 +24,7 @@ import ( "time" "cloud.google.com/go/auth" + "cloud.google.com/go/auth/credentials" "cloud.google.com/go/auth/httptransport" "cloud.google.com/go/auth/internal" ) @@ -33,11 +34,13 @@ var ( oauth2Endpoint = "https://oauth2.googleapis.com" ) -// NewCredentialTokenProvider returns an impersonated -// [cloud.google.com/go/auth/TokenProvider] configured with the provided options +// TODO(codyoss): plumb through base for this and idtoken + +// NewCredentials returns an impersonated +// [cloud.google.com/go/auth/NewCredentials] configured with the provided options // and using credentials loaded from Application Default Credentials as the base // credentials if not provided with the opts. -func NewCredentialTokenProvider(opts *CredentialOptions) (auth.TokenProvider, error) { +func NewCredentials(opts *CredentialsOptions) (*auth.Credentials, error) { if err := opts.validate(); err != nil { return nil, err } @@ -53,20 +56,26 @@ func NewCredentialTokenProvider(opts *CredentialOptions) (auth.TokenProvider, er } var client *http.Client - if opts.Client == nil && opts.TokenProvider == nil { + var creds *auth.Credentials + if opts.Client == nil && opts.Credentials == nil { var err error + creds, err = credentials.DetectDefault(&credentials.DetectOptions{ + Scopes: []string{defaultScope}, + UseSelfSignedJWT: true, + }) + if err != nil { + return nil, err + } client, err = httptransport.NewClient(&httptransport.Options{ - InternalOptions: &httptransport.InternalOptions{ - DefaultAudience: defaultAud, - DefaultScopes: []string{defaultScope}, - }, + Credentials: creds, }) if err != nil { return nil, err } - } else if opts.TokenProvider != nil { + } else if opts.Credentials != nil { + creds = opts.Credentials client = internal.CloneDefaultClient() - if err := httptransport.AddAuthorizationMiddleware(client, opts.TokenProvider); err != nil { + if err := httptransport.AddAuthorizationMiddleware(client, opts.Credentials); err != nil { return nil, err } } else { @@ -76,7 +85,18 @@ func NewCredentialTokenProvider(opts *CredentialOptions) (auth.TokenProvider, er // If a subject is specified a different auth-flow is initiated to // impersonate as the provided subject (user). if opts.Subject != "" { - return user(opts, client, lifetime, isStaticToken) + tp, err := user(opts, client, lifetime, isStaticToken) + if err != nil { + return nil, err + } + var udp auth.CredentialsPropertyProvider + if creds != nil { + udp = auth.CredentialsPropertyFunc(creds.UniverseDomain) + } + return auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: tp, + UniverseDomainProvider: udp, + }), nil } its := impersonatedTokenProvider{ @@ -96,11 +116,19 @@ func NewCredentialTokenProvider(opts *CredentialOptions) (auth.TokenProvider, er DisableAutoRefresh: true, } } - return auth.NewCachedTokenProvider(its, tpo), nil + + var udp auth.CredentialsPropertyProvider + if creds != nil { + udp = auth.CredentialsPropertyFunc(creds.UniverseDomain) + } + return auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: auth.NewCachedTokenProvider(its, tpo), + UniverseDomainProvider: udp, + }), nil } -// CredentialOptions for generating an impersonated credential token. -type CredentialOptions struct { +// CredentialsOptions for generating an impersonated credential token. +type CredentialsOptions struct { // TargetPrincipal is the email address of the service account to // impersonate. Required. TargetPrincipal string @@ -122,17 +150,17 @@ type CredentialOptions struct { // wide delegation. Optional. Subject string - // TokenProvider is the provider of the credentials used to fetch the ID + // Credentials is the provider of the credentials used to fetch the ID // token. If not provided, and a Client is also not provided, credentials // will try to be detected from the environment. Optional. - TokenProvider auth.TokenProvider + Credentials *auth.Credentials // Client configures the underlying client used to make network requests // when fetching tokens. If provided the client should provide it's own // credentials at call time. Optional. Client *http.Client } -func (o *CredentialOptions) validate() error { +func (o *CredentialsOptions) validate() error { if o == nil { return errors.New("impersonate: options must be provided") } diff --git a/auth/impersonate/impersonate_test.go b/auth/impersonate/impersonate_test.go index 33a27696f8d5..a3c869e20838 100644 --- a/auth/impersonate/impersonate_test.go +++ b/auth/impersonate/impersonate_test.go @@ -27,7 +27,7 @@ import ( "github.com/google/go-cmp/cmp" ) -func TestTokenSource_serviceAccount(t *testing.T) { +func TestNewCredentials_serviceAccount(t *testing.T) { ctx := context.Background() tests := []struct { name string @@ -100,7 +100,7 @@ func TestTokenSource_serviceAccount(t *testing.T) { return nil }), } - ts, err := NewCredentialTokenProvider(&CredentialOptions{ + ts, err := NewCredentials(&CredentialsOptions{ TargetPrincipal: tt.targetPrincipal, Scopes: tt.scopes, Lifetime: tt.lifetime, diff --git a/auth/impersonate/integration_test.go b/auth/impersonate/integration_test.go index dd4fc4967a44..3bb85f57dc6d 100644 --- a/auth/impersonate/integration_test.go +++ b/auth/impersonate/integration_test.go @@ -70,7 +70,7 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func TestCredentialsTokenSourceIntegration(t *testing.T) { +func TestNewCredentialsIntegration(t *testing.T) { testutil.IntegrationTestCheck(t) tests := []struct { name string @@ -97,10 +97,10 @@ func TestCredentialsTokenSourceIntegration(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() - var creds *auth.Credentials + var baseCreds *auth.Credentials if !tt.useDefaultCreds { var err error - creds, err = credentials.DetectDefault(&credentials.DetectOptions{ + baseCreds, err = credentials.DetectDefault(&credentials.DetectOptions{ Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"}, CredentialsFile: tt.baseKeyFile, }) @@ -109,19 +109,19 @@ func TestCredentialsTokenSourceIntegration(t *testing.T) { } } - opts := &impersonate.CredentialOptions{ + opts := &impersonate.CredentialsOptions{ TargetPrincipal: writerEmail, Scopes: []string{"https://www.googleapis.com/auth/devstorage.full_control"}, Delegates: tt.delegates, } if !tt.useDefaultCreds { - opts.TokenProvider = creds + opts.Credentials = baseCreds } - tp, err := impersonate.NewCredentialTokenProvider(opts) + creds, err := impersonate.NewCredentials(opts) if err != nil { t.Fatalf("failed to create ts: %v", err) } - client := testgcs.NewClient(tp) + client := testgcs.NewClient(creds) bucketName := fmt.Sprintf("%s-impersonate-test-%d", projectID, random.Int63()) if err := client.CreateBucket(ctx, projectID, bucketName); err != nil { t.Fatalf("error creating bucket: %v", err) @@ -133,7 +133,7 @@ func TestCredentialsTokenSourceIntegration(t *testing.T) { } } -func TestIDTokenSourceIntegration(t *testing.T) { +func TestNewIDTokenCredentialsIntegration(t *testing.T) { testutil.IntegrationTestCheck(t) ctx := context.Background() @@ -147,7 +147,6 @@ func TestIDTokenSourceIntegration(t *testing.T) { name: "SA -> SA", baseKeyFile: readerKeyFile, }, - { name: "SA -> SA (Default)", useDefaultCreds: true, @@ -162,10 +161,10 @@ func TestIDTokenSourceIntegration(t *testing.T) { for _, tt := range tests { name := tt.name t.Run(name, func(t *testing.T) { - var creds *auth.Credentials + var baseCreds *auth.Credentials if !tt.useDefaultCreds { var err error - creds, err = credentials.DetectDefault(&credentials.DetectOptions{ + baseCreds, err = credentials.DetectDefault(&credentials.DetectOptions{ Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"}, CredentialsFile: tt.baseKeyFile, }) @@ -181,13 +180,13 @@ func TestIDTokenSourceIntegration(t *testing.T) { IncludeEmail: true, } if !tt.useDefaultCreds { - opts.TokenProvider = creds + opts.Credentials = baseCreds } - tp, err := impersonate.NewIDTokenProvider(opts) + creds, err := impersonate.NewIDTokenCredentials(opts) if err != nil { t.Fatalf("failed to create ts: %v", err) } - tok, err := tp.Token(ctx) + tok, err := creds.Token(ctx) if err != nil { t.Fatalf("unable to retrieve Token: %v", err) } diff --git a/auth/impersonate/user.go b/auth/impersonate/user.go index ff86d7e446b7..09283b91a46d 100644 --- a/auth/impersonate/user.go +++ b/auth/impersonate/user.go @@ -28,7 +28,7 @@ import ( "cloud.google.com/go/auth/internal" ) -func user(opts *CredentialOptions, client *http.Client, lifetime time.Duration, isStaticToken bool) (auth.TokenProvider, error) { +func user(opts *CredentialsOptions, client *http.Client, lifetime time.Duration, isStaticToken bool) (auth.TokenProvider, error) { u := userTokenProvider{ client: client, targetPrincipal: opts.TargetPrincipal, diff --git a/auth/impersonate/user_test.go b/auth/impersonate/user_test.go index 5cb3834fca09..02ab889a55fe 100644 --- a/auth/impersonate/user_test.go +++ b/auth/impersonate/user_test.go @@ -28,7 +28,7 @@ import ( "cloud.google.com/go/auth/internal/jwt" ) -func TestTokenSource_user(t *testing.T) { +func TestNewCredentials_user(t *testing.T) { ctx := context.Background() tests := []struct { name string @@ -126,7 +126,7 @@ func TestTokenSource_user(t *testing.T) { return nil }), } - ts, err := NewCredentialTokenProvider(&CredentialOptions{ + ts, err := NewCredentials(&CredentialsOptions{ TargetPrincipal: tt.targetPrincipal, Scopes: tt.scopes, Lifetime: tt.lifetime, diff --git a/auth/internal/testutil/testdns/dns.go b/auth/internal/testutil/testdns/dns.go index 1939af9ea3c9..256216dceb42 100644 --- a/auth/internal/testutil/testdns/dns.go +++ b/auth/internal/testutil/testdns/dns.go @@ -36,7 +36,9 @@ type Client struct { // [cloud.google.com/go/auth.TokenProvider] for authentication. func NewClient(tp auth.TokenProvider) *Client { client := internal.CloneDefaultClient() - httptransport.AddAuthorizationMiddleware(client, tp) + httptransport.AddAuthorizationMiddleware(client, auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: tp, + })) return &Client{ client: client, } diff --git a/auth/internal/testutil/testgcs/storage.go b/auth/internal/testutil/testgcs/storage.go index fb336fa9b38c..e61853923ea4 100644 --- a/auth/internal/testutil/testgcs/storage.go +++ b/auth/internal/testutil/testgcs/storage.go @@ -38,7 +38,9 @@ type Client struct { // [cloud.google.com/go/auth.TokenProvider] for authentication. func NewClient(tp auth.TokenProvider) *Client { client := internal.CloneDefaultClient() - httptransport.AddAuthorizationMiddleware(client, tp) + httptransport.AddAuthorizationMiddleware(client, auth.NewCredentials(&auth.CredentialsOptions{ + TokenProvider: tp, + })) return &Client{ client: client, }