1
2
3
4
5 package internal
6
7 import (
8 "context"
9 "crypto/tls"
10 "encoding/json"
11 "errors"
12 "fmt"
13 "net"
14 "net/http"
15 "os"
16 "time"
17
18 "cloud.google.com/go/auth/credentials"
19 "cloud.google.com/go/auth/oauth2adapt"
20 "golang.org/x/oauth2"
21 "google.golang.org/api/internal/cert"
22 "google.golang.org/api/internal/impersonate"
23
24 "golang.org/x/oauth2/google"
25 )
26
27 const quotaProjectEnvVar = "GOOGLE_CLOUD_QUOTA_PROJECT"
28
29
30
31 func Creds(ctx context.Context, ds *DialSettings) (*google.Credentials, error) {
32 if ds.IsNewAuthLibraryEnabled() {
33 return credsNewAuth(ctx, ds)
34 }
35 creds, err := baseCreds(ctx, ds)
36 if err != nil {
37 return nil, err
38 }
39 if ds.ImpersonationConfig != nil {
40 return impersonateCredentials(ctx, creds, ds)
41 }
42 return creds, nil
43 }
44
45 func credsNewAuth(ctx context.Context, settings *DialSettings) (*google.Credentials, error) {
46
47 if settings.InternalCredentials != nil {
48 return settings.InternalCredentials, nil
49 } else if settings.Credentials != nil {
50 return settings.Credentials, nil
51 } else if settings.TokenSource != nil {
52 return &google.Credentials{TokenSource: settings.TokenSource}, nil
53 }
54
55 if settings.AuthCredentials != nil {
56 return oauth2adapt.Oauth2CredentialsFromAuthCredentials(settings.AuthCredentials), nil
57 }
58
59 var useSelfSignedJWT bool
60 var aud string
61 var scopes []string
62
63 if settings.EnableJwtWithScope || len(settings.Audiences) > 0 {
64 useSelfSignedJWT = true
65 }
66
67 if len(settings.Scopes) > 0 {
68 scopes = make([]string, len(settings.Scopes))
69 copy(scopes, settings.Scopes)
70 }
71 if len(settings.Audiences) > 0 {
72 aud = settings.Audiences[0]
73 }
74
75 if len(settings.Scopes) == 0 && aud == "" && len(settings.DefaultScopes) > 0 {
76 scopes = make([]string, len(settings.DefaultScopes))
77 copy(scopes, settings.DefaultScopes)
78 }
79 if len(scopes) == 0 && aud == "" {
80 aud = settings.DefaultAudience
81 }
82
83 creds, err := credentials.DetectDefault(&credentials.DetectOptions{
84 Scopes: scopes,
85 Audience: aud,
86 CredentialsFile: settings.CredentialsFile,
87 CredentialsJSON: settings.CredentialsJSON,
88 UseSelfSignedJWT: useSelfSignedJWT,
89 Client: oauth2.NewClient(ctx, nil),
90 })
91 if err != nil {
92 return nil, err
93 }
94
95 return oauth2adapt.Oauth2CredentialsFromAuthCredentials(creds), nil
96 }
97
98 func baseCreds(ctx context.Context, ds *DialSettings) (*google.Credentials, error) {
99 if ds.InternalCredentials != nil {
100 return ds.InternalCredentials, nil
101 }
102 if ds.Credentials != nil {
103 return ds.Credentials, nil
104 }
105 if ds.CredentialsJSON != nil {
106 return credentialsFromJSON(ctx, ds.CredentialsJSON, ds)
107 }
108 if ds.CredentialsFile != "" {
109 data, err := os.ReadFile(ds.CredentialsFile)
110 if err != nil {
111 return nil, fmt.Errorf("cannot read credentials file: %v", err)
112 }
113 return credentialsFromJSON(ctx, data, ds)
114 }
115 if ds.TokenSource != nil {
116 return &google.Credentials{TokenSource: ds.TokenSource}, nil
117 }
118 cred, err := google.FindDefaultCredentials(ctx, ds.GetScopes()...)
119 if err != nil {
120 return nil, err
121 }
122 if len(cred.JSON) > 0 {
123 return credentialsFromJSON(ctx, cred.JSON, ds)
124 }
125
126 return cred, nil
127 }
128
129
130 const (
131 serviceAccountKey = "service_account"
132 )
133
134
135
136
137
138
139
140
141
142
143
144
145
146 func credentialsFromJSON(ctx context.Context, data []byte, ds *DialSettings) (*google.Credentials, error) {
147 var params google.CredentialsParams
148 params.Scopes = ds.GetScopes()
149
150
151
152 clientCertSource, err := getClientCertificateSource(ds)
153 if err != nil {
154 return nil, err
155 }
156 params.TokenURL = oAuth2Endpoint(clientCertSource)
157 if clientCertSource != nil {
158 tlsConfig := &tls.Config{
159 GetClientCertificate: clientCertSource,
160 }
161 ctx = context.WithValue(ctx, oauth2.HTTPClient, customHTTPClient(tlsConfig))
162 }
163
164
165 cred, err := google.CredentialsFromJSONWithParams(ctx, data, params)
166 if err != nil {
167 return nil, err
168 }
169
170
171 isJWTFlow, err := isSelfSignedJWTFlow(data, ds)
172 if err != nil {
173 return nil, err
174 }
175 if isJWTFlow {
176 ts, err := selfSignedJWTTokenSource(data, ds)
177 if err != nil {
178 return nil, err
179 }
180 cred.TokenSource = ts
181 }
182
183 return cred, err
184 }
185
186 func oAuth2Endpoint(clientCertSource cert.Source) string {
187 if isMTLS(clientCertSource) {
188 return google.MTLSTokenURL
189 }
190 return google.Endpoint.TokenURL
191 }
192
193 func isSelfSignedJWTFlow(data []byte, ds *DialSettings) (bool, error) {
194
195
196 if !ds.IsUniverseDomainGDU() {
197 return typeServiceAccount(data)
198 }
199 if (ds.EnableJwtWithScope || ds.HasCustomAudience()) && ds.ImpersonationConfig == nil {
200 return typeServiceAccount(data)
201 }
202 return false, nil
203 }
204
205
206 func typeServiceAccount(data []byte) (bool, error) {
207 var f struct {
208 Type string `json:"type"`
209
210 }
211 if err := json.Unmarshal(data, &f); err != nil {
212 return false, err
213 }
214 return f.Type == serviceAccountKey, nil
215 }
216
217 func selfSignedJWTTokenSource(data []byte, ds *DialSettings) (oauth2.TokenSource, error) {
218 if len(ds.GetScopes()) > 0 && !ds.HasCustomAudience() {
219
220
221 return google.JWTAccessTokenSourceWithScope(data, ds.GetScopes()...)
222 } else if ds.GetAudience() != "" {
223
224 return google.JWTAccessTokenSourceFromJSON(data, ds.GetAudience())
225 } else {
226 return nil, errors.New("neither scopes or audience are available for the self-signed JWT")
227 }
228 }
229
230
231
232 func GetQuotaProject(creds *google.Credentials, clientOpt string) string {
233 if clientOpt != "" {
234 return clientOpt
235 }
236 if env := os.Getenv(quotaProjectEnvVar); env != "" {
237 return env
238 }
239 if creds == nil {
240 return ""
241 }
242 var v struct {
243 QuotaProject string `json:"quota_project_id"`
244 }
245 if err := json.Unmarshal(creds.JSON, &v); err != nil {
246 return ""
247 }
248 return v.QuotaProject
249 }
250
251 func impersonateCredentials(ctx context.Context, creds *google.Credentials, ds *DialSettings) (*google.Credentials, error) {
252 if len(ds.ImpersonationConfig.Scopes) == 0 {
253 ds.ImpersonationConfig.Scopes = ds.GetScopes()
254 }
255 ts, err := impersonate.TokenSource(ctx, creds.TokenSource, ds.ImpersonationConfig)
256 if err != nil {
257 return nil, err
258 }
259 return &google.Credentials{
260 TokenSource: ts,
261 ProjectID: creds.ProjectID,
262 }, nil
263 }
264
265
266 func customHTTPClient(tlsConfig *tls.Config) *http.Client {
267 trans := baseTransport()
268 trans.TLSClientConfig = tlsConfig
269 return &http.Client{Transport: trans}
270 }
271
272 func baseTransport() *http.Transport {
273 return &http.Transport{
274 Proxy: http.ProxyFromEnvironment,
275 DialContext: (&net.Dialer{
276 Timeout: 30 * time.Second,
277 KeepAlive: 30 * time.Second,
278 DualStack: true,
279 }).DialContext,
280 MaxIdleConns: 100,
281 MaxIdleConnsPerHost: 100,
282 IdleConnTimeout: 90 * time.Second,
283 TLSHandshakeTimeout: 10 * time.Second,
284 ExpectContinueTimeout: 1 * time.Second,
285 }
286 }
287
288
289
290 func ErrUniverseNotMatch(settingsUD, credsUD string) error {
291 return fmt.Errorf(
292 "the configured universe domain (%q) does not match the universe "+
293 "domain found in the credentials (%q). If you haven't configured "+
294 "WithUniverseDomain explicitly, \"googleapis.com\" is the default",
295 settingsUD,
296 credsUD)
297 }
298
View as plain text