Skip to content
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

feat: pluggable auth support #1045

Merged
merged 20 commits into from
Jun 28, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
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
149 changes: 149 additions & 0 deletions docs/user-guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,155 @@ Follow the detailed instructions on how to
.. _Configure Workload Identity Federation from an OIDC identity provider:
https://cloud.google.com/iam/docs/access-resources-oidc

Using Executable-sourced credentials with OIDC and SAML
renkelvin marked this conversation as resolved.
Show resolved Hide resolved
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

**Executable-sourced credentials** For executable-sourced credentials, a
local executable is used to retrieve the 3rd party token. The executable
must handle providing a valid, unexpired OIDC ID token or SAML assertion
in JSON format to stdout.

To use executable-sourced credentials, the
``GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES`` environment variable must
be set to ``1``.

To generate an executable-sourced workload identity configuration, run
the following command:

.. code:: bash

# Generate a configuration file for executable-sourced credentials.
gcloud iam workload-identity-pools create-cred-config \
projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID \
--service-account=$SERVICE_ACCOUNT_EMAIL \
--subject-token-type=$SUBJECT_TOKEN_TYPE \
# The absolute path for the program, including arguments.
# e.g. --executable-command="/path/to/command --foo=bar"
--executable-command=$EXECUTABLE_COMMAND \
# Optional argument for the executable timeout. Defaults to 30s.
# --executable-timeout-millis=$EXECUTABLE_TIMEOUT \
# Optional argument for the absolute path to the executable output file.
# See below on how this argument impacts the library behaviour.
# --executable-output-file=$EXECUTABLE_OUTPUT_FILE \
--output-file /path/to/generated/config.json

Where the following variables need to be substituted: -
``$PROJECT_NUMBER``: The Google Cloud project number. - ``$POOL_ID``:
The workload identity pool ID. - ``$PROVIDER_ID``: The OIDC or SAML
provider ID. - ``$SERVICE_ACCOUNT_EMAIL``: The email of the service
account to impersonate. - ``$SUBJECT_TOKEN_TYPE``: The subject token
type. - ``$EXECUTABLE_COMMAND``: The full command to run, including
arguments. Must be an absolute path to the program.

The ``--executable-timeout-millis`` flag is optional. This is the
duration for which the auth library will wait for the executable to
finish, in milliseconds. Defaults to 30 seconds when not provided. The
maximum allowed value is 2 minutes. The minimum is 5 seconds.

The ``--executable-output-file`` flag is optional. If provided, the file
path must point to the 3PI credential response generated by the
executable. This is useful for caching the credentials. By specifying
this path, the Auth libraries will first check for its existence before
running the executable. By caching the executable JSON response to this
file, it improves performance as it avoids the need to run the
executable until the cached credentials in the output file are expired.
The executable must handle writing to this file - the auth libraries
will only attempt to read from this location. The format of contents in
the file should match the JSON format expected by the executable shown
below.

To retrieve the 3rd party token, the library will call the executable
using the command specified. The executable’s output must adhere to the
response format specified below. It must output the response to stdout.

A sample successful executable OIDC response:

.. code:: json

{
"version": 1,
"success": true,
"token_type": "urn:ietf:params:oauth:token-type:id_token",
"id_token": "HEADER.PAYLOAD.SIGNATURE",
"expiration_time": 1620499962
}

A sample successful executable SAML response:

.. code:: json

{
"version": 1,
"success": true,
"token_type": "urn:ietf:params:oauth:token-type:saml2",
"saml_response": "...",
"expiration_time": 1620499962
}

A sample executable error response:

.. code:: json

{
"version": 1,
"success": false,
"code": "401",
"message": "Caller not authorized."
}

These are all required fields for an error response. The code and
message fields will be used by the library as part of the thrown
exception.

Response format fields summary: \* ``version``: The version of the JSON
output. Currently only version 1 is supported. \* ``success``: The
status of the response. When true, the response must contain the 3rd
party token, token type, and expiration. The executable must also exit
with exit code 0. When false, the response must contain the error code
and message fields and exit with a non-zero value. \* ``token_type``:
The 3rd party subject token type. Must be
*urn:ietf:params:oauth:token-type:jwt*,
*urn:ietf:params:oauth:token-type:id_token*, or
*urn:ietf:params:oauth:token-type:saml2*. \* ``id_token``: The 3rd party
OIDC token. \* ``saml_response``: The 3rd party SAML response. \*
``expiration_time``: The 3rd party subject token expiration time in
seconds (unix epoch time). \* ``code``: The error code string. \*
``message``: The error message.

All response types must include both the ``version`` and ``success``
fields. \* Successful responses must include the ``token_type``,
``expiration_time``, and one of ``id_token`` or ``saml_response``. \*
Error responses must include both the ``code`` and ``message`` fields.

The library will populate the following environment variables when the
executable is run: \* ``GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE``: The audience
field from the credential configuration. Always present. \*
``GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL``: The service account
email. Only present when service account impersonation is used. \*
``GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE``: The output file location from
the credential configuration. Only present when specified in the
credential configuration.

These environment variables can be used by the executable to avoid
hard-coding these values.

Security considerations

The following security practices are highly recommended:
\* Access to the script should be restricted as it will be displaying
credentials to stdout. This ensures that rogue processes do not gain
access to the script. \* The configuration file should not be
modifiable. Write access should be restricted to avoid processes
modifying the executable command portion.

Given the complexity of using executable-sourced credentials, it is
recommended to use the existing supported mechanisms
(file-sourced/URL-sourced) for providing 3rd party credentials unless
they do not meet your specific requirements.

You can now `use the Auth library <#using-external-identities>`__ to
call Google Cloud resources from an OIDC or SAML provider.

Using External Identities
~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
11 changes: 10 additions & 1 deletion google/auth/_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,14 +317,23 @@ def _get_external_account_credentials(
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.
# There are currently 3 types of external_account credentials.
if info.get("subject_token_type") == _AWS_SUBJECT_TOKEN_TYPE:
# Check if configuration corresponds to an AWS credentials.
from google.auth import aws

credentials = aws.Credentials.from_info(
info, scopes=scopes, default_scopes=default_scopes
)
elif (
info.get("credential_source") is not None
and info.get("credential_source").get("executable") is not None
):
from google.auth import pluggable

credentials = pluggable.Credentials.from_info(
info, scopes=scopes, default_scopes=default_scopes
)
else:
try:
# Check if configuration corresponds to an Identity Pool credentials.
Expand Down
Loading