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: add hooks in for new auth library #2228

Merged
merged 14 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module google.golang.org/api
go 1.19

require (
cloud.google.com/go/auth v0.1.0
cloud.google.com/go/auth/oauth2adapt v0.1.0
cloud.google.com/go/compute/metadata v0.2.3
github.com/google/go-cmp v0.6.0
github.com/google/s2a-go v0.1.7
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go/auth v0.1.0 h1:IW6zQbwuzQYbczgasiV1f+R0GpY3KKfDnGxQ7ZAhXWQ=
cloud.google.com/go/auth v0.1.0/go.mod h1:/Qq1aRmdEDFEBtjhFLNtzLM0aEAtSL0JhQeKeQGZNqw=
cloud.google.com/go/auth/oauth2adapt v0.1.0 h1:h6oXNGJbo9Gh6Ribb59Ne/Tq8nKzes1eJRGPy2ektEw=
cloud.google.com/go/auth/oauth2adapt v0.1.0/go.mod h1:hqo6pQ/D8Usi9vyzHFKiH9BZuGdSagncxl5kC2Yy1Yw=
cloud.google.com/go/compute v1.23.4 h1:EBT9Nw4q3zyE7G45Wvv3MzolIrCJEuHys5muLY0wvAw=
cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
Expand Down
34 changes: 25 additions & 9 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA=
cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw=
cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk=
cloud.google.com/go v0.110.6 h1:8uYAkj3YHTP/1iwReuHPxLSbdcyc+dSBbzFMrVwDR6Q=
cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o=
cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME=
cloud.google.com/go v0.110.9 h1:e7ITSqGFFk4rbz/JFIqZh3G4VEHguhAL4BQcFlWtU68=
cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y=
cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM=
cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM=
github.com/google/go-github/v59 v59.0.0 h1:7h6bgpF5as0YQLLkEiVqpgtJqjimMYhBkD4jT5aN3VA=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
Expand Down
22 changes: 22 additions & 0 deletions idtoken/idtoken.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"

newidtoken "cloud.google.com/go/auth/idtoken"
"cloud.google.com/go/auth/oauth2adapt"
"google.golang.org/api/impersonate"
"google.golang.org/api/internal"
"google.golang.org/api/option"
Expand Down Expand Up @@ -95,9 +97,29 @@ func NewTokenSource(ctx context.Context, audience string, opts ...ClientOption)
if ds.ImpersonationConfig != nil {
return nil, fmt.Errorf("idtoken: option.WithImpersonatedCredentials not supported")
}
if ds.IsNewAuthLibraryEnabled() {
return newTokenSourceNewAuth(ctx, audience, &ds)
}
return newTokenSource(ctx, audience, &ds)
}

func newTokenSourceNewAuth(ctx context.Context, audience string, ds *internal.DialSettings) (oauth2.TokenSource, error) {
if ds.TokenProvider != nil {
return nil, fmt.Errorf("idtoken: option.WithTokenProvider not supported")
}
tp, err := newidtoken.NewTokenProvider(&newidtoken.Options{
Audience: audience,
CustomClaims: ds.CustomClaims,
CredentialsFile: ds.CredentialsFile,
CredentialsJSON: ds.CredentialsJSON,
Client: oauth2.NewClient(ctx, nil),
})
if err != nil {
return nil, err
}
return oauth2adapt.TokenSourceFromTokenProvider(tp), nil
}

func newTokenSource(ctx context.Context, audience string, ds *internal.DialSettings) (oauth2.TokenSource, error) {
creds, err := internal.Creds(ctx, ds)
if err != nil {
Expand Down
59 changes: 59 additions & 0 deletions internal/creds.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"os"
"time"

"cloud.google.com/go/auth/detect"
"cloud.google.com/go/auth/oauth2adapt"
"golang.org/x/oauth2"
"google.golang.org/api/internal/cert"
"google.golang.org/api/internal/impersonate"
Expand All @@ -27,6 +29,9 @@ const quotaProjectEnvVar = "GOOGLE_CLOUD_QUOTA_PROJECT"
// Creds returns credential information obtained from DialSettings, or if none, then
// it returns default credential information.
func Creds(ctx context.Context, ds *DialSettings) (*google.Credentials, error) {
if ds.IsNewAuthLibraryEnabled() {
return credsNewAuth(ctx, ds)
}
creds, err := baseCreds(ctx, ds)
if err != nil {
return nil, err
Expand All @@ -37,6 +42,60 @@ func Creds(ctx context.Context, ds *DialSettings) (*google.Credentials, error) {
return creds, nil
}

func credsNewAuth(ctx context.Context, settings *DialSettings) (*google.Credentials, error) {
// Preserve old options behavior
if settings.InternalCredentials != nil {
return settings.InternalCredentials, nil
} else if settings.Credentials != nil {
return settings.Credentials, nil
} else if settings.TokenSource != nil {
return &google.Credentials{TokenSource: settings.TokenSource}, nil
}

if settings.TokenProvider != nil {
return &google.Credentials{TokenSource: oauth2adapt.TokenSourceFromTokenProvider(settings.TokenProvider)}, nil
}

var useSelfSignedJWT bool
var aud string
var scopes []string
// If scoped JWTs are enabled user provided an aud, allow self-signed JWT.
if settings.EnableJwtWithScope || len(settings.Audiences) > 0 {
useSelfSignedJWT = true
}

if len(settings.Audiences) > 0 {
aud = settings.Audiences[0]
}
// Only default scopes if user did not also set an audience.
if len(settings.Scopes) == 0 && aud == "" && len(settings.DefaultScopes) > 0 {
scopes = make([]string, len(scopes))
copy(scopes, settings.DefaultScopes)
}
if len(scopes) == 0 && aud == "" {
aud = settings.DefaultAudience
}

creds, err := detect.DefaultCredentials(&detect.Options{
Scopes: scopes,
Audience: aud,
CredentialsFile: settings.CredentialsFile,
CredentialsJSON: settings.CredentialsJSON,
UseSelfSignedJWT: useSelfSignedJWT,
Client: oauth2.NewClient(ctx, nil),
})
if err != nil {
return nil, err
}

ts := oauth2adapt.TokenSourceFromTokenProvider(creds)
return &google.Credentials{
ProjectID: creds.ProjectID(),
TokenSource: ts,
JSON: creds.JSON(),
}, nil
}

func baseCreds(ctx context.Context, ds *DialSettings) (*google.Credentials, error) {
if ds.InternalCredentials != nil {
return ds.InternalCredentials, nil
Expand Down
39 changes: 0 additions & 39 deletions internal/creds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,10 @@ import (
"os"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)

type dummyTokenSource struct {
oauth2.TokenSource
}

func TestTokenSource(t *testing.T) {
ctx := context.Background()

// Pass in a TokenSource, get it back.
ts := &dummyTokenSource{}
ds := &DialSettings{TokenSource: ts}
got, err := Creds(ctx, ds)
if err != nil {
t.Fatal(err)
}
want := &google.DefaultCredentials{TokenSource: ts}
if !cmp.Equal(got, want, cmpopts.IgnoreFields(google.Credentials{}, "udMu", "universeDomain")) {
t.Error("did not get the same TokenSource back")
}

// If both a file and TokenSource are passed, the file takes precedence
// (existing behavior).
// TODO(jba): make this an error?
ds = &DialSettings{
TokenSource: ts,
CredentialsFile: "testdata/service-account.json",
DefaultScopes: []string{"foo"},
}
got, err = Creds(ctx, ds)
if err != nil {
t.Fatal(err)
}
if cmp.Equal(got, want, cmpopts.IgnoreFields(google.Credentials{}, "udMu", "universeDomain")) {
t.Error("got the same TokenSource back, wanted one from the JSON file")
}
// TODO(jba): find a way to test the call to google.DefaultTokenSource.
}

func TestDefaultServiceAccount(t *testing.T) {
ctx := context.Background()

Expand Down
1 change: 1 addition & 0 deletions internal/kokoro/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export GCLOUD_TESTS_GOLANG_AWS_ROLE_ID="arn:aws:iam::$GCLOUD_TESTS_GOLANG_AWS_AC
export GCLOUD_TESTS_GOLANG_AUDIENCE_OIDC=$(cat ${KOKORO_GFILE_DIR}/secret_manager/go-cloud-integration-byoid-aud-oidc)
export GCLOUD_TESTS_GOLANG_AUDIENCE_AWS=$(cat ${KOKORO_GFILE_DIR}/secret_manager/go-cloud-integration-byoid-aud-aws)
export GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES="1"
export GOOGLE_API_GO_EXPERIMENTAL_ENABLE_NEW_AUTH_LIB="true"

# Display commands being run
set -x
Expand Down
14 changes: 12 additions & 2 deletions internal/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ import (
"strconv"
"time"

"cloud.google.com/go/auth"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/internal/impersonate"
"google.golang.org/grpc"
)

const (
newAuthLibEnVar = "GOOGLE_API_GO_EXPERIMENTAL_USE_NEW_AUTH_LIB"
newAuthLibEnVar = "GOOGLE_API_GO_EXPERIMENTAL_ENABLE_NEW_AUTH_LIB"
newAuthLibDisabledEnVar = "GOOGLE_API_GO_EXPERIMENTAL_DISABLE_NEW_AUTH_LIB"
universeDomainDefault = "googleapis.com"
)

Expand Down Expand Up @@ -56,7 +58,6 @@ type DialSettings struct {
ImpersonationConfig *impersonate.Config
EnableDirectPath bool
EnableDirectPathXds bool
EnableNewAuthLibrary bool
AllowNonDefaultServiceAccount bool
UniverseDomain string
DefaultUniverseDomain string
Expand All @@ -65,6 +66,10 @@ type DialSettings struct {
// https://cloud.google.com/apis/docs/system-parameters
QuotaProject string
RequestReason string

// New Auth library Options
TokenProvider auth.TokenProvider
EnableNewAuthLibrary bool
}

// GetScopes returns the user-provided scopes, if set, or else falls back to the
Expand All @@ -91,6 +96,11 @@ func (ds *DialSettings) HasCustomAudience() bool {

// IsNewAuthLibraryEnabled returns true if the new auth library should be used.
func (ds *DialSettings) IsNewAuthLibraryEnabled() bool {
// Disabled env is for future rollouts to make sure there is a way to easily
// disable this behaviour once we switch in on by default.
if b, err := strconv.ParseBool(os.Getenv(newAuthLibDisabledEnVar)); err == nil && b {
return false
}
if ds.EnableNewAuthLibrary {
return true
}
Expand Down
14 changes: 14 additions & 0 deletions option/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"crypto/tls"
"net/http"

"cloud.google.com/go/auth"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/internal"
Expand Down Expand Up @@ -344,6 +345,19 @@ func WithCredentials(creds *google.Credentials) ClientOption {
return (*withCreds)(creds)
}

// WithTokenProvider returns a ClientOption that specifies an
// [cloud.google.com/go/auth.TokenProvider] to be used as the basis for
// authentication.
func WithTokenProvider(tp auth.TokenProvider) ClientOption {
codyoss marked this conversation as resolved.
Show resolved Hide resolved
return withTokenProvider{tp}
}

type withTokenProvider struct{ tp auth.TokenProvider }

func (w withTokenProvider) Apply(o *internal.DialSettings) {
o.TokenProvider = w.tp
}

// WithUniverseDomain returns a ClientOption that sets the universe domain.
//
// This is an EXPERIMENTAL API and may be changed or removed in the future.
Expand Down