Skip to content

Commit

Permalink
fix(auth): read universe_domain from all credentials files (googleapi…
Browse files Browse the repository at this point in the history
  • Loading branch information
quartzmo authored Mar 22, 2024
1 parent cddd528 commit 16efbb5
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 45 deletions.
3 changes: 2 additions & 1 deletion auth/credentials/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ type DetectOptions struct {
// when fetching tokens. Optional.
Client *http.Client
// UniverseDomain is the default service domain for a given Cloud universe.
// The default value is "googleapis.com". Optional.
// The default value is "googleapis.com". This option is ignored for
// authentication flows that do not support universe domain. Optional.
UniverseDomain string
}

Expand Down
130 changes: 102 additions & 28 deletions auth/credentials/detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type tokResp struct {

func TestDefaultCredentials_GdchServiceAccountKey(t *testing.T) {
ctx := context.Background()
aud := "http://sampele-aud.com/"
aud := "http://sample-aud.com/"
b, err := os.ReadFile("../internal/testdata/gdch.json")
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -285,7 +285,7 @@ func TestDefaultCredentials_UserCredentialsKey_UniverseDomain(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if want := "googleapis.com"; got != want {
if want := "example.com"; got != want {
t.Fatalf("got %q, want %q", got, want)
}
tok, err := creds.Token(context.Background())
Expand Down Expand Up @@ -728,6 +728,39 @@ func TestDefaultCredentials_UniverseDomain(t *testing.T) {
opts *DetectOptions
want string
}{
{
name: "service account json",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/sa.json",
},
want: "googleapis.com",
},
{
name: "service account json with file universe domain",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/sa_universe_domain.json",
UseSelfSignedJWT: true,
},
want: "example.com",
},
{
name: "service account json with options universe domain",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/sa.json",
UseSelfSignedJWT: true,
UniverseDomain: "foo.com",
},
want: "foo.com",
},
{
name: "service account json with file and options universe domain",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/sa_universe_domain.json",
UseSelfSignedJWT: true,
UniverseDomain: "foo.com",
},
want: "foo.com",
},
{
name: "user json",
opts: &DetectOptions{
Expand All @@ -736,71 +769,104 @@ func TestDefaultCredentials_UniverseDomain(t *testing.T) {
},
want: "googleapis.com",
},
{
name: "user json with options universe domain",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/user.json",
UniverseDomain: "foo.com",
},
want: "googleapis.com",
},
{
name: "user json with file universe domain",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/user_universe_domain.json",
TokenURL: "example.com",
},
want: "googleapis.com",
want: "example.com",
},
{
name: "service account token URL json",
name: "user json with file and options universe domain",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/sa.json",
CredentialsFile: "../internal/testdata/user_universe_domain.json",
UniverseDomain: "foo.com",
},
want: "googleapis.com",
want: "example.com",
},
{
name: "external account json",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/exaccount_user.json",
UseSelfSignedJWT: true,
CredentialsFile: "../internal/testdata/exaccount_url.json",
},
want: "googleapis.com",
},
{
name: "service account impersonation json",
name: "external account json with file universe domain",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/imp.json",
UseSelfSignedJWT: true,
CredentialsFile: "../internal/testdata/exaccount_url_universe_domain.json",
},
want: "example.com",
},
{
name: "external account json with options universe domain",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/exaccount_url.json",
UniverseDomain: "foo.com",
},
want: "foo.com",
},
{
name: "external account json with file and options universe domain",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/exaccount_url_universe_domain.json",
UniverseDomain: "foo.com",
},
want: "foo.com",
},
{
name: "external account user json",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/exaccount_user.json",
},
want: "googleapis.com",
},
{
name: "service account json with file universe domain",
name: "external account user json with file universe domain",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/sa_universe_domain.json",
UseSelfSignedJWT: true,
CredentialsFile: "../internal/testdata/exaccount_user_universe_domain.json",
},
want: "example.com",
},
{
name: "service account json with options universe domain",
name: "external account user json with options universe domain",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/sa.json",
UseSelfSignedJWT: true,
UniverseDomain: "foo.com",
CredentialsFile: "../internal/testdata/exaccount_user.json",
UniverseDomain: "foo.com",
},
want: "foo.com",
want: "googleapis.com",
},
{
name: "service account json with file and options universe domain",
name: "external account user json with file and options universe domain",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/sa_universe_domain.json",
UseSelfSignedJWT: true,
UniverseDomain: "bar.com",
CredentialsFile: "../internal/testdata/exaccount_user_universe_domain.json",
UniverseDomain: "foo.com",
},
want: "bar.com",
want: "example.com",
},
{
name: "external account json with options universe domain",
name: "impersonated service account json",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/exaccount_user.json",
CredentialsFile: "../internal/testdata/imp.json",
UseSelfSignedJWT: true,
UniverseDomain: "foo.com",
},
want: "foo.com",
want: "googleapis.com",
},
{
name: "impersonated service account json with file universe domain",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/imp_universe_domain.json",
},
want: "example.com",
},
{
name: "impersonated service account json with options universe domain",
Expand All @@ -811,6 +877,14 @@ func TestDefaultCredentials_UniverseDomain(t *testing.T) {
},
want: "foo.com",
},
{
name: "impersonated service account json with file and options universe domain",
opts: &DetectOptions{
CredentialsFile: "../internal/testdata/imp_universe_domain.json",
UniverseDomain: "foo.com",
},
want: "foo.com",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
23 changes: 17 additions & 6 deletions auth/credentials/filetypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
return nil, err
}
projectID = f.ProjectID
universeDomain = f.UniverseDomain
universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
case credsfile.UserCredentialsKey:
f, err := credsfile.ParseUserCredentials(b)
if err != nil {
Expand All @@ -57,6 +57,7 @@ func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
return nil, err
}
quotaProjectID = f.QuotaProjectID
universeDomain = f.UniverseDomain
case credsfile.ExternalAccountKey:
f, err := credsfile.ParseExternalAccount(b)
if err != nil {
Expand All @@ -67,7 +68,7 @@ func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
return nil, err
}
quotaProjectID = f.QuotaProjectID
universeDomain = f.UniverseDomain
universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
case credsfile.ExternalAccountAuthorizedUserKey:
f, err := credsfile.ParseExternalAccountAuthorizedUser(b)
if err != nil {
Expand All @@ -78,6 +79,7 @@ func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
return nil, err
}
quotaProjectID = f.QuotaProjectID
universeDomain = f.UniverseDomain
case credsfile.ImpersonatedServiceAccountKey:
f, err := credsfile.ParseImpersonatedServiceAccount(b)
if err != nil {
Expand All @@ -87,7 +89,7 @@ func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
if err != nil {
return nil, err
}
universeDomain = f.UniverseDomain
universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
case credsfile.GDCHServiceAccountKey:
f, err := credsfile.ParseGDCHServiceAccount(b)
if err != nil {
Expand All @@ -98,12 +100,10 @@ func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
return nil, err
}
projectID = f.Project
universeDomain = f.UniverseDomain
default:
return nil, fmt.Errorf("credentials: unsupported filetype %q", fileType)
}
if opts.UniverseDomain != "" {
universeDomain = opts.UniverseDomain
}
return auth.NewCredentials(&auth.CredentialsOptions{
TokenProvider: auth.NewCachedTokenProvider(tp, &auth.CachedTokenProviderOptions{
ExpireEarly: opts.EarlyTokenRefresh,
Expand All @@ -115,6 +115,17 @@ func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
}), nil
}

// resolveUniverseDomain returns optsUniverseDomain if non-empty, in order to
// support configuring universe-specific credentials in code. Auth flows
// unsupported for universe domain should not use this func, but should instead
// simply set the file universe domain on the credentials.
func resolveUniverseDomain(optsUniverseDomain, fileUniverseDomain string) string {
if optsUniverseDomain != "" {
return optsUniverseDomain
}
return fileUniverseDomain
}

func handleServiceAccount(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
if opts.UseSelfSignedJWT {
return configureSelfSignedJWT(f, opts)
Expand Down
24 changes: 14 additions & 10 deletions auth/internal/credsfile/filetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ type Config3LO struct {

// ClientCredentialsFile representation.
type ClientCredentialsFile struct {
Web *Config3LO `json:"web"`
Installed *Config3LO `json:"installed"`
Web *Config3LO `json:"web"`
Installed *Config3LO `json:"installed"`
UniverseDomain string `json:"universe_domain"`
}

// ServiceAccountFile representation.
Expand All @@ -51,6 +52,7 @@ type UserCredentialsFile struct {
ClientSecret string `json:"client_secret"`
QuotaProjectID string `json:"quota_project_id"`
RefreshToken string `json:"refresh_token"`
UniverseDomain string `json:"universe_domain"`
}

// ExternalAccountFile representation.
Expand Down Expand Up @@ -81,6 +83,7 @@ type ExternalAccountAuthorizedUserFile struct {
TokenInfoURL string `json:"token_info_url"`
RevokeURL string `json:"revoke_url"`
QuotaProjectID string `json:"quota_project_id"`
UniverseDomain string `json:"universe_domain"`
}

// CredentialSource stores the information necessary to retrieve the credentials for the STS exchange.
Expand Down Expand Up @@ -132,12 +135,13 @@ type ImpersonatedServiceAccountFile struct {

// GDCHServiceAccountFile represents the Google Distributed Cloud Hosted (GDCH) service identity file.
type GDCHServiceAccountFile struct {
Type string `json:"type"`
FormatVersion string `json:"format_version"`
Project string `json:"project"`
Name string `json:"name"`
CertPath string `json:"ca_cert_path"`
PrivateKeyID string `json:"private_key_id"`
PrivateKey string `json:"private_key"`
TokenURL string `json:"token_uri"`
Type string `json:"type"`
FormatVersion string `json:"format_version"`
Project string `json:"project"`
Name string `json:"name"`
CertPath string `json:"ca_cert_path"`
PrivateKeyID string `json:"private_key_id"`
PrivateKey string `json:"private_key"`
TokenURL string `json:"token_uri"`
UniverseDomain string `json:"universe_domain"`
}
15 changes: 15 additions & 0 deletions auth/internal/testdata/exaccount_url_universe_domain.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/$EMAIL:generateAccessToken",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": {
"url": "http://localhost:5000/token",
"format": {
"type": "json",
"subject_token_field_name": "id_token"
}
},
"universe_domain": "example.com"
}
12 changes: 12 additions & 0 deletions auth/internal/testdata/exaccount_user_universe_domain.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"type": "external_account_authorized_user",
"audience": "//iam.googleapis.com/locations/global/workforcePools/$POOL_ID/providers/$PROVIDER_ID",
"client_id": "abc123.apps.googleusercontent.com",
"client_secret": "shh",
"refresh_token": "refreshing",
"token_url": "https://sts.googleapis.com/v1/oauthtoken",
"token_info_url": "https://sts.googleapis.com/v1/info",
"revoke_url": "https://sts.googleapis.com/v1/revoke",
"quota_project_id": "fake_project2",
"universe_domain": "example.com"
}
19 changes: 19 additions & 0 deletions auth/internal/testdata/imp_universe_domain.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[email protected]:generateAccessToken",
"delegates": [
"[email protected]",
"[email protected]"
],
"source_credentials": {
"type": "service_account",
"project_id": "fake_project",
"private_key_id": "89asd789789uo473454c47543",
"private_key": "fake",
"client_email": "sa@fake_project.iam.gserviceaccount.com",
"client_id": "gopher",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token"
},
"type": "impersonated_service_account",
"universe_domain": "example.com"
}

0 comments on commit 16efbb5

Please sign in to comment.