Skip to content

Commit 16efbb5

Browse files
authored
fix(auth): read universe_domain from all credentials files (#9632)
1 parent cddd528 commit 16efbb5

File tree

7 files changed

+181
-45
lines changed

7 files changed

+181
-45
lines changed

auth/credentials/detect.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ type DetectOptions struct {
142142
// when fetching tokens. Optional.
143143
Client *http.Client
144144
// UniverseDomain is the default service domain for a given Cloud universe.
145-
// The default value is "googleapis.com". Optional.
145+
// The default value is "googleapis.com". This option is ignored for
146+
// authentication flows that do not support universe domain. Optional.
146147
UniverseDomain string
147148
}
148149

auth/credentials/detect_test.go

Lines changed: 102 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ type tokResp struct {
4141

4242
func TestDefaultCredentials_GdchServiceAccountKey(t *testing.T) {
4343
ctx := context.Background()
44-
aud := "http://sampele-aud.com/"
44+
aud := "http://sample-aud.com/"
4545
b, err := os.ReadFile("../internal/testdata/gdch.json")
4646
if err != nil {
4747
t.Fatal(err)
@@ -285,7 +285,7 @@ func TestDefaultCredentials_UserCredentialsKey_UniverseDomain(t *testing.T) {
285285
if err != nil {
286286
t.Fatal(err)
287287
}
288-
if want := "googleapis.com"; got != want {
288+
if want := "example.com"; got != want {
289289
t.Fatalf("got %q, want %q", got, want)
290290
}
291291
tok, err := creds.Token(context.Background())
@@ -728,6 +728,39 @@ func TestDefaultCredentials_UniverseDomain(t *testing.T) {
728728
opts *DetectOptions
729729
want string
730730
}{
731+
{
732+
name: "service account json",
733+
opts: &DetectOptions{
734+
CredentialsFile: "../internal/testdata/sa.json",
735+
},
736+
want: "googleapis.com",
737+
},
738+
{
739+
name: "service account json with file universe domain",
740+
opts: &DetectOptions{
741+
CredentialsFile: "../internal/testdata/sa_universe_domain.json",
742+
UseSelfSignedJWT: true,
743+
},
744+
want: "example.com",
745+
},
746+
{
747+
name: "service account json with options universe domain",
748+
opts: &DetectOptions{
749+
CredentialsFile: "../internal/testdata/sa.json",
750+
UseSelfSignedJWT: true,
751+
UniverseDomain: "foo.com",
752+
},
753+
want: "foo.com",
754+
},
755+
{
756+
name: "service account json with file and options universe domain",
757+
opts: &DetectOptions{
758+
CredentialsFile: "../internal/testdata/sa_universe_domain.json",
759+
UseSelfSignedJWT: true,
760+
UniverseDomain: "foo.com",
761+
},
762+
want: "foo.com",
763+
},
731764
{
732765
name: "user json",
733766
opts: &DetectOptions{
@@ -736,71 +769,104 @@ func TestDefaultCredentials_UniverseDomain(t *testing.T) {
736769
},
737770
want: "googleapis.com",
738771
},
772+
{
773+
name: "user json with options universe domain",
774+
opts: &DetectOptions{
775+
CredentialsFile: "../internal/testdata/user.json",
776+
UniverseDomain: "foo.com",
777+
},
778+
want: "googleapis.com",
779+
},
739780
{
740781
name: "user json with file universe domain",
741782
opts: &DetectOptions{
742783
CredentialsFile: "../internal/testdata/user_universe_domain.json",
743784
TokenURL: "example.com",
744785
},
745-
want: "googleapis.com",
786+
want: "example.com",
746787
},
747788
{
748-
name: "service account token URL json",
789+
name: "user json with file and options universe domain",
749790
opts: &DetectOptions{
750-
CredentialsFile: "../internal/testdata/sa.json",
791+
CredentialsFile: "../internal/testdata/user_universe_domain.json",
792+
UniverseDomain: "foo.com",
751793
},
752-
want: "googleapis.com",
794+
want: "example.com",
753795
},
754796
{
755797
name: "external account json",
756798
opts: &DetectOptions{
757-
CredentialsFile: "../internal/testdata/exaccount_user.json",
758-
UseSelfSignedJWT: true,
799+
CredentialsFile: "../internal/testdata/exaccount_url.json",
759800
},
760801
want: "googleapis.com",
761802
},
762803
{
763-
name: "service account impersonation json",
804+
name: "external account json with file universe domain",
764805
opts: &DetectOptions{
765-
CredentialsFile: "../internal/testdata/imp.json",
766-
UseSelfSignedJWT: true,
806+
CredentialsFile: "../internal/testdata/exaccount_url_universe_domain.json",
807+
},
808+
want: "example.com",
809+
},
810+
{
811+
name: "external account json with options universe domain",
812+
opts: &DetectOptions{
813+
CredentialsFile: "../internal/testdata/exaccount_url.json",
814+
UniverseDomain: "foo.com",
815+
},
816+
want: "foo.com",
817+
},
818+
{
819+
name: "external account json with file and options universe domain",
820+
opts: &DetectOptions{
821+
CredentialsFile: "../internal/testdata/exaccount_url_universe_domain.json",
822+
UniverseDomain: "foo.com",
823+
},
824+
want: "foo.com",
825+
},
826+
{
827+
name: "external account user json",
828+
opts: &DetectOptions{
829+
CredentialsFile: "../internal/testdata/exaccount_user.json",
767830
},
768831
want: "googleapis.com",
769832
},
770833
{
771-
name: "service account json with file universe domain",
834+
name: "external account user json with file universe domain",
772835
opts: &DetectOptions{
773-
CredentialsFile: "../internal/testdata/sa_universe_domain.json",
774-
UseSelfSignedJWT: true,
836+
CredentialsFile: "../internal/testdata/exaccount_user_universe_domain.json",
775837
},
776838
want: "example.com",
777839
},
778840
{
779-
name: "service account json with options universe domain",
841+
name: "external account user json with options universe domain",
780842
opts: &DetectOptions{
781-
CredentialsFile: "../internal/testdata/sa.json",
782-
UseSelfSignedJWT: true,
783-
UniverseDomain: "foo.com",
843+
CredentialsFile: "../internal/testdata/exaccount_user.json",
844+
UniverseDomain: "foo.com",
784845
},
785-
want: "foo.com",
846+
want: "googleapis.com",
786847
},
787848
{
788-
name: "service account json with file and options universe domain",
849+
name: "external account user json with file and options universe domain",
789850
opts: &DetectOptions{
790-
CredentialsFile: "../internal/testdata/sa_universe_domain.json",
791-
UseSelfSignedJWT: true,
792-
UniverseDomain: "bar.com",
851+
CredentialsFile: "../internal/testdata/exaccount_user_universe_domain.json",
852+
UniverseDomain: "foo.com",
793853
},
794-
want: "bar.com",
854+
want: "example.com",
795855
},
796856
{
797-
name: "external account json with options universe domain",
857+
name: "impersonated service account json",
798858
opts: &DetectOptions{
799-
CredentialsFile: "../internal/testdata/exaccount_user.json",
859+
CredentialsFile: "../internal/testdata/imp.json",
800860
UseSelfSignedJWT: true,
801-
UniverseDomain: "foo.com",
802861
},
803-
want: "foo.com",
862+
want: "googleapis.com",
863+
},
864+
{
865+
name: "impersonated service account json with file universe domain",
866+
opts: &DetectOptions{
867+
CredentialsFile: "../internal/testdata/imp_universe_domain.json",
868+
},
869+
want: "example.com",
804870
},
805871
{
806872
name: "impersonated service account json with options universe domain",
@@ -811,6 +877,14 @@ func TestDefaultCredentials_UniverseDomain(t *testing.T) {
811877
},
812878
want: "foo.com",
813879
},
880+
{
881+
name: "impersonated service account json with file and options universe domain",
882+
opts: &DetectOptions{
883+
CredentialsFile: "../internal/testdata/imp_universe_domain.json",
884+
UniverseDomain: "foo.com",
885+
},
886+
want: "foo.com",
887+
},
814888
}
815889
for _, tt := range tests {
816890
t.Run(tt.name, func(t *testing.T) {

auth/credentials/filetypes.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
4646
return nil, err
4747
}
4848
projectID = f.ProjectID
49-
universeDomain = f.UniverseDomain
49+
universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
5050
case credsfile.UserCredentialsKey:
5151
f, err := credsfile.ParseUserCredentials(b)
5252
if err != nil {
@@ -57,6 +57,7 @@ func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
5757
return nil, err
5858
}
5959
quotaProjectID = f.QuotaProjectID
60+
universeDomain = f.UniverseDomain
6061
case credsfile.ExternalAccountKey:
6162
f, err := credsfile.ParseExternalAccount(b)
6263
if err != nil {
@@ -67,7 +68,7 @@ func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
6768
return nil, err
6869
}
6970
quotaProjectID = f.QuotaProjectID
70-
universeDomain = f.UniverseDomain
71+
universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
7172
case credsfile.ExternalAccountAuthorizedUserKey:
7273
f, err := credsfile.ParseExternalAccountAuthorizedUser(b)
7374
if err != nil {
@@ -78,6 +79,7 @@ func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
7879
return nil, err
7980
}
8081
quotaProjectID = f.QuotaProjectID
82+
universeDomain = f.UniverseDomain
8183
case credsfile.ImpersonatedServiceAccountKey:
8284
f, err := credsfile.ParseImpersonatedServiceAccount(b)
8385
if err != nil {
@@ -87,7 +89,7 @@ func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
8789
if err != nil {
8890
return nil, err
8991
}
90-
universeDomain = f.UniverseDomain
92+
universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
9193
case credsfile.GDCHServiceAccountKey:
9294
f, err := credsfile.ParseGDCHServiceAccount(b)
9395
if err != nil {
@@ -98,12 +100,10 @@ func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
98100
return nil, err
99101
}
100102
projectID = f.Project
103+
universeDomain = f.UniverseDomain
101104
default:
102105
return nil, fmt.Errorf("credentials: unsupported filetype %q", fileType)
103106
}
104-
if opts.UniverseDomain != "" {
105-
universeDomain = opts.UniverseDomain
106-
}
107107
return auth.NewCredentials(&auth.CredentialsOptions{
108108
TokenProvider: auth.NewCachedTokenProvider(tp, &auth.CachedTokenProviderOptions{
109109
ExpireEarly: opts.EarlyTokenRefresh,
@@ -115,6 +115,17 @@ func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
115115
}), nil
116116
}
117117

118+
// resolveUniverseDomain returns optsUniverseDomain if non-empty, in order to
119+
// support configuring universe-specific credentials in code. Auth flows
120+
// unsupported for universe domain should not use this func, but should instead
121+
// simply set the file universe domain on the credentials.
122+
func resolveUniverseDomain(optsUniverseDomain, fileUniverseDomain string) string {
123+
if optsUniverseDomain != "" {
124+
return optsUniverseDomain
125+
}
126+
return fileUniverseDomain
127+
}
128+
118129
func handleServiceAccount(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
119130
if opts.UseSelfSignedJWT {
120131
return configureSelfSignedJWT(f, opts)

auth/internal/credsfile/filetype.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ type Config3LO struct {
2727

2828
// ClientCredentialsFile representation.
2929
type ClientCredentialsFile struct {
30-
Web *Config3LO `json:"web"`
31-
Installed *Config3LO `json:"installed"`
30+
Web *Config3LO `json:"web"`
31+
Installed *Config3LO `json:"installed"`
32+
UniverseDomain string `json:"universe_domain"`
3233
}
3334

3435
// ServiceAccountFile representation.
@@ -51,6 +52,7 @@ type UserCredentialsFile struct {
5152
ClientSecret string `json:"client_secret"`
5253
QuotaProjectID string `json:"quota_project_id"`
5354
RefreshToken string `json:"refresh_token"`
55+
UniverseDomain string `json:"universe_domain"`
5456
}
5557

5658
// ExternalAccountFile representation.
@@ -81,6 +83,7 @@ type ExternalAccountAuthorizedUserFile struct {
8183
TokenInfoURL string `json:"token_info_url"`
8284
RevokeURL string `json:"revoke_url"`
8385
QuotaProjectID string `json:"quota_project_id"`
86+
UniverseDomain string `json:"universe_domain"`
8487
}
8588

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

133136
// GDCHServiceAccountFile represents the Google Distributed Cloud Hosted (GDCH) service identity file.
134137
type GDCHServiceAccountFile struct {
135-
Type string `json:"type"`
136-
FormatVersion string `json:"format_version"`
137-
Project string `json:"project"`
138-
Name string `json:"name"`
139-
CertPath string `json:"ca_cert_path"`
140-
PrivateKeyID string `json:"private_key_id"`
141-
PrivateKey string `json:"private_key"`
142-
TokenURL string `json:"token_uri"`
138+
Type string `json:"type"`
139+
FormatVersion string `json:"format_version"`
140+
Project string `json:"project"`
141+
Name string `json:"name"`
142+
CertPath string `json:"ca_cert_path"`
143+
PrivateKeyID string `json:"private_key_id"`
144+
PrivateKey string `json:"private_key"`
145+
TokenURL string `json:"token_uri"`
146+
UniverseDomain string `json:"universe_domain"`
143147
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"type": "external_account",
3+
"audience": "//iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID",
4+
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
5+
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/$EMAIL:generateAccessToken",
6+
"token_url": "https://sts.googleapis.com/v1/token",
7+
"credential_source": {
8+
"url": "http://localhost:5000/token",
9+
"format": {
10+
"type": "json",
11+
"subject_token_field_name": "id_token"
12+
}
13+
},
14+
"universe_domain": "example.com"
15+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"type": "external_account_authorized_user",
3+
"audience": "//iam.googleapis.com/locations/global/workforcePools/$POOL_ID/providers/$PROVIDER_ID",
4+
"client_id": "abc123.apps.googleusercontent.com",
5+
"client_secret": "shh",
6+
"refresh_token": "refreshing",
7+
"token_url": "https://sts.googleapis.com/v1/oauthtoken",
8+
"token_info_url": "https://sts.googleapis.com/v1/info",
9+
"revoke_url": "https://sts.googleapis.com/v1/revoke",
10+
"quota_project_id": "fake_project2",
11+
"universe_domain": "example.com"
12+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[email protected]:generateAccessToken",
3+
"delegates": [
4+
5+
6+
],
7+
"source_credentials": {
8+
"type": "service_account",
9+
"project_id": "fake_project",
10+
"private_key_id": "89asd789789uo473454c47543",
11+
"private_key": "fake",
12+
"client_email": "sa@fake_project.iam.gserviceaccount.com",
13+
"client_id": "gopher",
14+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
15+
"token_uri": "https://oauth2.googleapis.com/token"
16+
},
17+
"type": "impersonated_service_account",
18+
"universe_domain": "example.com"
19+
}

0 commit comments

Comments
 (0)