Skip to content

Commit

Permalink
feat(auth): refactor public sigs to use Credentials (#9603)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
codyoss authored Mar 19, 2024
1 parent 81281c0 commit 69cb240
Show file tree
Hide file tree
Showing 33 changed files with 286 additions and 197 deletions.
4 changes: 2 additions & 2 deletions auth/credentials/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down
6 changes: 3 additions & 3 deletions auth/credentials/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion auth/downscope/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 21 additions & 16 deletions auth/downscope/downscope.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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")
Expand All @@ -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.
Expand Down Expand Up @@ -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)
}
Expand Down
30 changes: 18 additions & 12 deletions auth/downscope/downscope_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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",
Expand All @@ -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
Expand All @@ -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",
}},
Expand All @@ -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")
}
})
Expand Down
6 changes: 3 additions & 3 deletions auth/downscope/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions auth/downscope/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion auth/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 6 additions & 3 deletions auth/grpctransport/dial_socketopt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"testing"
"time"

"cloud.google.com/go/auth"
"google.golang.org/grpc"
)

Expand Down Expand Up @@ -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,
},
Expand Down
4 changes: 2 additions & 2 deletions auth/grpctransport/directpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
37 changes: 18 additions & 19 deletions auth/grpctransport/grpctransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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,
}),
)

Expand All @@ -240,37 +239,37 @@ 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

// Additional metadata attached as headers.
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
}

Expand Down
Loading

0 comments on commit 69cb240

Please sign in to comment.