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

fetch_id_token_credentials doesn't follow AIP-4110 #1331

Open
juzna opened this issue Jun 10, 2023 · 2 comments
Open

fetch_id_token_credentials doesn't follow AIP-4110 #1331

juzna opened this issue Jun 10, 2023 · 2 comments

Comments

@juzna
Copy link
Contributor

juzna commented Jun 10, 2023

AIP-4110 specifies where should client libraries load credentials from. It's correctly used by google.auth.default(), which loads the OAuth credentials (ie credentials with access token).

In our service, we need the same default credentials, but our use case requires OpenID credentials. google.oauth2.id_token.fetch_id_token_credentials() pretty much does this, as it also states in the doc string:

Create the ID Token credentials from the current environment

This function acquires ID token from the environment in the following order. See https://google.aip.dev/auth/4110.

However, there are notable differences in the OpenID flow:

  1. it doesn't support impersonated or external credentials
  2. it doesn't check "gcloud default credentials through its default path", as stated in the AIP

It would be good if fetch_id_token_credentials() would match google.auth.default().

@juzna
Copy link
Contributor Author

juzna commented Jun 10, 2023

We currently use a workaround:

  1. we get default OAuth credentials from google.auth.default()
  2. based on the type, we decide what OpenID credentials should be returned

The following code works, but it's not complete (doesn't support all kinds of credentials) and also does some redundant requests to metadata server.

def fixed_fetch_id_token_credentials(audience: str, request=None):
    """Get OpenID credentials from the current environment.

    NOTE: This is needed only because google.oauth2.id_token.fetch_id_token_credentials doesn't support impersonated
    credentials. Once it does, this function can be removed.
    """
    creds, _project_id = google.auth.default()

    if request is None:
        request = google.auth.transport.requests.Request()

    if isinstance(creds, google.auth.impersonated_credentials.Credentials):
        id_creds = google.auth.impersonated_credentials.IDTokenCredentials(
            creds, audience, include_email=True
        )

    elif isinstance(creds, google.oauth2.service_account.Credentials):
        id_creds = google.oauth2.service_account.IDTokenCredentials(
            signer=creds.signer,
            service_account_email=creds.service_account_email,
            token_uri=creds._token_uri,
            quota_project_id=creds.quota_project_id,
            target_audience=audience,
        )

    elif isinstance(creds, google.auth.compute_engine.credentials.Credentials):
        id_creds = google.auth.compute_engine.credentials.IDTokenCredentials(
            request,
            audience,
            use_metadata_identity_endpoint=True,
            quota_project_id=creds.quota_project_id,
        )

    elif isinstance(creds, google.oauth2.credentials.Credentials):
        raise ValueError(
            "IDTokens are not supported for human accounts. Provide a service account instead."
        )

    else:
        raise ValueError(f"Unknown credentials type {type(creds)}")

    return id_creds

We'd appreciate the library supporting this natively so that we could delete the code.

@stijntratsaertit
Copy link

Indeed! I just came across this difference as well, as in the Go library it's been implemented adherent to the AIP spec.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants