VictoriaMetrics/vendor/google.golang.org/api/internal/creds.go

240 lines
6.9 KiB
Go
Raw Normal View History

// Copyright 2017 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package internal
import (
"context"
2023-03-15 21:24:12 +01:00
"crypto/tls"
"encoding/json"
2021-06-24 16:33:31 +02:00
"errors"
"fmt"
2023-03-15 21:24:12 +01:00
"net"
"net/http"
"os"
"time"
"golang.org/x/oauth2"
2024-01-26 22:56:37 +01:00
"google.golang.org/api/internal/cert"
2020-09-17 00:43:19 +02:00
"google.golang.org/api/internal/impersonate"
"golang.org/x/oauth2/google"
)
2023-03-15 21:24:12 +01:00
const quotaProjectEnvVar = "GOOGLE_CLOUD_QUOTA_PROJECT"
// Creds returns credential information obtained from DialSettings, or if none, then
// it returns default credential information.
func Creds(ctx context.Context, ds *DialSettings) (*google.Credentials, error) {
2020-09-17 00:43:19 +02:00
creds, err := baseCreds(ctx, ds)
if err != nil {
return nil, err
}
if ds.ImpersonationConfig != nil {
return impersonateCredentials(ctx, creds, ds)
}
return creds, nil
}
func baseCreds(ctx context.Context, ds *DialSettings) (*google.Credentials, error) {
if ds.InternalCredentials != nil {
return ds.InternalCredentials, nil
}
if ds.Credentials != nil {
return ds.Credentials, nil
}
if ds.CredentialsJSON != nil {
2020-12-03 19:16:30 +01:00
return credentialsFromJSON(ctx, ds.CredentialsJSON, ds)
}
if ds.CredentialsFile != "" {
2023-07-07 09:04:32 +02:00
data, err := os.ReadFile(ds.CredentialsFile)
if err != nil {
return nil, fmt.Errorf("cannot read credentials file: %v", err)
}
2020-12-03 19:16:30 +01:00
return credentialsFromJSON(ctx, data, ds)
}
if ds.TokenSource != nil {
return &google.Credentials{TokenSource: ds.TokenSource}, nil
}
2020-12-03 19:16:30 +01:00
cred, err := google.FindDefaultCredentials(ctx, ds.GetScopes()...)
if err != nil {
return nil, err
}
if len(cred.JSON) > 0 {
2020-12-03 19:16:30 +01:00
return credentialsFromJSON(ctx, cred.JSON, ds)
}
// For GAE and GCE, the JSON is empty so return the default credentials directly.
return cred, nil
}
// JSON key file type.
const (
serviceAccountKey = "service_account"
)
2021-06-24 16:33:31 +02:00
// credentialsFromJSON returns a google.Credentials from the JSON data
//
2021-06-24 16:33:31 +02:00
// - A self-signed JWT flow will be executed if the following conditions are
// met:
2022-08-14 23:53:41 +02:00
//
// (1) At least one of the following is true:
2023-10-02 21:49:16 +02:00
// (a) Scope for self-signed JWT flow is enabled
// (b) Audiences are explicitly provided by users
2022-08-14 23:53:41 +02:00
// (2) No service account impersontation
2021-02-01 18:39:00 +01:00
//
2021-06-24 16:33:31 +02:00
// - Otherwise, executes standard OAuth 2.0 flow
// More details: google.aip.dev/auth/4111
2020-12-03 19:16:30 +01:00
func credentialsFromJSON(ctx context.Context, data []byte, ds *DialSettings) (*google.Credentials, error) {
2023-03-15 21:24:12 +01:00
var params google.CredentialsParams
params.Scopes = ds.GetScopes()
// Determine configurations for the OAuth2 transport, which is separate from the API transport.
// The OAuth2 transport and endpoint will be configured for mTLS if applicable.
2024-01-26 22:56:37 +01:00
clientCertSource, err := getClientCertificateSource(ds)
2023-03-15 21:24:12 +01:00
if err != nil {
return nil, err
}
2024-01-26 22:56:37 +01:00
params.TokenURL = oAuth2Endpoint(clientCertSource)
2023-03-15 21:24:12 +01:00
if clientCertSource != nil {
tlsConfig := &tls.Config{
GetClientCertificate: clientCertSource,
}
ctx = context.WithValue(ctx, oauth2.HTTPClient, customHTTPClient(tlsConfig))
}
2021-06-24 16:33:31 +02:00
// By default, a standard OAuth 2.0 token source is created
2023-03-15 21:24:12 +01:00
cred, err := google.CredentialsFromJSONWithParams(ctx, data, params)
if err != nil {
return nil, err
}
2021-02-01 18:39:00 +01:00
2021-06-24 16:33:31 +02:00
// Override the token source to use self-signed JWT if conditions are met
isJWTFlow, err := isSelfSignedJWTFlow(data, ds)
if err != nil {
2021-02-01 18:39:00 +01:00
return nil, err
}
2021-06-24 16:33:31 +02:00
if isJWTFlow {
ts, err := selfSignedJWTTokenSource(data, ds)
2021-02-01 18:39:00 +01:00
if err != nil {
return nil, err
}
2021-02-01 18:39:00 +01:00
cred.TokenSource = ts
}
2021-06-24 16:33:31 +02:00
return cred, err
}
2024-01-26 22:56:37 +01:00
func oAuth2Endpoint(clientCertSource cert.Source) string {
if isMTLS(clientCertSource) {
return google.MTLSTokenURL
}
return google.Endpoint.TokenURL
}
2021-06-24 16:33:31 +02:00
func isSelfSignedJWTFlow(data []byte, ds *DialSettings) (bool, error) {
2024-01-26 22:56:37 +01:00
// For non-GDU universe domains, token exchange is impossible and services
// must support self-signed JWTs with scopes.
if !ds.IsUniverseDomainGDU() {
return typeServiceAccount(data)
}
if (ds.EnableJwtWithScope || ds.HasCustomAudience()) && ds.ImpersonationConfig == nil {
return typeServiceAccount(data)
2021-06-24 16:33:31 +02:00
}
return false, nil
}
2024-01-26 22:56:37 +01:00
// typeServiceAccount checks if JSON data is for a service account.
func typeServiceAccount(data []byte) (bool, error) {
var f struct {
Type string `json:"type"`
// The remaining JSON fields are omitted because they are not used.
}
if err := json.Unmarshal(data, &f); err != nil {
return false, err
}
return f.Type == serviceAccountKey, nil
}
2021-06-24 16:33:31 +02:00
func selfSignedJWTTokenSource(data []byte, ds *DialSettings) (oauth2.TokenSource, error) {
if len(ds.GetScopes()) > 0 && !ds.HasCustomAudience() {
// Scopes are preferred in self-signed JWT unless the scope is not available
// or a custom audience is used.
return google.JWTAccessTokenSourceWithScope(data, ds.GetScopes()...)
} else if ds.GetAudience() != "" {
// Fallback to audience if scope is not provided
return google.JWTAccessTokenSourceFromJSON(data, ds.GetAudience())
} else {
return nil, errors.New("neither scopes or audience are available for the self-signed JWT")
}
}
2020-02-10 22:28:15 +01:00
2023-03-15 21:24:12 +01:00
// GetQuotaProject retrieves quota project with precedence being: client option,
// environment variable, creds file.
func GetQuotaProject(creds *google.Credentials, clientOpt string) string {
if clientOpt != "" {
return clientOpt
}
if env := os.Getenv(quotaProjectEnvVar); env != "" {
return env
}
if creds == nil {
return ""
}
2020-02-10 22:28:15 +01:00
var v struct {
QuotaProject string `json:"quota_project_id"`
}
2023-03-15 21:24:12 +01:00
if err := json.Unmarshal(creds.JSON, &v); err != nil {
2020-02-10 22:28:15 +01:00
return ""
}
return v.QuotaProject
}
2020-09-17 00:43:19 +02:00
func impersonateCredentials(ctx context.Context, creds *google.Credentials, ds *DialSettings) (*google.Credentials, error) {
if len(ds.ImpersonationConfig.Scopes) == 0 {
2020-12-03 19:16:30 +01:00
ds.ImpersonationConfig.Scopes = ds.GetScopes()
2020-09-17 00:43:19 +02:00
}
ts, err := impersonate.TokenSource(ctx, creds.TokenSource, ds.ImpersonationConfig)
if err != nil {
return nil, err
}
return &google.Credentials{
TokenSource: ts,
ProjectID: creds.ProjectID,
}, nil
}
2023-03-15 21:24:12 +01:00
// customHTTPClient constructs an HTTPClient using the provided tlsConfig, to support mTLS.
func customHTTPClient(tlsConfig *tls.Config) *http.Client {
trans := baseTransport()
trans.TLSClientConfig = tlsConfig
return &http.Client{Transport: trans}
}
func baseTransport() *http.Transport {
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}
2024-01-26 22:56:37 +01:00
// ErrUniverseNotMatch composes an error string from the provided universe
// domain sources (DialSettings and Credentials, respectively).
func ErrUniverseNotMatch(settingsUD, credsUD string) error {
return fmt.Errorf(
"the configured universe domain (%q) does not match the universe "+
"domain found in the credentials (%q). If you haven't configured "+
"WithUniverseDomain explicitly, \"googleapis.com\" is the default",
settingsUD,
credsUD)
}