blob: 3b8c281e72b6b9e0f8deadaef93b232e71b65e40 [file] [log] [blame]
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -07001# Copyright 2015 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Application default credentials.
16
17Implements application default credentials and project ID detection.
18"""
19
20import io
21import json
22import logging
23import os
Thea Flowersa8d93482018-05-31 14:52:06 -070024import warnings
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070025
Danny Hermes895e3692017-11-09 11:35:57 -080026import six
27
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070028from google.auth import environment_vars
29from google.auth import exceptions
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070030import google.auth.transport._http_client
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070031
32_LOGGER = logging.getLogger(__name__)
33
34# Valid types accepted for file-based credentials.
Bu Sun Kim9eec0912019-10-21 17:04:21 -070035_AUTHORIZED_USER_TYPE = "authorized_user"
36_SERVICE_ACCOUNT_TYPE = "service_account"
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070037_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE)
38
39# Help message when no credentials can be found.
Thea Flowersa8d93482018-05-31 14:52:06 -070040_HELP_MESSAGE = """\
41Could not automatically determine credentials. Please set {env} or \
42explicitly create credentials and re-run the application. For more \
43information, please see \
Christopher Wilcoxf1028252018-09-21 10:03:04 -070044https://cloud.google.com/docs/authentication/getting-started
Bu Sun Kim9eec0912019-10-21 17:04:21 -070045""".format(
46 env=environment_vars.CREDENTIALS
47).strip()
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070048
Thea Flowersa8d93482018-05-31 14:52:06 -070049# Warning when using Cloud SDK user credentials
50_CLOUD_SDK_CREDENTIALS_WARNING = """\
51Your application has authenticated using end user credentials from Google \
arithmetic1728f30b45a2020-06-17 23:36:04 -070052Cloud SDK without a quota project. You might receive a "quota exceeded" \
53or "API not enabled" error. We recommend you rerun \
54`gcloud auth application-default login` and make sure a quota project is \
55added. Or you can use service accounts instead. For more information \
56about service accounts, see https://cloud.google.com/docs/authentication/"""
Thea Flowersa8d93482018-05-31 14:52:06 -070057
58
59def _warn_about_problematic_credentials(credentials):
60 """Determines if the credentials are problematic.
61
62 Credentials from the Cloud SDK that are associated with Cloud SDK's project
63 are problematic because they may not have APIs enabled and have limited
64 quota. If this is the case, warn about it.
65 """
66 from google.auth import _cloud_sdk
Bu Sun Kim9eec0912019-10-21 17:04:21 -070067
Thea Flowersa8d93482018-05-31 14:52:06 -070068 if credentials.client_id == _cloud_sdk.CLOUD_SDK_CLIENT_ID:
69 warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING)
70
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070071
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -070072def load_credentials_from_file(
73 filename, scopes=None, default_scopes=None, quota_project_id=None
74):
Bu Sun Kim15d5fa92020-06-18 14:05:40 -070075 """Loads Google credentials from a file.
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070076
77 The credentials file must be a service account key or stored authorized
78 user credentials.
79
80 Args:
81 filename (str): The full path to the credentials file.
Bu Sun Kim15d5fa92020-06-18 14:05:40 -070082 scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
83 specified, the credentials will automatically be scoped if
Bu Sun Kim3dda7b22020-07-09 10:39:39 -070084 necessary
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -070085 default_scopes (Optional[Sequence[str]]): Default scopes passed by a
86 Google client library. Use 'scopes' for user-defined scopes.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -070087 quota_project_id (Optional[str]): The project ID used for
88 quota and billing.
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070089
90 Returns:
91 Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
92 credentials and the project ID. Authorized user credentials do not
93 have the project ID information.
94
95 Raises:
96 google.auth.exceptions.DefaultCredentialsError: if the file is in the
weitaiting6e86c932017-08-12 03:26:59 +080097 wrong format or is missing.
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -070098 """
weitaiting6e86c932017-08-12 03:26:59 +080099 if not os.path.exists(filename):
100 raise exceptions.DefaultCredentialsError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700101 "File {} was not found.".format(filename)
102 )
weitaiting6e86c932017-08-12 03:26:59 +0800103
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700104 with io.open(filename, "r") as file_obj:
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700105 try:
106 info = json.load(file_obj)
Danny Hermes895e3692017-11-09 11:35:57 -0800107 except ValueError as caught_exc:
108 new_exc = exceptions.DefaultCredentialsError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700109 "File {} is not a valid json file.".format(filename), caught_exc
110 )
Danny Hermes895e3692017-11-09 11:35:57 -0800111 six.raise_from(new_exc, caught_exc)
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700112
113 # The type key should indicate that the file is either a service account
114 # credentials file or an authorized user credentials file.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700115 credential_type = info.get("type")
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700116
117 if credential_type == _AUTHORIZED_USER_TYPE:
arithmetic1728772dac62020-03-27 14:34:13 -0700118 from google.oauth2 import credentials
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800119
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700120 try:
Bu Sun Kim15d5fa92020-06-18 14:05:40 -0700121 credentials = credentials.Credentials.from_authorized_user_info(
122 info, scopes=scopes
Bu Sun Kimab2be5d2020-07-15 16:49:27 -0700123 )
Danny Hermes895e3692017-11-09 11:35:57 -0800124 except ValueError as caught_exc:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700125 msg = "Failed to load authorized user credentials from {}".format(filename)
Danny Hermes0a93e872017-11-09 12:18:58 -0800126 new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
Danny Hermes895e3692017-11-09 11:35:57 -0800127 six.raise_from(new_exc, caught_exc)
Bu Sun Kimab2be5d2020-07-15 16:49:27 -0700128 if quota_project_id:
129 credentials = credentials.with_quota_project(quota_project_id)
arithmetic1728f30b45a2020-06-17 23:36:04 -0700130 if not credentials.quota_project_id:
131 _warn_about_problematic_credentials(credentials)
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700132 return credentials, None
133
134 elif credential_type == _SERVICE_ACCOUNT_TYPE:
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800135 from google.oauth2 import service_account
136
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700137 try:
Bu Sun Kim15d5fa92020-06-18 14:05:40 -0700138 credentials = service_account.Credentials.from_service_account_info(
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700139 info, scopes=scopes, default_scopes=default_scopes
Bu Sun Kimab2be5d2020-07-15 16:49:27 -0700140 )
Danny Hermes895e3692017-11-09 11:35:57 -0800141 except ValueError as caught_exc:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700142 msg = "Failed to load service account credentials from {}".format(filename)
Danny Hermes0a93e872017-11-09 12:18:58 -0800143 new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
Danny Hermes895e3692017-11-09 11:35:57 -0800144 six.raise_from(new_exc, caught_exc)
Bu Sun Kimab2be5d2020-07-15 16:49:27 -0700145 if quota_project_id:
146 credentials = credentials.with_quota_project(quota_project_id)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700147 return credentials, info.get("project_id")
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700148
149 else:
150 raise exceptions.DefaultCredentialsError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700151 "The file {file} does not have a valid type. "
152 "Type is {type}, expected one of {valid_types}.".format(
153 file=filename, type=credential_type, valid_types=_VALID_TYPES
154 )
155 )
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700156
157
158def _get_gcloud_sdk_credentials():
159 """Gets the credentials and project ID from the Cloud SDK."""
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800160 from google.auth import _cloud_sdk
161
Vaughan Hiltsecd88d42020-07-21 16:25:51 -0400162 _LOGGER.debug("Checking Cloud SDK credentials as part of auth process...")
163
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700164 # Check if application default credentials exist.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700165 credentials_filename = _cloud_sdk.get_application_default_credentials_path()
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700166
167 if not os.path.isfile(credentials_filename):
Vaughan Hiltsecd88d42020-07-21 16:25:51 -0400168 _LOGGER.debug("Cloud SDK credentials not found on disk; not using them")
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700169 return None, None
170
Bu Sun Kim15d5fa92020-06-18 14:05:40 -0700171 credentials, project_id = load_credentials_from_file(credentials_filename)
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700172
173 if not project_id:
174 project_id = _cloud_sdk.get_project_id()
175
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700176 return credentials, project_id
177
178
179def _get_explicit_environ_credentials():
180 """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
181 variable."""
182 explicit_file = os.environ.get(environment_vars.CREDENTIALS)
183
Vaughan Hiltsecd88d42020-07-21 16:25:51 -0400184 _LOGGER.debug(
185 "Checking %s for explicit credentials as part of auth process...", explicit_file
186 )
187
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700188 if explicit_file is not None:
Bu Sun Kim15d5fa92020-06-18 14:05:40 -0700189 credentials, project_id = load_credentials_from_file(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700190 os.environ[environment_vars.CREDENTIALS]
191 )
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700192
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700193 return credentials, project_id
194
195 else:
196 return None, None
197
198
199def _get_gae_credentials():
200 """Gets Google App Engine App Identity credentials and project ID."""
James Wilson6e0781b2018-12-20 20:38:52 -0500201 # While this library is normally bundled with app_engine, there are
202 # some cases where it's not available, so we tolerate ImportError.
203 try:
Vaughan Hiltsecd88d42020-07-21 16:25:51 -0400204 _LOGGER.debug("Checking for App Engine runtime as part of auth process...")
James Wilson6e0781b2018-12-20 20:38:52 -0500205 import google.auth.app_engine as app_engine
206 except ImportError:
Vaughan Hiltsecd88d42020-07-21 16:25:51 -0400207 _LOGGER.warning("Import of App Engine auth library failed.")
James Wilson6e0781b2018-12-20 20:38:52 -0500208 return None, None
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800209
Jon Wayne Parrott2148fde2016-10-24 13:44:25 -0700210 try:
211 credentials = app_engine.Credentials()
212 project_id = app_engine.get_project_id()
213 return credentials, project_id
214 except EnvironmentError:
Vaughan Hiltsecd88d42020-07-21 16:25:51 -0400215 _LOGGER.debug(
216 "No App Engine library was found so cannot authentication via App Engine Identity Credentials."
217 )
Jon Wayne Parrott2148fde2016-10-24 13:44:25 -0700218 return None, None
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700219
220
221def _get_gce_credentials(request=None):
222 """Gets credentials and project ID from the GCE Metadata Service."""
223 # Ping requires a transport, but we want application default credentials
224 # to require no arguments. So, we'll use the _http_client transport which
225 # uses http.client. This is only acceptable because the metadata server
226 # doesn't do SSL and never requires proxies.
James Wilson6e0781b2018-12-20 20:38:52 -0500227
228 # While this library is normally bundled with compute_engine, there are
229 # some cases where it's not available, so we tolerate ImportError.
230 try:
231 from google.auth import compute_engine
232 from google.auth.compute_engine import _metadata
233 except ImportError:
Vaughan Hiltsecd88d42020-07-21 16:25:51 -0400234 _LOGGER.warning("Import of Compute Engine auth library failed.")
James Wilson6e0781b2018-12-20 20:38:52 -0500235 return None, None
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700236
237 if request is None:
238 request = google.auth.transport._http_client.Request()
239
240 if _metadata.ping(request=request):
241 # Get the project ID.
242 try:
Jon Wayne Parrott5b03ba12016-10-24 13:51:26 -0700243 project_id = _metadata.get_project_id(request=request)
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700244 except exceptions.TransportError:
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700245 project_id = None
246
247 return compute_engine.Credentials(), project_id
248 else:
Vaughan Hiltsecd88d42020-07-21 16:25:51 -0400249 _LOGGER.warning(
250 "Authentication failed using Compute Engine authentication due to unavailable metadata server."
251 )
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700252 return None, None
253
254
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700255def default(scopes=None, request=None, quota_project_id=None, default_scopes=None):
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700256 """Gets the default credentials for the current environment.
257
258 `Application Default Credentials`_ provides an easy way to obtain
259 credentials to call Google APIs for server-to-server or local applications.
260 This function acquires credentials from the environment in the following
261 order:
262
263 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
264 to the path of a valid service account JSON private key file, then it is
265 loaded and returned. The project ID returned is the project ID defined
266 in the service account file if available (some older files do not
267 contain project ID information).
268 2. If the `Google Cloud SDK`_ is installed and has application default
269 credentials set they are loaded and returned.
270
271 To enable application default credentials with the Cloud SDK run::
272
273 gcloud auth application-default login
274
275 If the Cloud SDK has an active project, the project ID is returned. The
276 active project can be set using::
277
278 gcloud config set project
279
280 3. If the application is running in the `App Engine standard environment`_
David Buxton0323cf32020-10-29 21:26:11 +0000281 (first generation) then the credentials and project ID from the
282 `App Identity Service`_ are used.
283 4. If the application is running in `Compute Engine`_ or `Cloud Run`_ or
284 the `App Engine flexible environment`_ or the `App Engine standard
285 environment`_ (second generation) then the credentials and project ID
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700286 are obtained from the `Metadata Service`_.
287 5. If no credentials are found,
288 :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised.
289
290 .. _Application Default Credentials: https://developers.google.com\
291 /identity/protocols/application-default-credentials
292 .. _Google Cloud SDK: https://cloud.google.com/sdk
293 .. _App Engine standard environment: https://cloud.google.com/appengine
294 .. _App Identity Service: https://cloud.google.com/appengine/docs/python\
295 /appidentity/
296 .. _Compute Engine: https://cloud.google.com/compute
297 .. _App Engine flexible environment: https://cloud.google.com\
298 /appengine/flexible
299 .. _Metadata Service: https://cloud.google.com/compute/docs\
300 /storing-retrieving-metadata
David Buxton0323cf32020-10-29 21:26:11 +0000301 .. _Cloud Run: https://cloud.google.com/run
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700302
303 Example::
304
305 import google.auth
306
307 credentials, project_id = google.auth.default()
308
309 Args:
Jon Wayne Parrott8a7e5062016-11-07 16:45:17 -0800310 scopes (Sequence[str]): The list of scopes for the credentials. If
311 specified, the credentials will automatically be scoped if
312 necessary.
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700313 request (google.auth.transport.Request): An object used to make
314 HTTP requests. This is used to detect whether the application
315 is running on Compute Engine. If not specified, then it will
316 use the standard library http client to make requests.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700317 quota_project_id (Optional[str]): The project ID used for
318 quota and billing.
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700319 default_scopes (Optional[Sequence[str]]): Default scopes passed by a
320 Google client library. Use 'scopes' for user-defined scopes.
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700321 Returns:
322 Tuple[~google.auth.credentials.Credentials, Optional[str]]:
323 the current environment's credentials and project ID. Project ID
324 may be None, which indicates that the Project ID could not be
325 ascertained from the environment.
326
327 Raises:
328 ~google.auth.exceptions.DefaultCredentialsError:
329 If no credentials were found, or if the credentials found were
330 invalid.
331 """
Jon Wayne Parrott6dca98c2016-12-01 15:34:59 -0800332 from google.auth.credentials import with_scopes_if_required
333
Jon Wayne Parrottce37cba2016-11-07 16:41:42 -0800334 explicit_project_id = os.environ.get(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700335 environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT)
336 )
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700337
338 checkers = (
339 _get_explicit_environ_credentials,
340 _get_gcloud_sdk_credentials,
341 _get_gae_credentials,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700342 lambda: _get_gce_credentials(request),
343 )
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700344
345 for checker in checkers:
346 credentials, project_id = checker()
347 if credentials is not None:
Bu Sun Kimbf5ce0c2021-02-01 15:17:49 -0700348 credentials = with_scopes_if_required(
349 credentials, scopes, default_scopes=default_scopes
350 )
Bu Sun Kimab2be5d2020-07-15 16:49:27 -0700351 if quota_project_id:
352 credentials = credentials.with_quota_project(quota_project_id)
353
Jacob Hayes15af07b2017-12-13 14:09:47 -0600354 effective_project_id = explicit_project_id or project_id
355 if not effective_project_id:
356 _LOGGER.warning(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700357 "No project ID could be determined. Consider running "
358 "`gcloud config set project` or setting the %s "
359 "environment variable",
360 environment_vars.PROJECT,
361 )
Jacob Hayes15af07b2017-12-13 14:09:47 -0600362 return credentials, effective_project_id
Jon Wayne Parrottaadb3de2016-10-19 09:34:05 -0700363
364 raise exceptions.DefaultCredentialsError(_HELP_MESSAGE)