Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

feat: Support external_accounts in google.auth.default() & google.auth.load_credentials_from_file() #635

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 95 additions & 14 deletions 109 google/auth/_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
# Valid types accepted for file-based credentials.
_AUTHORIZED_USER_TYPE = "authorized_user"
_SERVICE_ACCOUNT_TYPE = "service_account"
_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE)
_EXTERNAL_ACCOUNT_TYPE = "external_account"
_VALID_TYPES = (_AUTHORIZED_USER_TYPE, _SERVICE_ACCOUNT_TYPE, _EXTERNAL_ACCOUNT_TYPE)

# Help message when no credentials can be found.
_HELP_MESSAGE = """\
Expand Down Expand Up @@ -69,24 +70,32 @@ def _warn_about_problematic_credentials(credentials):
warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING)


def load_credentials_from_file(filename, scopes=None, quota_project_id=None):
def load_credentials_from_file(
filename, scopes=None, quota_project_id=None, request=None
):
"""Loads Google credentials from a file.

The credentials file must be a service account key or stored authorized
user credentials.
The credentials file must be a service account key, stored authorized
user credentials or external account credentials.

Args:
filename (str): The full path to the credentials file.
scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
specified, the credentials will automatically be scoped if
necessary
quota_project_id (Optional[str]): The project ID used for
quota and billing.
quota and billing.
request (Optional[google.auth.transport.Request]): An object used to make
HTTP requests. This is used to determine the associated project ID
for a workload identity pool resource (external account credentials).
If not specified, then it will use a
google.auth.transport.requests.Request client to make requests.

Returns:
Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
credentials and the project ID. Authorized user credentials do not
have the project ID information.
have the project ID information. External account credentials project
IDs may not always be determined.

Raises:
google.auth.exceptions.DefaultCredentialsError: if the file is in the
Expand Down Expand Up @@ -142,6 +151,14 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None):
credentials = credentials.with_quota_project(quota_project_id)
return credentials, info.get("project_id")

elif credential_type == _EXTERNAL_ACCOUNT_TYPE:
credentials, project_id = _get_external_account_credentials(
info, filename, scopes=scopes, request=request
)
if quota_project_id:
credentials = credentials.with_quota_project(quota_project_id)
return credentials, project_id

else:
raise exceptions.DefaultCredentialsError(
"The file {file} does not have a valid type. "
Expand Down Expand Up @@ -172,7 +189,7 @@ def _get_gcloud_sdk_credentials():
return credentials, project_id


def _get_explicit_environ_credentials():
def _get_explicit_environ_credentials(request=None):
"""Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
variable."""
explicit_file = os.environ.get(environment_vars.CREDENTIALS)
Expand All @@ -183,7 +200,7 @@ def _get_explicit_environ_credentials():

if explicit_file is not None:
credentials, project_id = load_credentials_from_file(
os.environ[environment_vars.CREDENTIALS]
os.environ[environment_vars.CREDENTIALS], request=request
)

return credentials, project_id
Expand Down Expand Up @@ -248,6 +265,57 @@ def _get_gce_credentials(request=None):
return None, None


def _get_external_account_credentials(info, filename, scopes=None, request=None):
"""Loads external account Credentials from the parsed external account info.

The credentials information must correspond to a supported external account
credentials.

Args:
info (Mapping[str, str]): The external account info in Google format.
filename (str): The full path to the credentials file.
scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
specified, the credentials will automatically be scoped if
necessary.
request (Optional[google.auth.transport.Request]): An object used to make
HTTP requests. This is used to determine the associated project ID
for a workload identity pool resource (external account credentials).
If not specified, then it will use a
google.auth.transport.requests.Request client to make requests.

Returns:
Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
credentials and the project ID. External account credentials project
IDs may not always be determined.

Raises:
google.auth.exceptions.DefaultCredentialsError: if the info dictionary
is in the wrong format or is missing required information.
"""
# There are currently 2 types of external_account credentials.
try:
# Check if configuration corresponds to an Identity Pool credentials.
from google.auth import identity_pool

credentials = identity_pool.Credentials.from_info(info, scopes=scopes)
except ValueError:
try:
# Check if configuration corresponds to an AWS credentials.
from google.auth import aws

credentials = aws.Credentials.from_info(info, scopes=scopes)
except ValueError:
# If the configuration is invalid or does not correspond to any
# supported external_account credentials, raise an error.
raise exceptions.DefaultCredentialsError(
"Failed to load external account credentials from {}".format(filename)
)
if request is None:
request = google.auth.transport.requests.Request()

return credentials, credentials.get_project_id(request=request)


def default(scopes=None, request=None, quota_project_id=None):
"""Gets the default credentials for the current environment.

Expand All @@ -261,6 +329,15 @@ def default(scopes=None, request=None, quota_project_id=None):
loaded and returned. The project ID returned is the project ID defined
in the service account file if available (some older files do not
contain project ID information).

If the environment variable is set to the path of a valid external
account JSON configuration file, then the configuration file is used to
determine and retrieve the external credentials from the current
environment (AWS, Azure, etc).
These will then be exchanged for Google access tokens via the Google STS
endpoint.
The project ID returned in this case is the one corresponding to the
underlying workload identity pool resource if determinable.
2. If the `Google Cloud SDK`_ is installed and has application default
credentials set they are loaded and returned.

Expand Down Expand Up @@ -304,11 +381,15 @@ def default(scopes=None, request=None, quota_project_id=None):
scopes (Sequence[str]): The list of scopes for the credentials. If
specified, the credentials will automatically be scoped if
necessary.
request (google.auth.transport.Request): An object used to make
HTTP requests. This is used to detect whether the application
is running on Compute Engine. If not specified, then it will
use the standard library http client to make requests.
quota_project_id (Optional[str]): The project ID used for
request (Optional[google.auth.transport.Request]): An object used to make
HTTP requests. This is used to either detect whether the application
is running on Compute Engine or to determine the associated project
ID for a workload identity pool resource (external account
credentials). If not specified, then it will either use the standard
library http client to make requests for Compute Engine credentials
or a google.auth.transport.requests.Request client for external
account credentials.
quota_project_id (Optional[str]): The project ID used for
quota and billing.
Returns:
Tuple[~google.auth.credentials.Credentials, Optional[str]]:
Expand All @@ -328,7 +409,7 @@ def default(scopes=None, request=None, quota_project_id=None):
)

checkers = (
_get_explicit_environ_credentials,
lambda: _get_explicit_environ_credentials(request=request),
_get_gcloud_sdk_credentials,
_get_gae_credentials,
lambda: _get_gce_credentials(request),
Expand Down
13 changes: 8 additions & 5 deletions 13 google/auth/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,11 @@ def __init__(
scopes (Optional[Sequence[str]]): Optional scopes to request during
the authorization grant.

Raises:
google.auth.exceptions.RefreshError: If an error is encountered during
access token retrieval logic.
ValueError: For invalid parameters.

.. note:: Typically one of the helper constructors
:meth:`from_file` or
:meth:`from_info` are used instead of calling the constructor directly.
Expand Down Expand Up @@ -393,11 +398,9 @@ def __init__(
env_id, env_version = (None, None)

if env_id != "aws" or self._cred_verification_url is None:
raise exceptions.GoogleAuthError(
"No valid AWS 'credential_source' provided"
)
raise ValueError("No valid AWS 'credential_source' provided")
elif int(env_version or "") != 1:
raise exceptions.GoogleAuthError(
raise ValueError(
"aws version '{}' is not supported in the current build.".format(
env_version
)
Expand Down Expand Up @@ -666,7 +669,7 @@ def from_info(cls, info, **kwargs):
google.auth.aws.Credentials: The constructed credentials.

Raises:
google.auth.exceptions.GoogleAuthError: For invalid parameters.
ValueError: For invalid parameters.
"""
return cls(
audience=info.get("audience"),
Expand Down
10 changes: 5 additions & 5 deletions 10 google/auth/identity_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def __init__(
Raises:
google.auth.exceptions.RefreshError: If an error is encountered during
access token retrieval logic.
google.auth.exceptions.GoogleAuthError: For invalid parameters.
ValueError: For invalid parameters.

.. note:: Typically one of the helper constructors
:meth:`from_file` or
Expand Down Expand Up @@ -101,7 +101,7 @@ def __init__(
credential_source_format.get("type") or "text"
)
if self._credential_source_format_type not in ["text", "json"]:
raise exceptions.GoogleAuthError(
raise ValueError(
"Invalid credential_source format '{}'".format(
self._credential_source_format_type
)
Expand All @@ -112,15 +112,15 @@ def __init__(
"subject_token_field_name"
)
if self._credential_source_field_name is None:
raise exceptions.GoogleAuthError(
raise ValueError(
"Missing subject_token_field_name for JSON credential_source format"
)
else:
self._credential_source_field_name = None
else:
self._credential_source_file = None
if not self._credential_source_file:
raise exceptions.GoogleAuthError("Missing credential_source file")
raise ValueError("Missing credential_source file")

@_helpers.copy_docstring(external_account.Credentials)
def retrieve_subject_token(self, request):
Expand Down Expand Up @@ -173,7 +173,7 @@ def from_info(cls, info, **kwargs):
credentials.

Raises:
google.auth.exceptions.GoogleAuthError: For invalid parameters.
ValueError: For invalid parameters.
"""
return cls(
audience=info.get("audience"),
Expand Down
7 changes: 6 additions & 1 deletion 7 noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@
"grpcio",
]

ASYNC_DEPENDENCIES = ["pytest-asyncio", "aioresponses", "asynctest"]
ASYNC_DEPENDENCIES = [
"pytest-asyncio",
"aiohttp < 3.7.0dev",
"aioresponses",
"asynctest",
]

BLACK_VERSION = "black==19.3b0"
BLACK_PATHS = [
Expand Down
2 changes: 1 addition & 1 deletion 2 setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"six>=1.9.0",
)

extras = {"aiohttp": "aiohttp >= 3.6.2, < 4.0.0dev; python_version>='3.6'"}
extras = {"aiohttp": "aiohttp >= 3.6.2, < 3.7.0dev; python_version>='3.6'"}

with io.open("README.rst", "r") as fh:
long_description = fh.read()
Expand Down
2 changes: 1 addition & 1 deletion 2 system_tests/noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def configure_cloud_sdk(session, application_default_credentials, project=False)

# Test sesssions

TEST_DEPENDENCIES_ASYNC = ["aiohttp", "pytest-asyncio", "nest-asyncio"]
TEST_DEPENDENCIES_ASYNC = ["aiohttp < 3.7.0dev", "pytest-asyncio", "nest-asyncio"]
TEST_DEPENDENCIES_SYNC = ["pytest", "requests"]
PYTHON_VERSIONS_ASYNC = ["3.7"]
PYTHON_VERSIONS_SYNC = ["2.7", "3.7"]
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.