99 "encoding/json"
1010 "fmt"
1111 "net/http"
12+ "os"
1213 "path/filepath"
1314 "strings"
1415
@@ -20,6 +21,7 @@ import (
2021 "cloud.google.com/go/auth/oauth2adapt"
2122 "google.golang.org/api/impersonate"
2223 "google.golang.org/api/internal"
24+ "google.golang.org/api/internal/credentialstype"
2325 "google.golang.org/api/option"
2426 "google.golang.org/api/option/internaloption"
2527 htransport "google.golang.org/api/transport/http"
@@ -30,13 +32,38 @@ import (
3032// ClientOption is for configuring a Google API client or transport.
3133type ClientOption = option.ClientOption
3234
33- type credentialsType int
35+ // CredentialsType specifies the type of JSON credentials being provided
36+ // to a loading function such as [WithAuthCredentialsFile] or
37+ // [WithAuthCredentialsJSON].
38+ type CredentialsType = credentialstype.CredType
3439
3540const (
36- unknownCredType credentialsType = iota
37- serviceAccount
38- impersonatedServiceAccount
39- externalAccount
41+ // ServiceAccount represents a service account file type.
42+ ServiceAccount = credentialstype .ServiceAccount
43+ // AuthorizedUser represents an authorized user credentials file type.
44+ AuthorizedUser = credentialstype .AuthorizedUser
45+ // ImpersonatedServiceAccount represents an impersonated service account file type.
46+ //
47+ // IMPORTANT:
48+ // This credential type does not validate the credential configuration. A security
49+ // risk occurs when a credential configuration configured with malicious urls
50+ // is used.
51+ // You should validate credential configurations provided by untrusted sources.
52+ // See [Security requirements when using credential configurations from an external
53+ // source] https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
54+ // for more details.
55+ ImpersonatedServiceAccount = credentialstype .ImpersonatedServiceAccount
56+ // ExternalAccount represents an external account file type.
57+ //
58+ // IMPORTANT:
59+ // This credential type does not validate the credential configuration. A security
60+ // risk occurs when a credential configuration configured with malicious urls
61+ // is used.
62+ // You should validate credential configurations provided by untrusted sources.
63+ // See [Security requirements when using credential configurations from an external
64+ // source] https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
65+ // for more details.
66+ ExternalAccount = credentialstype .ExternalAccount
4067)
4168
4269// NewClient creates a HTTP Client that automatically adds an ID token to each
@@ -110,11 +137,33 @@ func newTokenSourceNewAuth(ctx context.Context, audience string, ds *internal.Di
110137 if ds .AuthCredentials != nil {
111138 return nil , fmt .Errorf ("idtoken: option.WithTokenProvider not supported" )
112139 }
140+
141+ var credsJSON []byte
142+ var credsType credentialstype.CredType
143+ var err error
144+
145+ credsFile , fileCredsType := ds .GetAuthCredentialsFile ()
146+ if credsFile != "" {
147+ credsJSON , err = os .ReadFile (credsFile )
148+ if err != nil {
149+ return nil , fmt .Errorf ("idtoken: cannot read credentials file: %v" , err )
150+ }
151+ credsType = fileCredsType
152+ } else {
153+ credsJSON , credsType = ds .GetAuthCredentialsJSON ()
154+ }
155+
156+ if credsType != credentialstype .Unknown {
157+ allowed := []credentialstype.CredType {ServiceAccount , ImpersonatedServiceAccount , ExternalAccount }
158+ if err := credentialstype .CheckCredentialType (credsJSON , credsType , allowed ... ); err != nil {
159+ return nil , err
160+ }
161+ }
162+
113163 creds , err := newidtoken .NewCredentials (& newidtoken.Options {
114164 Audience : audience ,
115165 CustomClaims : ds .CustomClaims ,
116- CredentialsFile : ds .CredentialsFile ,
117- CredentialsJSON : ds .CredentialsJSON ,
166+ CredentialsJSON : credsJSON , // Pass the bytes to avoid re-reading the file.
118167 Client : oauth2 .NewClient (ctx , nil ),
119168 Logger : ds .Logger ,
120169 })
@@ -141,12 +190,12 @@ func newTokenSource(ctx context.Context, audience string, ds *internal.DialSetti
141190}
142191
143192func tokenSourceFromBytes (ctx context.Context , data []byte , audience string , ds * internal.DialSettings ) (oauth2.TokenSource , error ) {
144- allowedType , err := getAllowedType (data )
193+ credType , err := credentialstype . GetCredType (data )
145194 if err != nil {
146195 return nil , err
147196 }
148- switch allowedType {
149- case serviceAccount :
197+ switch credType {
198+ case ServiceAccount :
150199 cfg , err := google .JWTConfigFromJSON (data , ds .GetScopes ()... )
151200 if err != nil {
152201 return nil , err
@@ -166,7 +215,7 @@ func tokenSourceFromBytes(ctx context.Context, data []byte, audience string, ds
166215 return nil , err
167216 }
168217 return oauth2 .ReuseTokenSource (tok , ts ), nil
169- case impersonatedServiceAccount , externalAccount :
218+ case ImpersonatedServiceAccount , ExternalAccount :
170219 type url struct {
171220 ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
172221 }
@@ -182,43 +231,13 @@ func tokenSourceFromBytes(ctx context.Context, data []byte, audience string, ds
182231 TargetPrincipal : account ,
183232 IncludeEmail : true ,
184233 }
185- ts , err := impersonate .IDTokenSource (ctx , config , option .WithCredentialsJSON ( data ))
234+ ts , err := impersonate .IDTokenSource (ctx , config , option .WithAuthCredentialsJSON ( credType , data ))
186235 if err != nil {
187236 return nil , err
188237 }
189238 return ts , nil
190239 default :
191- return nil , fmt .Errorf ("idtoken: unsupported credentials type" )
192- }
193- }
194-
195- // getAllowedType returns the credentials type of type credentialsType, and an error.
196- // allowed types are "service_account" and "impersonated_service_account"
197- func getAllowedType (data []byte ) (credentialsType , error ) {
198- var t credentialsType
199- if len (data ) == 0 {
200- return t , fmt .Errorf ("idtoken: credential provided is 0 bytes" )
201- }
202- var f struct {
203- Type string `json:"type"`
204- }
205- if err := json .Unmarshal (data , & f ); err != nil {
206- return t , err
207- }
208- t = parseCredType (f .Type )
209- return t , nil
210- }
211-
212- func parseCredType (typeString string ) credentialsType {
213- switch typeString {
214- case "service_account" :
215- return serviceAccount
216- case "impersonated_service_account" :
217- return impersonatedServiceAccount
218- case "external_account" :
219- return externalAccount
220- default :
221- return unknownCredType
240+ return nil , fmt .Errorf ("idtoken: unsupported credentials type: %q" , credType )
222241 }
223242}
224243
@@ -236,17 +255,93 @@ func (w withCustomClaims) Apply(o *internal.DialSettings) {
236255// WithCredentialsFile returns a ClientOption that authenticates
237256// API calls with the given service account or refresh token JSON
238257// credentials file.
258+ //
259+ // Deprecated: This function is being deprecated because of a potential security risk.
260+ //
261+ // This function does not validate the credential configuration. The security
262+ // risk occurs when a credential configuration is accepted from a source that
263+ // is not under your control and used without validation on your side.
264+ //
265+ // If you know that you will be loading credential configurations of a
266+ // specific type, it is recommended to use a credential-type-specific
267+ // option function.
268+ // This will ensure that an unexpected credential type with potential for
269+ // malicious intent is not loaded unintentionally. You might still have to do
270+ // validation for certain credential types. Please follow the recommendation
271+ // for that function. For example, if you want to load only service accounts,
272+ // you can use [WithAuthCredentialsFile] with [ServiceAccount]:
273+ //
274+ // option.WithAuthCredentialsFile(option.ServiceAccount, "/path/to/file.json")
275+ //
276+ // If you are loading your credential configuration from an untrusted source and have
277+ // not mitigated the risks (e.g. by validating the configuration yourself), make
278+ // these changes as soon as possible to prevent security risks to your environment.
279+ //
280+ // Regardless of the function used, it is always your responsibility to validate
281+ // configurations received from external sources.
239282func WithCredentialsFile (filename string ) ClientOption {
240283 return option .WithCredentialsFile (filename )
241284}
242285
286+ // WithAuthCredentialsFile returns a ClientOption that authenticates API calls
287+ // with the given JSON credentials file and credential type.
288+ //
289+ // Important: If you accept a credential configuration (credential
290+ // JSON/File/Stream) from an external source for authentication to Google
291+ // Cloud Platform, you must validate it before providing it to any Google
292+ // API or library. Providing an unvalidated credential configuration to
293+ // Google APIs can compromise the security of your systems and data. For
294+ // more information, refer to [Validate credential configurations from
295+ // external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
296+ func WithAuthCredentialsFile (credType CredentialsType , filename string ) ClientOption {
297+ return option .WithAuthCredentialsFile (credType , filename )
298+ }
299+
243300// WithCredentialsJSON returns a ClientOption that authenticates
244301// API calls with the given service account or refresh token JSON
245302// credentials.
303+ //
304+ // Deprecated: This function is being deprecated because of a potential security risk.
305+ //
306+ // This function does not validate the credential configuration. The security
307+ // risk occurs when a credential configuration is accepted from a source that
308+ // is not under your control and used without validation on your side.
309+ //
310+ // If you know that you will be loading credential configurations of a
311+ // specific type, it is recommended to use a credential-type-specific
312+ // option function.
313+ // This will ensure that an unexpected credential type with potential for
314+ // malicious intent is not loaded unintentionally. You might still have to do
315+ // validation for certain credential types. Please follow the recommendation
316+ // for that function. For example, if you want to load only service accounts,
317+ // you can use [WithAuthCredentialsJSON] with [ServiceAccount]:
318+ //
319+ // option.WithAuthCredentialsJSON(option.ServiceAccount, json)
320+ //
321+ // If you are loading your credential configuration from an untrusted source and have
322+ // not mitigated the risks (e.g. by validating the configuration yourself), make
323+ // these changes as soon as possible to prevent security risks to your environment.
324+ //
325+ // Regardless of the function used, it is always your responsibility to validate
326+ // configurations received from external sources.
246327func WithCredentialsJSON (p []byte ) ClientOption {
247328 return option .WithCredentialsJSON (p )
248329}
249330
331+ // WithAuthCredentialsJSON returns a ClientOption that authenticates API calls
332+ // with the given JSON credentials and credential type.
333+ //
334+ // Important: If you accept a credential configuration (credential
335+ // JSON/File/Stream) from an external source for authentication to Google
336+ // Cloud Platform, you must validate it before providing it to any Google
337+ // API or library. Providing an unvalidated credential configuration to
338+ // Google APIs can compromise the security of your systems and data. For
339+ // more information, refer to [Validate credential configurations from
340+ // external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
341+ func WithAuthCredentialsJSON (credType CredentialsType , json []byte ) ClientOption {
342+ return option .WithAuthCredentialsJSON (credType , json )
343+ }
344+
250345// WithHTTPClient returns a ClientOption that specifies the HTTP client to use
251346// as the basis of communications. This option may only be used with services
252347// that support HTTP as their communication transport. When used, the
0 commit comments