creds.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. // Copyright 2017 Google LLC.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package internal
  5. import (
  6. "context"
  7. "crypto/tls"
  8. "encoding/json"
  9. "errors"
  10. "fmt"
  11. "net"
  12. "net/http"
  13. "os"
  14. "time"
  15. "golang.org/x/oauth2"
  16. "google.golang.org/api/internal/impersonate"
  17. "golang.org/x/oauth2/google"
  18. )
  19. const quotaProjectEnvVar = "GOOGLE_CLOUD_QUOTA_PROJECT"
  20. // Creds returns credential information obtained from DialSettings, or if none, then
  21. // it returns default credential information.
  22. func Creds(ctx context.Context, ds *DialSettings) (*google.Credentials, error) {
  23. creds, err := baseCreds(ctx, ds)
  24. if err != nil {
  25. return nil, err
  26. }
  27. if ds.ImpersonationConfig != nil {
  28. return impersonateCredentials(ctx, creds, ds)
  29. }
  30. return creds, nil
  31. }
  32. func baseCreds(ctx context.Context, ds *DialSettings) (*google.Credentials, error) {
  33. if ds.InternalCredentials != nil {
  34. return ds.InternalCredentials, nil
  35. }
  36. if ds.Credentials != nil {
  37. return ds.Credentials, nil
  38. }
  39. if ds.CredentialsJSON != nil {
  40. return credentialsFromJSON(ctx, ds.CredentialsJSON, ds)
  41. }
  42. if ds.CredentialsFile != "" {
  43. data, err := os.ReadFile(ds.CredentialsFile)
  44. if err != nil {
  45. return nil, fmt.Errorf("cannot read credentials file: %v", err)
  46. }
  47. return credentialsFromJSON(ctx, data, ds)
  48. }
  49. if ds.TokenSource != nil {
  50. return &google.Credentials{TokenSource: ds.TokenSource}, nil
  51. }
  52. cred, err := google.FindDefaultCredentials(ctx, ds.GetScopes()...)
  53. if err != nil {
  54. return nil, err
  55. }
  56. if len(cred.JSON) > 0 {
  57. return credentialsFromJSON(ctx, cred.JSON, ds)
  58. }
  59. // For GAE and GCE, the JSON is empty so return the default credentials directly.
  60. return cred, nil
  61. }
  62. // JSON key file type.
  63. const (
  64. serviceAccountKey = "service_account"
  65. )
  66. // credentialsFromJSON returns a google.Credentials from the JSON data
  67. //
  68. // - A self-signed JWT flow will be executed if the following conditions are
  69. // met:
  70. //
  71. // (1) At least one of the following is true:
  72. // (a) No scope is provided
  73. // (b) Scope for self-signed JWT flow is enabled
  74. // (c) Audiences are explicitly provided by users
  75. // (2) No service account impersontation
  76. //
  77. // - Otherwise, executes standard OAuth 2.0 flow
  78. // More details: google.aip.dev/auth/4111
  79. func credentialsFromJSON(ctx context.Context, data []byte, ds *DialSettings) (*google.Credentials, error) {
  80. var params google.CredentialsParams
  81. params.Scopes = ds.GetScopes()
  82. // Determine configurations for the OAuth2 transport, which is separate from the API transport.
  83. // The OAuth2 transport and endpoint will be configured for mTLS if applicable.
  84. clientCertSource, oauth2Endpoint, err := getClientCertificateSourceAndEndpoint(oauth2DialSettings(ds))
  85. if err != nil {
  86. return nil, err
  87. }
  88. params.TokenURL = oauth2Endpoint
  89. if clientCertSource != nil {
  90. tlsConfig := &tls.Config{
  91. GetClientCertificate: clientCertSource,
  92. }
  93. ctx = context.WithValue(ctx, oauth2.HTTPClient, customHTTPClient(tlsConfig))
  94. }
  95. // By default, a standard OAuth 2.0 token source is created
  96. cred, err := google.CredentialsFromJSONWithParams(ctx, data, params)
  97. if err != nil {
  98. return nil, err
  99. }
  100. // Override the token source to use self-signed JWT if conditions are met
  101. isJWTFlow, err := isSelfSignedJWTFlow(data, ds)
  102. if err != nil {
  103. return nil, err
  104. }
  105. if isJWTFlow {
  106. ts, err := selfSignedJWTTokenSource(data, ds)
  107. if err != nil {
  108. return nil, err
  109. }
  110. cred.TokenSource = ts
  111. }
  112. return cred, err
  113. }
  114. func isSelfSignedJWTFlow(data []byte, ds *DialSettings) (bool, error) {
  115. if (ds.EnableJwtWithScope || ds.HasCustomAudience()) &&
  116. ds.ImpersonationConfig == nil {
  117. // Check if JSON is a service account and if so create a self-signed JWT.
  118. var f struct {
  119. Type string `json:"type"`
  120. // The rest JSON fields are omitted because they are not used.
  121. }
  122. if err := json.Unmarshal(data, &f); err != nil {
  123. return false, err
  124. }
  125. return f.Type == serviceAccountKey, nil
  126. }
  127. return false, nil
  128. }
  129. func selfSignedJWTTokenSource(data []byte, ds *DialSettings) (oauth2.TokenSource, error) {
  130. if len(ds.GetScopes()) > 0 && !ds.HasCustomAudience() {
  131. // Scopes are preferred in self-signed JWT unless the scope is not available
  132. // or a custom audience is used.
  133. return google.JWTAccessTokenSourceWithScope(data, ds.GetScopes()...)
  134. } else if ds.GetAudience() != "" {
  135. // Fallback to audience if scope is not provided
  136. return google.JWTAccessTokenSourceFromJSON(data, ds.GetAudience())
  137. } else {
  138. return nil, errors.New("neither scopes or audience are available for the self-signed JWT")
  139. }
  140. }
  141. // GetQuotaProject retrieves quota project with precedence being: client option,
  142. // environment variable, creds file.
  143. func GetQuotaProject(creds *google.Credentials, clientOpt string) string {
  144. if clientOpt != "" {
  145. return clientOpt
  146. }
  147. if env := os.Getenv(quotaProjectEnvVar); env != "" {
  148. return env
  149. }
  150. if creds == nil {
  151. return ""
  152. }
  153. var v struct {
  154. QuotaProject string `json:"quota_project_id"`
  155. }
  156. if err := json.Unmarshal(creds.JSON, &v); err != nil {
  157. return ""
  158. }
  159. return v.QuotaProject
  160. }
  161. func impersonateCredentials(ctx context.Context, creds *google.Credentials, ds *DialSettings) (*google.Credentials, error) {
  162. if len(ds.ImpersonationConfig.Scopes) == 0 {
  163. ds.ImpersonationConfig.Scopes = ds.GetScopes()
  164. }
  165. ts, err := impersonate.TokenSource(ctx, creds.TokenSource, ds.ImpersonationConfig)
  166. if err != nil {
  167. return nil, err
  168. }
  169. return &google.Credentials{
  170. TokenSource: ts,
  171. ProjectID: creds.ProjectID,
  172. }, nil
  173. }
  174. // oauth2DialSettings returns the settings to be used by the OAuth2 transport, which is separate from the API transport.
  175. func oauth2DialSettings(ds *DialSettings) *DialSettings {
  176. var ods DialSettings
  177. ods.DefaultEndpoint = google.Endpoint.TokenURL
  178. ods.DefaultMTLSEndpoint = google.MTLSTokenURL
  179. ods.ClientCertSource = ds.ClientCertSource
  180. return &ods
  181. }
  182. // customHTTPClient constructs an HTTPClient using the provided tlsConfig, to support mTLS.
  183. func customHTTPClient(tlsConfig *tls.Config) *http.Client {
  184. trans := baseTransport()
  185. trans.TLSClientConfig = tlsConfig
  186. return &http.Client{Transport: trans}
  187. }
  188. func baseTransport() *http.Transport {
  189. return &http.Transport{
  190. Proxy: http.ProxyFromEnvironment,
  191. DialContext: (&net.Dialer{
  192. Timeout: 30 * time.Second,
  193. KeepAlive: 30 * time.Second,
  194. DualStack: true,
  195. }).DialContext,
  196. MaxIdleConns: 100,
  197. MaxIdleConnsPerHost: 100,
  198. IdleConnTimeout: 90 * time.Second,
  199. TLSHandshakeTimeout: 10 * time.Second,
  200. ExpectContinueTimeout: 1 * time.Second,
  201. }
  202. }