1 // Copyright 2015 The Go Authors. All rights reserved.
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 google
6 7 import (
8 "context"
9 "encoding/json"
10 "fmt"
11 "net/http"
12 "os"
13 "path/filepath"
14 "runtime"
15 "sync"
16 "time"
17 18 "cloud.google.com/go/compute/metadata"
19 "golang.org/x/oauth2"
20 "golang.org/x/oauth2/authhandler"
21 )
22 23 const (
24 adcSetupURL = "https://cloud.google.com/docs/authentication/external/set-up-adc"
25 defaultUniverseDomain = "googleapis.com"
26 )
27 28 // Credentials holds Google credentials, including "Application Default Credentials".
29 // For more details, see:
30 // https://developers.google.com/accounts/docs/application-default-credentials
31 // Credentials from external accounts (workload identity federation) are used to
32 // identify a particular application from an on-prem or non-Google Cloud platform
33 // including Amazon Web Services (AWS), Microsoft Azure or any identity provider
34 // that supports OpenID Connect (OIDC).
35 type Credentials struct {
36 ProjectID string // may be empty
37 TokenSource oauth2.TokenSource
38 39 // JSON contains the raw bytes from a JSON credentials file.
40 // This field may be nil if authentication is provided by the
41 // environment and not with a credentials file, e.g. when code is
42 // running on Google Cloud Platform.
43 JSON []byte
44 45 // UniverseDomainProvider returns the default service domain for a given
46 // Cloud universe. Optional.
47 //
48 // On GCE, UniverseDomainProvider should return the universe domain value
49 // from Google Compute Engine (GCE)'s metadata server. See also [The attached service
50 // account](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa).
51 // If the GCE metadata server returns a 404 error, the default universe
52 // domain value should be returned. If the GCE metadata server returns an
53 // error other than 404, the error should be returned.
54 UniverseDomainProvider func() (string, error)
55 56 udMu sync.Mutex // guards universeDomain
57 // universeDomain is the default service domain for a given Cloud universe.
58 universeDomain string
59 }
60 61 // UniverseDomain returns the default service domain for a given Cloud universe.
62 //
63 // The default value is "googleapis.com".
64 //
65 // Deprecated: Use instead (*Credentials).GetUniverseDomain(), which supports
66 // obtaining the universe domain when authenticating via the GCE metadata server.
67 // Unlike GetUniverseDomain, this method, UniverseDomain, will always return the
68 // default value when authenticating via the GCE metadata server.
69 // See also [The attached service account](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa).
70 func (c *Credentials) UniverseDomain() string {
71 if c.universeDomain == "" {
72 return defaultUniverseDomain
73 }
74 return c.universeDomain
75 }
76 77 // GetUniverseDomain returns the default service domain for a given Cloud
78 // universe. If present, UniverseDomainProvider will be invoked and its return
79 // value will be cached.
80 //
81 // The default value is "googleapis.com".
82 func (c *Credentials) GetUniverseDomain() (string, error) {
83 c.udMu.Lock()
84 defer c.udMu.Unlock()
85 if c.universeDomain == "" && c.UniverseDomainProvider != nil {
86 // On Google Compute Engine, an App Engine standard second generation
87 // runtime, or App Engine flexible, use an externally provided function
88 // to request the universe domain from the metadata server.
89 ud, err := c.UniverseDomainProvider()
90 if err != nil {
91 return "", err
92 }
93 c.universeDomain = ud
94 }
95 // If no UniverseDomainProvider (meaning not on Google Compute Engine), or
96 // in case of any (non-error) empty return value from
97 // UniverseDomainProvider, set the default universe domain.
98 if c.universeDomain == "" {
99 c.universeDomain = defaultUniverseDomain
100 }
101 return c.universeDomain, nil
102 }
103 104 // DefaultCredentials is the old name of Credentials.
105 //
106 // Deprecated: use Credentials instead.
107 type DefaultCredentials = Credentials
108 109 // CredentialsParams holds user supplied parameters that are used together
110 // with a credentials file for building a Credentials object.
111 type CredentialsParams struct {
112 // Scopes is the list OAuth scopes. Required.
113 // Example: https://www.googleapis.com/auth/cloud-platform
114 Scopes []string
115 116 // Subject is the user email used for domain wide delegation (see
117 // https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority).
118 // Optional.
119 Subject string
120 121 // AuthHandler is the AuthorizationHandler used for 3-legged OAuth flow. Required for 3LO flow.
122 AuthHandler authhandler.AuthorizationHandler
123 124 // State is a unique string used with AuthHandler. Required for 3LO flow.
125 State string
126 127 // PKCE is used to support PKCE flow. Optional for 3LO flow.
128 PKCE *authhandler.PKCEParams
129 130 // The OAuth2 TokenURL default override. This value overrides the default TokenURL,
131 // unless explicitly specified by the credentials config file. Optional.
132 TokenURL string
133 134 // EarlyTokenRefresh is the amount of time before a token expires that a new
135 // token will be preemptively fetched. If unset the default value is 10
136 // seconds.
137 //
138 // Note: This option is currently only respected when using credentials
139 // fetched from the GCE metadata server.
140 EarlyTokenRefresh time.Duration
141 142 // UniverseDomain is the default service domain for a given Cloud universe.
143 // Only supported in authentication flows that support universe domains.
144 // This value takes precedence over a universe domain explicitly specified
145 // in a credentials config file or by the GCE metadata server. Optional.
146 UniverseDomain string
147 }
148 149 func (params CredentialsParams) deepCopy() CredentialsParams {
150 paramsCopy := params
151 paramsCopy.Scopes = make([]string, len(params.Scopes))
152 copy(paramsCopy.Scopes, params.Scopes)
153 return paramsCopy
154 }
155 156 // DefaultClient returns an HTTP Client that uses the
157 // DefaultTokenSource to obtain authentication credentials.
158 func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) {
159 ts, err := DefaultTokenSource(ctx, scope...)
160 if err != nil {
161 return nil, err
162 }
163 return oauth2.NewClient(ctx, ts), nil
164 }
165 166 // DefaultTokenSource returns the token source for
167 // "Application Default Credentials".
168 // It is a shortcut for FindDefaultCredentials(ctx, scope).TokenSource.
169 func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) {
170 creds, err := FindDefaultCredentials(ctx, scope...)
171 if err != nil {
172 return nil, err
173 }
174 return creds.TokenSource, nil
175 }
176 177 // FindDefaultCredentialsWithParams searches for "Application Default Credentials".
178 //
179 // It looks for credentials in the following places,
180 // preferring the first location found:
181 //
182 // 1. A JSON file whose path is specified by the
183 // GOOGLE_APPLICATION_CREDENTIALS environment variable.
184 // For workload identity federation, refer to
185 // https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation on
186 // how to generate the JSON configuration file for on-prem/non-Google cloud
187 // platforms.
188 // 2. A JSON file in a location known to the gcloud command-line tool.
189 // On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
190 // On other systems, $HOME/.config/gcloud/application_default_credentials.json.
191 // 3. On Google Compute Engine, Google App Engine standard second generation runtimes
192 // (>= Go 1.11), and Google App Engine flexible environment, it fetches
193 // credentials from the metadata server.
194 func FindDefaultCredentialsWithParams(ctx context.Context, params CredentialsParams) (*Credentials, error) {
195 // Make defensive copy of the slices in params.
196 params = params.deepCopy()
197 198 // First, try the environment variable.
199 const envVar = "GOOGLE_APPLICATION_CREDENTIALS"
200 if filename := os.Getenv(envVar); filename != "" {
201 creds, err := readCredentialsFile(ctx, filename, params)
202 if err != nil {
203 return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err)
204 }
205 return creds, nil
206 }
207 208 // Second, try a well-known file.
209 filename := wellKnownFile()
210 if b, err := os.ReadFile(filename); err == nil {
211 return CredentialsFromJSONWithParams(ctx, b, params)
212 }
213 214 // Third, if we're on Google Compute Engine, an App Engine standard second generation runtime,
215 // or App Engine flexible, use the metadata server.
216 if metadata.OnGCE() {
217 id, _ := metadata.ProjectID()
218 universeDomainProvider := func() (string, error) {
219 universeDomain, err := metadata.Get("universe/universe_domain")
220 if err != nil {
221 if _, ok := err.(metadata.NotDefinedError); ok {
222 // http.StatusNotFound (404)
223 return defaultUniverseDomain, nil
224 } else {
225 return "", err
226 }
227 }
228 return universeDomain, nil
229 }
230 return &Credentials{
231 ProjectID: id,
232 TokenSource: computeTokenSource("", params.EarlyTokenRefresh, params.Scopes...),
233 UniverseDomainProvider: universeDomainProvider,
234 universeDomain: params.UniverseDomain,
235 }, nil
236 }
237 238 // None are found; return helpful error.
239 return nil, fmt.Errorf("google: could not find default credentials. See %v for more information", adcSetupURL)
240 }
241 242 // FindDefaultCredentials invokes FindDefaultCredentialsWithParams with the specified scopes.
243 func FindDefaultCredentials(ctx context.Context, scopes ...string) (*Credentials, error) {
244 var params CredentialsParams
245 params.Scopes = scopes
246 return FindDefaultCredentialsWithParams(ctx, params)
247 }
248 249 // CredentialsFromJSONWithParams obtains Google credentials from a JSON value. The JSON can
250 // represent either a Google Developers Console client_credentials.json file (as in ConfigFromJSON),
251 // a Google Developers service account key file, a gcloud user credentials file (a.k.a. refresh
252 // token JSON), or the JSON configuration file for workload identity federation in non-Google cloud
253 // platforms (see https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation).
254 //
255 // Important: If you accept a credential configuration (credential JSON/File/Stream) from an
256 // external source for authentication to Google Cloud Platform, you must validate it before
257 // providing it to any Google API or library. Providing an unvalidated credential configuration to
258 // Google APIs can compromise the security of your systems and data. For more information, refer to
259 // [Validate credential configurations from external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
260 func CredentialsFromJSONWithParams(ctx context.Context, jsonData []byte, params CredentialsParams) (*Credentials, error) {
261 // Make defensive copy of the slices in params.
262 params = params.deepCopy()
263 264 // First, attempt to parse jsonData as a Google Developers Console client_credentials.json.
265 config, _ := ConfigFromJSON(jsonData, params.Scopes...)
266 if config != nil {
267 return &Credentials{
268 ProjectID: "",
269 TokenSource: authhandler.TokenSourceWithPKCE(ctx, config, params.State, params.AuthHandler, params.PKCE),
270 JSON: jsonData,
271 }, nil
272 }
273 274 // Otherwise, parse jsonData as one of the other supported credentials files.
275 var f credentialsFile
276 if err := json.Unmarshal(jsonData, &f); err != nil {
277 return nil, err
278 }
279 280 universeDomain := f.UniverseDomain
281 if params.UniverseDomain != "" {
282 universeDomain = params.UniverseDomain
283 }
284 // Authorized user credentials are only supported in the googleapis.com universe.
285 if f.Type == userCredentialsKey {
286 universeDomain = defaultUniverseDomain
287 }
288 289 ts, err := f.tokenSource(ctx, params)
290 if err != nil {
291 return nil, err
292 }
293 ts = newErrWrappingTokenSource(ts)
294 return &Credentials{
295 ProjectID: f.ProjectID,
296 TokenSource: ts,
297 JSON: jsonData,
298 universeDomain: universeDomain,
299 }, nil
300 }
301 302 // CredentialsFromJSON invokes CredentialsFromJSONWithParams with the specified scopes.
303 //
304 // Important: If you accept a credential configuration (credential JSON/File/Stream) from an
305 // external source for authentication to Google Cloud Platform, you must validate it before
306 // providing it to any Google API or library. Providing an unvalidated credential configuration to
307 // Google APIs can compromise the security of your systems and data. For more information, refer to
308 // [Validate credential configurations from external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
309 func CredentialsFromJSON(ctx context.Context, jsonData []byte, scopes ...string) (*Credentials, error) {
310 var params CredentialsParams
311 params.Scopes = scopes
312 return CredentialsFromJSONWithParams(ctx, jsonData, params)
313 }
314 315 func wellKnownFile() string {
316 const f = "application_default_credentials.json"
317 if runtime.GOOS == "windows" {
318 return filepath.Join(os.Getenv("APPDATA"), "gcloud", f)
319 }
320 return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f)
321 }
322 323 func readCredentialsFile(ctx context.Context, filename string, params CredentialsParams) (*Credentials, error) {
324 b, err := os.ReadFile(filename)
325 if err != nil {
326 return nil, err
327 }
328 return CredentialsFromJSONWithParams(ctx, b, params)
329 }
330