Skip to content

Cannot refresh credentials to retrieve an ID token #441

Closed
@GergelyKalmar

Description

@GergelyKalmar

TL;DR

I'm trying to get an ID token to use for authenticating towards Google Cloud Run services in GitHub Actions in Python code, however, for some reason it does not seem to be working.

Expected behavior

Receive the ID token.

Observed behavior

I got the following error:

    ...
    credentials.refresh(request=Request())  # type: ignore[no-untyped-call]
.../lib/python3.8/site-packages/google/auth/external_account.py:397: in refresh
    self._impersonated_credentials.refresh(request)
.../lib/python3.8/site-packages/google/auth/impersonated_credentials.py:250: in refresh
    self._update_token(request)
.../lib/python3.8/site-packages/google/auth/impersonated_credentials.py:282: in _update_token
    self.token, self.expiry = _make_iam_token_request(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <google.auth.transport.requests.Request object at ...>
principal = 'app-tester@....iam.gserviceaccount.com'
headers = {'Content-Type': 'application/json', 'authorization': '*** 'x-allowed-locations': '0x0', 'x-goog-api-client': 'gl-python/3.8.18 auth/2.29.0 auth-request-type/at cred-type/imp'}
body = b'{"delegates": null, "scope": null, "lifetime": "3600s"}'
iam_endpoint_override = 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/app-tester@....iam.gserviceaccount.com:generateAccessToken'

    def _make_iam_token_request(
        request, principal, headers, body, iam_endpoint_override=None
    ):
        """Makes a request to the Google Cloud IAM service for an access token.
        Args:
            request (Request): The Request object to use.
            principal (str): The principal to request an access token for.
            headers (Mapping[str, str]): Map of headers to transmit.
            body (Mapping[str, str]): JSON Payload body for the iamcredentials
                API call.
            iam_endpoint_override (Optiona[str]): The full IAM endpoint override
                with the target_principal embedded. This is useful when supporting
                impersonation with regional endpoints.
    
        Raises:
            google.auth.exceptions.TransportError: Raised if there is an underlying
                HTTP connection error
            google.auth.exceptions.RefreshError: Raised if the impersonated
                credentials are not available.  Common reasons are
                `iamcredentials.googleapis.com` is not enabled or the
                `Service Account Token Creator` is not assigned
        """
        iam_endpoint = iam_endpoint_override or _IAM_ENDPOINT.format(principal)
    
        body = json.dumps(body).encode("utf-8")
    
        response = request(url=iam_endpoint, method="POST", headers=headers, body=body)
    
        # support both string and bytes type response.data
        response_body = (
            response.data.decode("utf-8")
            if hasattr(response.data, "decode")
            else response.data
        )
    
        if response.status != http_client.OK:
>           raise exceptions.RefreshError(_REFRESH_ERROR, response_body)
E           google.auth.exceptions.RefreshError: ('Unable to acquire impersonated credentials', '{\n  "error": {\n    "code": 400,\n    "message": "Request contains an invalid argument.",\n    "status": "INVALID_ARGUMENT"\n  }\n}\n')

.../lib/python3.8/site-packages/google/auth/impersonated_credentials.py:100: RefreshError

Action YAML

See https://github.com/logikal-io/github-workflows/blob/main/.github/workflows/run-python-tests.yml

It is essentially:

- name: Authenticate to Google Cloud Platform
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: ${{ inputs.gcp-testing-workload-identity-provider }}
          service_account: ${{ inputs.gcp-testing-service-account }}

- name: Run pytest
        run: orb --command 'pytest ${{ inputs.pytest-options }}'

Log output

------------------------------ Captured log call -------------------------------
2024-09-12 17:06:04.630 DEBUG Loading default credentials (stormware.google.auth:91)
2024-09-12 17:06:04.630 DEBUG Checking /home/runner/work/.../gha-creds-....json for explicit credentials as part of auth process... (google.auth._default:255)
2024-09-12 17:06:04.631 DEBUG Making request: GET https://pipelinesghubeus7.actions.githubusercontent.com/.../_apis/distributedtask/hubs/Actions/plans.../jobs/.../idtoken?api-version=2.0&audience=https%3A%2F%2Fiam.googleapis.com%2Fprojects%2...%2Flocations%2Fglobal%2FworkloadIdentityPools%2Fci-cd%2Fproviders%2Fgithub-actions (google.auth.transport.requests:185)
2024-09-12 17:06:04.920 DEBUG Making request: POST https://sts.googleapis.com/v1/token (google.auth.transport.requests:185)
2024-09-12 17:06:04.977 DEBUG Making request: POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/app-tester@....iam.gserviceaccount.com:generateAccessToken (google.auth.transport.requests:185)

Additional information

The following code snippet was triggering the error:

from google.auth import default
from google.auth.transport.requests import Request

credentials = default()[0]
credentials.refresh(request=Request())  # <-- here
print(credentials.id_token)

I understand that there is a way to generate an ID token as an output value, however, injecting that value into the Python script seems like a hack as opposed to using the default credential flow. Shouldn't this approach work too?

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions