@@ -9,11 +9,14 @@ import (
99 "encoding/json"
1010 "fmt"
1111 "net/http"
12+ "path/filepath"
13+ "strings"
1214
1315 "cloud.google.com/go/compute/metadata"
1416 "golang.org/x/oauth2"
1517 "golang.org/x/oauth2/google"
1618
19+ "google.golang.org/api/impersonate"
1720 "google.golang.org/api/internal"
1821 "google.golang.org/api/option"
1922 "google.golang.org/api/option/internaloption"
@@ -25,6 +28,14 @@ import (
2528// ClientOption is for configuring a Google API client or transport.
2629type ClientOption = option.ClientOption
2730
31+ type credentialsType int
32+
33+ const (
34+ unknownCredType credentialsType = iota
35+ serviceAccount
36+ impersonatedServiceAccount
37+ )
38+
2839// NewClient creates a HTTP Client that automatically adds an ID token to each
2940// request via an Authorization header. The token will have the audience
3041// 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
103114}
104115
105116func 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 )
110118 if err != nil {
111119 return nil , err
112120 }
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" )
117165 }
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
129166}
130167
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
132172 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" )
134174 }
135175 var f struct {
136176 Type string `json:"type"`
137177 }
138178 if err := json .Unmarshal (data , & f ); err != nil {
139- return err
179+ return t , err
140180 }
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
143193 }
144- return nil
145194}
146195
147196// WithCustomClaims optionally specifies custom private claims for an ID token.
0 commit comments