settings.go raw
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
5 // Package internal supports the options and transport packages.
6 package internal
7
8 import (
9 "crypto/tls"
10 "errors"
11 "log/slog"
12 "net/http"
13 "os"
14 "strconv"
15 "time"
16
17 "cloud.google.com/go/auth"
18 "golang.org/x/oauth2"
19 "golang.org/x/oauth2/google"
20 "google.golang.org/api/internal/credentialstype"
21 "google.golang.org/api/internal/impersonate"
22 "google.golang.org/grpc"
23 )
24
25 const (
26 newAuthLibEnvVar = "GOOGLE_API_GO_EXPERIMENTAL_ENABLE_NEW_AUTH_LIB"
27 newAuthLibDisabledEnVar = "GOOGLE_API_GO_EXPERIMENTAL_DISABLE_NEW_AUTH_LIB"
28 universeDomainEnvVar = "GOOGLE_CLOUD_UNIVERSE_DOMAIN"
29 defaultUniverseDomain = "googleapis.com"
30 )
31
32 // DialSettings holds information needed to establish a connection with a
33 // Google API service.
34 type DialSettings struct {
35 Endpoint string
36 DefaultEndpoint string
37 DefaultEndpointTemplate string
38 DefaultMTLSEndpoint string
39 Scopes []string
40 DefaultScopes []string
41 EnableJwtWithScope bool
42 TokenSource oauth2.TokenSource
43 Credentials *google.Credentials
44 // Deprecated: Use AuthCredentialsFile instead, due to security risk.
45 CredentialsFile string
46 // Deprecated: Use AuthCredentialsJSON instead, due to security risk.
47 CredentialsJSON []byte
48 InternalCredentials *google.Credentials
49 UserAgent string
50 APIKey string
51 Audiences []string
52 DefaultAudience string
53 HTTPClient *http.Client
54 GRPCDialOpts []grpc.DialOption
55 GRPCConn *grpc.ClientConn
56 GRPCConnPool ConnPool
57 GRPCConnPoolSize int
58 NoAuth bool
59 TelemetryDisabled bool
60 ClientCertSource func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
61 CustomClaims map[string]interface{}
62 SkipValidation bool
63 ImpersonationConfig *impersonate.Config
64 EnableDirectPath bool
65 EnableDirectPathXds bool
66 AllowNonDefaultServiceAccount bool
67 DefaultUniverseDomain string
68 UniverseDomain string
69 AllowHardBoundTokens []string
70 Logger *slog.Logger
71 // Google API system parameters. For more information please read:
72 // https://cloud.google.com/apis/docs/system-parameters
73 QuotaProject string
74 RequestReason string
75
76 // New Auth library Options
77 AuthCredentials *auth.Credentials
78 AuthCredentialsJSON []byte
79 AuthCredentialsFile string
80 AuthCredentialsType credentialstype.CredType
81 EnableNewAuthLibrary bool
82
83 // TODO(b/372244283): Remove after b/358175516 has been fixed
84 EnableAsyncRefreshDryRun func()
85 }
86
87 // GetScopes returns the user-provided scopes, if set, or else falls back to the
88 // default scopes.
89 func (ds *DialSettings) GetScopes() []string {
90 if len(ds.Scopes) > 0 {
91 return ds.Scopes
92 }
93 return ds.DefaultScopes
94 }
95
96 // GetAudience returns the user-provided audience, if set, or else falls back to the default audience.
97 func (ds *DialSettings) GetAudience() string {
98 if ds.HasCustomAudience() {
99 return ds.Audiences[0]
100 }
101 return ds.DefaultAudience
102 }
103
104 // HasCustomAudience returns true if a custom audience is provided by users.
105 func (ds *DialSettings) HasCustomAudience() bool {
106 return len(ds.Audiences) > 0
107 }
108
109 // IsNewAuthLibraryEnabled returns true if the new auth library should be used.
110 func (ds *DialSettings) IsNewAuthLibraryEnabled() bool {
111 // Disabled env is for future rollouts to make sure there is a way to easily
112 // disable this behaviour once we switch in on by default.
113 if b, err := strconv.ParseBool(os.Getenv(newAuthLibDisabledEnVar)); err == nil && b {
114 return false
115 }
116 if ds.EnableNewAuthLibrary {
117 return true
118 }
119 if ds.AuthCredentials != nil {
120 return true
121 }
122 if len(ds.AuthCredentialsJSON) > 0 {
123 return true
124 }
125 if ds.AuthCredentialsFile != "" {
126 return true
127 }
128 if b, err := strconv.ParseBool(os.Getenv(newAuthLibEnvVar)); err == nil {
129 return b
130 }
131 return false
132 }
133
134 // GetAuthCredentialsJSON returns the AuthCredentialsJSON and AuthCredentialsType, if set.
135 // Otherwise it falls back to the deprecated CredentialsJSON with an Unknown type.
136 //
137 // Use AuthCredentialsJSON if provided, as it is the safer, recommended option.
138 // CredentialsJSON is populated by the deprecated WithCredentialsJSON.
139 func (ds *DialSettings) GetAuthCredentialsJSON() ([]byte, credentialstype.CredType) {
140 if len(ds.AuthCredentialsJSON) > 0 {
141 return ds.AuthCredentialsJSON, ds.AuthCredentialsType
142 }
143 return ds.CredentialsJSON, credentialstype.Unknown
144 }
145
146 // GetAuthCredentialsFile returns the AuthCredentialsFile and AuthCredentialsType, if set.
147 // Otherwise it falls back to the deprecated CredentialsFile with an Unknown type.
148 //
149 // Use AuthCredentialsFile if provided, as it is the safer, recommended option.
150 // CredentialsFile is populated by the deprecated WithCredentialsFile.
151 func (ds *DialSettings) GetAuthCredentialsFile() (string, credentialstype.CredType) {
152 if ds.AuthCredentialsFile != "" {
153 return ds.AuthCredentialsFile, ds.AuthCredentialsType
154 }
155 return ds.CredentialsFile, credentialstype.Unknown
156 }
157
158 // Validate reports an error if ds is invalid.
159 func (ds *DialSettings) Validate() error {
160 if ds.SkipValidation {
161 return nil
162 }
163 hasCreds := ds.APIKey != "" || ds.TokenSource != nil || ds.CredentialsFile != "" || ds.Credentials != nil || ds.AuthCredentials != nil || len(ds.AuthCredentialsJSON) > 0 || ds.AuthCredentialsFile != ""
164 if ds.NoAuth && hasCreds {
165 return errors.New("options.WithoutAuthentication is incompatible with any option that provides credentials")
166 }
167 // Credentials should not appear with other options.
168 // AuthCredentials is a special case that may be present with
169 // with other options in order to facilitate automatic conversion of
170 // oauth2 types (old auth) to cloud.google.com/go/auth types (new auth).
171 // We currently allow TokenSource and CredentialsFile to coexist.
172 // TODO(jba): make TokenSource & CredentialsFile an error (breaking change).
173 nCreds := 0
174 if ds.Credentials != nil {
175 nCreds++
176 }
177 if len(ds.CredentialsJSON) > 0 {
178 nCreds++
179 }
180 if len(ds.AuthCredentialsJSON) > 0 {
181 nCreds++
182 }
183 if ds.AuthCredentialsFile != "" {
184 nCreds++
185 }
186 if ds.CredentialsFile != "" {
187 nCreds++
188 }
189 if ds.APIKey != "" {
190 nCreds++
191 }
192 if ds.TokenSource != nil {
193 nCreds++
194 }
195 if len(ds.Scopes) > 0 && len(ds.Audiences) > 0 {
196 return errors.New("WithScopes is incompatible with WithAudience")
197 }
198 // Accept only one form of credentials, except we allow TokenSource and CredentialsFile for backwards compatibility.
199 if nCreds > 1 && !(nCreds == 2 && ds.TokenSource != nil && ds.CredentialsFile != "") {
200 return errors.New("multiple credential options provided")
201 }
202 if ds.GRPCConn != nil && ds.GRPCConnPool != nil {
203 return errors.New("WithGRPCConn is incompatible with WithConnPool")
204 }
205 if ds.HTTPClient != nil && ds.GRPCConnPool != nil {
206 return errors.New("WithHTTPClient is incompatible with WithConnPool")
207 }
208 if ds.HTTPClient != nil && ds.GRPCConn != nil {
209 return errors.New("WithHTTPClient is incompatible with WithGRPCConn")
210 }
211 if ds.HTTPClient != nil && ds.GRPCDialOpts != nil {
212 return errors.New("WithHTTPClient is incompatible with gRPC dial options")
213 }
214 if ds.HTTPClient != nil && ds.QuotaProject != "" {
215 return errors.New("WithHTTPClient is incompatible with QuotaProject")
216 }
217 if ds.HTTPClient != nil && ds.RequestReason != "" {
218 return errors.New("WithHTTPClient is incompatible with RequestReason")
219 }
220 if ds.HTTPClient != nil && ds.ClientCertSource != nil {
221 return errors.New("WithHTTPClient is incompatible with WithClientCertSource")
222 }
223 if ds.ClientCertSource != nil && (ds.GRPCConn != nil || ds.GRPCConnPool != nil || ds.GRPCConnPoolSize != 0 || ds.GRPCDialOpts != nil) {
224 return errors.New("WithClientCertSource is currently only supported for HTTP. gRPC settings are incompatible")
225 }
226 if ds.ImpersonationConfig != nil && len(ds.ImpersonationConfig.Scopes) == 0 && len(ds.Scopes) == 0 {
227 return errors.New("WithImpersonatedCredentials requires scopes being provided")
228 }
229 return nil
230 }
231
232 // GetDefaultUniverseDomain returns the Google default universe domain
233 // ("googleapis.com").
234 func (ds *DialSettings) GetDefaultUniverseDomain() string {
235 return defaultUniverseDomain
236 }
237
238 // GetUniverseDomain returns the default service domain for a given Cloud
239 // universe, with the following precedence:
240 //
241 // 1. A non-empty option.WithUniverseDomain.
242 // 2. A non-empty environment variable GOOGLE_CLOUD_UNIVERSE_DOMAIN.
243 // 3. The default value "googleapis.com".
244 func (ds *DialSettings) GetUniverseDomain() string {
245 if ds.UniverseDomain != "" {
246 return ds.UniverseDomain
247 }
248 if envUD := os.Getenv(universeDomainEnvVar); envUD != "" {
249 return envUD
250 }
251 return defaultUniverseDomain
252 }
253
254 // IsUniverseDomainGDU returns true if the universe domain is the default Google
255 // universe ("googleapis.com").
256 func (ds *DialSettings) IsUniverseDomainGDU() bool {
257 return ds.GetUniverseDomain() == defaultUniverseDomain
258 }
259
260 // GetUniverseDomain returns the default service domain for a given Cloud
261 // universe, from google.Credentials. This wrapper function should be removed
262 // to close https://github.com/googleapis/google-api-go-client/issues/2399.
263 func GetUniverseDomain(creds *google.Credentials) (string, error) {
264 timer := time.NewTimer(time.Second)
265 defer timer.Stop()
266 errors := make(chan error)
267 results := make(chan string)
268
269 go func() {
270 result, err := creds.GetUniverseDomain()
271 if err != nil {
272 errors <- err
273 return
274 }
275 results <- result
276 }()
277
278 select {
279 case <-errors:
280 // An error that is returned before the timer expires is likely to be
281 // connection refused. Temporarily (2024-03-21) return the GDU domain.
282 return defaultUniverseDomain, nil
283 case res := <-results:
284 return res, nil
285 case <-timer.C: // Timer is expired.
286 // If err or res was not returned, it means that creds.GetUniverseDomain()
287 // did not complete in 1s. Assume that MDS is likely never responding to
288 // the endpoint and will timeout. This is the source of issues such as
289 // https://github.com/googleapis/google-cloud-go/issues/9350.
290 // Temporarily (2024-02-02) return the GDU domain. Restore the original
291 // calls to creds.GetUniverseDomain() in grpc/dial.go and http/dial.go
292 // and remove this method to close
293 // https://github.com/googleapis/google-api-go-client/issues/2399.
294 return defaultUniverseDomain, nil
295 }
296 }
297