From 0920706a19e9d22c3d0da43d1db5939c6ad837a8 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Mon, 2 Jun 2025 10:48:14 -0400 Subject: [PATCH] Update troubleshooting docs for Python (#488) Closes https://github.com/google-github-actions/auth/pull/487 --------- Co-authored-by: Abhi Srivastava --- README.md | 1 - docs/EXAMPLES.md | 63 +++++++++++++++++++++++++++++++++++++++++ docs/TROUBLESHOOTING.md | 47 ++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d0116cc..8816eaf 100644 --- a/README.md +++ b/README.md @@ -322,7 +322,6 @@ regardless of the authentication mechanism. "token_format" is "id_token". - ## Setup diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index 145cb67..b798987 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -187,6 +187,69 @@ jobs: run: |- curl https://myapp-uvehjacqzq.a.run.app \ --header "Authorization: Bearer ${{ steps.auth.outputs.id_token }}" + + # Example of using ID token in Python code + - id: 'python-example' + run: |- + python -c " + import os + import requests + + # ID token is available as environment variable + id_token = os.environ.get('GOOGLE_ID_TOKEN', '${{ steps.auth.outputs.id_token }}') + + # Use the token to invoke a Cloud Run service + response = requests.get( + 'https://myapp-uvehjacqzq.a.run.app', + headers={'Authorization': f'Bearer {id_token}'} + ) + print(response.text) + " +``` + +### Using Default Credentials with Scopes in Python + +When using Workload Identity Federation with Python libraries, you may need to +add scopes before refreshing credentials: + +```yaml +jobs: + job_id: + permissions: + contents: 'read' + id-token: 'write' + + steps: + - uses: 'actions/checkout@v4' + + - id: 'auth' + uses: 'google-github-actions/auth@v2' + with: + workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider' + service_account: 'my-service-account@my-project.iam.gserviceaccount.com' + + - id: 'python-auth' + run: |- + python -c " + from google.auth import default + from google.auth.transport.requests import Request + + # Get default credentials + credentials, project = default() + + # Add scopes before refreshing for impersonation + credentials = credentials.with_scopes( + ['https://www.googleapis.com/auth/cloud-platform'] + ) + + # Refresh to get the token + credentials.refresh(request=Request()) + + # Now you can use the credentials + print(f'Access token: {credentials.token}') + if hasattr(credentials, 'id_token'): + print(f'ID token: {credentials.id_token}') + " ``` [github-markdown-toc]: https://github.blog/changelog/2021-04-13-table-of-contents-support-in-markdown-files/ diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 2bf8154..c138e13 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -230,6 +230,53 @@ tool like `jq`: cat credentials.json | jq -r tostring ``` + + +## Cannot refresh credentials to retrieve an ID token + +If you get an error like: + +```text +google.auth.exceptions.RefreshError: ('Unable to acquire impersonated credentials', '{"error": {"code": 400, "message": "Request contains an invalid argument.", "status": "INVALID_ARGUMENT"}}') +``` + +when trying to refresh credentials in Python code to get an ID token, this is +usually because the credentials are missing required scopes. The Google Auth +library requires scopes to be set when refreshing credentials for impersonation. + +To fix this issue, add the required scopes before refreshing: + +```python +from google.auth import default +from google.auth.transport.requests import Request + +credentials, project = default() + +# Add scopes before refreshing +credentials = credentials.with_scopes( + ["https://www.googleapis.com/auth/cloud-platform"] +) +credentials.refresh(request=Request()) + +# Now you can access the ID token +print(credentials.id_token) +``` + +Alternatively, you can use the `token_format` parameter of this action to +generate an ID token directly: + +```yaml +- uses: 'google-github-actions/auth@v2' + with: + workload_identity_provider: ${{ secrets.WIF_PROVIDER }} + service_account: ${{ secrets.WIF_SERVICE_ACCOUNT }} + token_format: 'id_token' + id_token_audience: 'https://example.com' +``` + +This will export the ID token as an environment variable that you can use in +your Python code. + ## Organizational Policy Constraints > **ℹ️ NOTE!** Your Google Cloud organization administrator controls these