Skip to content

Commit f9869f7

Browse files
authored
fix(auth): enable self-signed JWT for non-GDU universe domain (#10831)
* default to UseSelfSignedJWT=true for non-GDU service account flows * add error for UseSelfSignedJWT without aud or scope
1 parent 6720291 commit f9869f7

File tree

5 files changed

+75
-23
lines changed

5 files changed

+75
-23
lines changed

auth/credentials/detect_test.go

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -459,11 +459,10 @@ func TestDefaultCredentials_ServiceAccountKeySelfSigned_UniverseDomain(t *testin
459459
now = func() time.Time { return time.Date(2000, 2, 1, 12, 30, 0, 0, time.UTC) }
460460
defer func() { now = oldNow }()
461461
wantTok := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiY2RlZjEyMzQ1Njc4OTAifQ.eyJpc3MiOiJnb3BoZXJAZmFrZV9wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwic2NvcGUiOiJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9hdXRoL2Nsb3VkLXBsYXRmb3JtIiwiZXhwIjo5NDk0MTE4MDAsImlhdCI6OTQ5NDA4MjAwLCJhdWQiOiIiLCJzdWIiOiJnb3BoZXJAZmFrZV9wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIn0.n9Hggd-1Vw4WTQiWkh7q9r5eDsz-khU5vwkZl2VmgdUF3ZxDq1ARzchCNtTifeorzbp9C0i0vCr855G7FZkVCJXPVMcnxbwfMSafUYmVsmutbQiV9eTWfWM0_Ljiwa9GEbv1bN06Lz4LrelPKEaxsDbY6tU8LJUiome_gSMLfLk"
462-
463462
creds, err := DetectDefault(&DetectOptions{
464-
CredentialsJSON: b,
465-
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
466-
UseSelfSignedJWT: true,
463+
// default scopes are set in resolveDetectOptions before calling DetectDefault.
464+
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
465+
CredentialsJSON: b,
467466
})
468467
if err != nil {
469468
t.Fatal(err)
@@ -805,26 +804,29 @@ func TestDefaultCredentials_UniverseDomain(t *testing.T) {
805804
{
806805
name: "service account json with file universe domain",
807806
opts: &DetectOptions{
808-
CredentialsFile: "../internal/testdata/sa_universe_domain.json",
809-
UseSelfSignedJWT: true,
807+
CredentialsFile: "../internal/testdata/sa_universe_domain.json",
808+
// default scopes are set in resolveDetectOptions before calling DetectDefault.
809+
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
810810
},
811811
want: "example.com",
812812
},
813813
{
814814
name: "service account json with options universe domain",
815815
opts: &DetectOptions{
816-
CredentialsFile: "../internal/testdata/sa.json",
817-
UseSelfSignedJWT: true,
818-
UniverseDomain: "foo.com",
816+
CredentialsFile: "../internal/testdata/sa.json",
817+
// default scopes are set in resolveDetectOptions before calling DetectDefault.
818+
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
819+
UniverseDomain: "foo.com",
819820
},
820821
want: "foo.com",
821822
},
822823
{
823824
name: "service account json with file and options universe domain",
824825
opts: &DetectOptions{
825-
CredentialsFile: "../internal/testdata/sa_universe_domain.json",
826-
UseSelfSignedJWT: true,
827-
UniverseDomain: "foo.com",
826+
CredentialsFile: "../internal/testdata/sa_universe_domain.json",
827+
// default scopes are set in resolveDetectOptions before calling DetectDefault.
828+
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
829+
UniverseDomain: "foo.com",
828830
},
829831
want: "foo.com",
830832
},
@@ -923,8 +925,9 @@ func TestDefaultCredentials_UniverseDomain(t *testing.T) {
923925
{
924926
name: "impersonated service account json",
925927
opts: &DetectOptions{
926-
CredentialsFile: "../internal/testdata/imp.json",
927-
UseSelfSignedJWT: true,
928+
CredentialsFile: "../internal/testdata/imp.json",
929+
// default scopes are set in resolveDetectOptions before calling DetectDefault.
930+
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
928931
},
929932
want: "googleapis.com",
930933
},
@@ -938,17 +941,20 @@ func TestDefaultCredentials_UniverseDomain(t *testing.T) {
938941
{
939942
name: "impersonated service account json with options universe domain",
940943
opts: &DetectOptions{
941-
CredentialsFile: "../internal/testdata/imp.json",
942-
UseSelfSignedJWT: true,
943-
UniverseDomain: "foo.com",
944+
CredentialsFile: "../internal/testdata/imp.json",
945+
// default scopes are set in resolveDetectOptions before calling DetectDefault.
946+
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
947+
UniverseDomain: "foo.com",
944948
},
945949
want: "foo.com",
946950
},
947951
{
948952
name: "impersonated service account json with file and options universe domain",
949953
opts: &DetectOptions{
950954
CredentialsFile: "../internal/testdata/imp_universe_domain.json",
951-
UniverseDomain: "foo.com",
955+
// default scopes are set in resolveDetectOptions before calling DetectDefault.
956+
Scopes: []string{"https://www.googleapis.com/auth/cloud-platform"},
957+
UniverseDomain: "foo.com",
952958
},
953959
want: "foo.com",
954960
},

auth/credentials/filetypes.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,14 @@ func resolveUniverseDomain(optsUniverseDomain, fileUniverseDomain string) string
124124
}
125125

126126
func handleServiceAccount(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
127+
ud := resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
127128
if opts.UseSelfSignedJWT {
128129
return configureSelfSignedJWT(f, opts)
130+
} else if ud != "" && ud != internalauth.DefaultUniverseDomain {
131+
// For non-GDU universe domains, token exchange is impossible and services
132+
// must support self-signed JWTs.
133+
opts.UseSelfSignedJWT = true
134+
return configureSelfSignedJWT(f, opts)
129135
}
130136
opts2LO := &auth.Options2LO{
131137
Email: f.ClientEmail,

auth/credentials/selfsignedjwt.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package credentials
1717
import (
1818
"context"
1919
"crypto/rsa"
20+
"errors"
2021
"fmt"
2122
"strings"
2223
"time"
@@ -35,6 +36,9 @@ var (
3536
// configureSelfSignedJWT uses the private key in the service account to create
3637
// a JWT without making a network call.
3738
func configureSelfSignedJWT(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
39+
if len(opts.scopes()) == 0 && opts.Audience == "" {
40+
return nil, errors.New("credentials: both scopes and audience are empty")
41+
}
3842
pk, err := internal.ParseKey([]byte(f.PrivateKey))
3943
if err != nil {
4044
return nil, fmt.Errorf("credentials: could not parse key: %w", err)

auth/credentials/selfsignedjwt_test.go

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ var jwtJSONKey = []byte(`{
4040
"audience": "https://testpervice.googleapis.com/"
4141
}`)
4242

43-
func TestDefaultCredentials_SelfSignedJSON(t *testing.T) {
43+
func TestDetectDefault_SelfSignedJSON(t *testing.T) {
4444
privateKey, jsonKey, err := setupFakeKey()
4545
if err != nil {
4646
t.Fatal(err)
@@ -51,7 +51,7 @@ func TestDefaultCredentials_SelfSignedJSON(t *testing.T) {
5151
UseSelfSignedJWT: true,
5252
})
5353
if err != nil {
54-
t.Fatalf("DefaultCredentials(%s): %v", jsonKey, err)
54+
t.Fatalf("DetectDefault(%s): %v", jsonKey, err)
5555
}
5656

5757
tok, err := tp.Token(context.Background())
@@ -102,7 +102,7 @@ func TestDefaultCredentials_SelfSignedJSON(t *testing.T) {
102102
}
103103
}
104104

105-
func TestDefaultCredentials_SelfSignedWithScope(t *testing.T) {
105+
func TestDetectDefault_SelfSignedWithScope(t *testing.T) {
106106
privateKey, jsonKey, err := setupFakeKey()
107107
if err != nil {
108108
t.Fatal(err)
@@ -113,7 +113,7 @@ func TestDefaultCredentials_SelfSignedWithScope(t *testing.T) {
113113
UseSelfSignedJWT: true,
114114
})
115115
if err != nil {
116-
t.Fatalf("DefaultCredentials(%s): %v", jsonKey, err)
116+
t.Fatalf("DetectDefault(%s): %v", jsonKey, err)
117117
}
118118

119119
tok, err := tp.Token(context.Background())
@@ -164,6 +164,42 @@ func TestDefaultCredentials_SelfSignedWithScope(t *testing.T) {
164164
}
165165
}
166166

167+
func TestDetectDefault_SelfSignedWithAudienceAndScope(t *testing.T) {
168+
_, jsonKey, err := setupFakeKey()
169+
if err != nil {
170+
t.Fatal(err)
171+
}
172+
_, err = DetectDefault(&DetectOptions{
173+
CredentialsJSON: jsonKey,
174+
Audience: "audience",
175+
Scopes: []string{"scope1", "scope2"},
176+
UseSelfSignedJWT: true,
177+
})
178+
if err == nil {
179+
t.Fatal("DetectDefault(): want non-nil err")
180+
}
181+
if want := "credentials: both scopes and audience were provided"; err.Error() != want {
182+
t.Errorf("TokenType = %q, want %q", err, want)
183+
}
184+
}
185+
186+
func TestDetectDefault_SelfSignedWithoutAudienceOrScope(t *testing.T) {
187+
_, jsonKey, err := setupFakeKey()
188+
if err != nil {
189+
t.Fatal(err)
190+
}
191+
_, err = DetectDefault(&DetectOptions{
192+
CredentialsJSON: jsonKey,
193+
UseSelfSignedJWT: true,
194+
})
195+
if err == nil {
196+
t.Fatal("DetectDefault(): want non-nil err")
197+
}
198+
if want := "credentials: both scopes and audience are empty"; err.Error() != want {
199+
t.Errorf("DetectDefault = %q, want %q", err, want)
200+
}
201+
}
202+
167203
// setupFakeKey generates a key we can use in the test data.
168204
func setupFakeKey() (*rsa.PrivateKey, []byte, error) {
169205
// Generate a key we can use in the test data.

auth/internal/testdata/imp_universe_domain.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"type": "service_account",
99
"project_id": "fake_project",
1010
"private_key_id": "89asd789789uo473454c47543",
11-
"private_key": "fake",
11+
"private_key": "-----BEGIN PRIVATE KEY-----\nMIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALX0PQoe1igW12ikv1bN/r9lN749y2ijmbc/mFHPyS3hNTyOCjDvBbXYbDhQJzWVUikh4mvGBA07qTj79Xc3yBDfKP2IeyYQIFe0t0zkd7R9Zdn98Y2rIQC47aAbDfubtkU1U72t4zL11kHvoa0/RuFZjncvlr42X7be7lYh4p3NAgMBAAECgYASk5wDw4Az2ZkmeuN6Fk/y9H+Lcb2pskJIXjrL533vrDWGOC48LrsThMQPv8cxBky8HFSEklPpkfTF95tpD43iVwJRB/GrCtGTw65IfJ4/tI09h6zGc4yqvIo1cHX/LQ+SxKLGyir/dQM925rGt/VojxY5ryJR7GLbCzxPnJm/oQJBANwOCO6D2hy1LQYJhXh7O+RLtA/tSnT1xyMQsGT+uUCMiKS2bSKx2wxo9k7h3OegNJIu1q6nZ6AbxDK8H3+d0dUCQQDTrPSXagBxzp8PecbaCHjzNRSQE2in81qYnrAFNB4o3DpHyMMY6s5ALLeHKscEWnqP8Ur6X4PvzZecCWU9BKAZAkAutLPknAuxSCsUOvUfS1i87ex77Ot+w6POp34pEX+UWb+u5iFn2cQacDTHLV1LtE80L8jVLSbrbrlH43H0DjU5AkEAgidhycxS86dxpEljnOMCw8CKoUBd5I880IUahEiUltk7OLJYS/Ts1wbn3kPOVX3wyJs8WBDtBkFrDHW2ezth2QJADj3e1YhMVdjJW5jqwlD/VNddGjgzyunmiZg0uOXsHXbytYmsA545S8KRQFaJKFXYYFo2kOjqOiC1T2cAzMDjCQ==\n-----END PRIVATE KEY-----\n",
1212
"client_email": "sa@fake_project.iam.gserviceaccount.com",
1313
"client_id": "gopher",
1414
"auth_uri": "https://accounts.google.com/o/oauth2/auth",

0 commit comments

Comments
 (0)