@@ -9,11 +9,14 @@ import (
9
9
"encoding/json"
10
10
"fmt"
11
11
"net/http"
12
+ "path/filepath"
13
+ "strings"
12
14
13
15
"cloud.google.com/go/compute/metadata"
14
16
"golang.org/x/oauth2"
15
17
"golang.org/x/oauth2/google"
16
18
19
+ "google.golang.org/api/impersonate"
17
20
"google.golang.org/api/internal"
18
21
"google.golang.org/api/option"
19
22
"google.golang.org/api/option/internaloption"
@@ -25,6 +28,14 @@ import (
25
28
// ClientOption is for configuring a Google API client or transport.
26
29
type ClientOption = option.ClientOption
27
30
31
+ type credentialsType int
32
+
33
+ const (
34
+ unknownCredType credentialsType = iota
35
+ serviceAccount
36
+ impersonatedServiceAccount
37
+ )
38
+
28
39
// NewClient creates a HTTP Client that automatically adds an ID token to each
29
40
// request via an Authorization header. The token will have the audience
30
41
// provided and be configured with the supplied options. The parameter audience
@@ -103,45 +114,83 @@ func newTokenSource(ctx context.Context, audience string, ds *internal.DialSetti
103
114
}
104
115
105
116
func tokenSourceFromBytes (ctx context.Context , data []byte , audience string , ds * internal.DialSettings ) (oauth2.TokenSource , error ) {
106
- if err := isServiceAccount (data ); err != nil {
107
- return nil , err
108
- }
109
- cfg , err := google .JWTConfigFromJSON (data , ds .GetScopes ()... )
117
+ allowedType , err := getAllowedType (data )
110
118
if err != nil {
111
119
return nil , err
112
120
}
113
-
114
- customClaims := ds .CustomClaims
115
- if customClaims == nil {
116
- customClaims = make (map [string ]interface {})
121
+ switch allowedType {
122
+ case serviceAccount :
123
+ cfg , err := google .JWTConfigFromJSON (data , ds .GetScopes ()... )
124
+ if err != nil {
125
+ return nil , err
126
+ }
127
+ customClaims := ds .CustomClaims
128
+ if customClaims == nil {
129
+ customClaims = make (map [string ]interface {})
130
+ }
131
+ customClaims ["target_audience" ] = audience
132
+
133
+ cfg .PrivateClaims = customClaims
134
+ cfg .UseIDToken = true
135
+
136
+ ts := cfg .TokenSource (ctx )
137
+ tok , err := ts .Token ()
138
+ if err != nil {
139
+ return nil , err
140
+ }
141
+ return oauth2 .ReuseTokenSource (tok , ts ), nil
142
+ case impersonatedServiceAccount :
143
+ type url struct {
144
+ ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
145
+ }
146
+ var accountURL * url
147
+ if err := json .Unmarshal (data , & accountURL ); err != nil {
148
+ return nil , err
149
+ }
150
+ account := filepath .Base (accountURL .ServiceAccountImpersonationURL )
151
+ account = strings .Split (account , ":" )[0 ]
152
+
153
+ config := impersonate.IDTokenConfig {
154
+ Audience : audience ,
155
+ TargetPrincipal : account ,
156
+ IncludeEmail : true ,
157
+ }
158
+ ts , err := impersonate .IDTokenSource (ctx , config )
159
+ if err != nil {
160
+ return nil , err
161
+ }
162
+ return ts , nil
163
+ default :
164
+ return nil , fmt .Errorf ("idtoken: unsupported credentials type" )
117
165
}
118
- customClaims ["target_audience" ] = audience
119
-
120
- cfg .PrivateClaims = customClaims
121
- cfg .UseIDToken = true
122
-
123
- ts := cfg .TokenSource (ctx )
124
- tok , err := ts .Token ()
125
- if err != nil {
126
- return nil , err
127
- }
128
- return oauth2 .ReuseTokenSource (tok , ts ), nil
129
166
}
130
167
131
- func isServiceAccount (data []byte ) error {
168
+ // getAllowedType returns the credentials type of type credentialsType, and an error.
169
+ // allowed types are "service_account" and "impersonated_service_account"
170
+ func getAllowedType (data []byte ) (credentialsType , error ) {
171
+ var t credentialsType
132
172
if len (data ) == 0 {
133
- return fmt .Errorf ("idtoken: credential provided is 0 bytes" )
173
+ return t , fmt .Errorf ("idtoken: credential provided is 0 bytes" )
134
174
}
135
175
var f struct {
136
176
Type string `json:"type"`
137
177
}
138
178
if err := json .Unmarshal (data , & f ); err != nil {
139
- return err
179
+ return t , err
140
180
}
141
- if f .Type != "service_account" {
142
- return fmt .Errorf ("idtoken: credential must be service_account, found %q" , f .Type )
181
+ t = parseCredType (f .Type )
182
+ return t , nil
183
+ }
184
+
185
+ func parseCredType (typeString string ) credentialsType {
186
+ switch typeString {
187
+ case "service_account" :
188
+ return serviceAccount
189
+ case "impersonated_service_account" :
190
+ return impersonatedServiceAccount
191
+ default :
192
+ return unknownCredType
143
193
}
144
- return nil
145
194
}
146
195
147
196
// WithCustomClaims optionally specifies custom private claims for an ID token.
0 commit comments