creds.go 6.4 KB

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