Secure Federated Access to Google Cloud: Simulating Access with a Headless OIDC Client
In my previous post, I introduced the concept of Workload Identity Federation (WIF), and showed how to build and deploy a mock OpenID Connect (OIDC) Identity Provider. In this post, we'll walk through the end-to-end mechanics of federated identity access to Google Cloud using a headless OIDC client.
Federated Access Flow with OIDC and Impersonation
To understand how our headless client interacts with the mock OIDC IdP and Google Cloud, let's consider the federated identity flow below.
- The client initiates the flow by requesting an ID token from the OIDC IdP.
- Upon valid authentication, the IdP returns a signed JWT (ID token) that includes key identity claims like
sub
,email
, and custom attributes. - The client sends this ID token to the Security Token Service (STS), which acts as a bridge between Google Cloud and external identities.
- STS consults the linked workload identity pool to determine if the token matches an authorised external identity, and verifies the signature on the ID token using the IdP's JWKS (public key set).
- If the ID token is valid, STS issues a Google Cloud federated access token, scoped for impersonation.
- The client presents the federated access token to Google Cloud IAM to impersonate a service account with access to the target resources.
- Google Cloud returns a short-lived (1 hour default), impersonated access token.
- The client uses this access token to securely interact with Google Cloud services like Cloud Storage, BigQuery etc. and access the resources.
This flow assumes service account impersonation; you can also use direct resource access to grant access directly to external identities using resource-specific roles. While it is useful to understand how WIF works under the hood, Google Cloud abstracts this complexity away if you're using gcloud
or the client libraries.
Setting up Workload Identity Federation on Google Cloud
Now that we understand the federated identity flows, let's set up workload identity federation on Google Cloud. We'll primarily use this guide as reference. In the Google Cloud Console, select a dedicated project with billing enabled, and then enable the IAM
, Resource Manager
, Service Account Credentials
, and Security Token Service
APIs.
gcloud services enable iam.googleapis.com \
iamcredentials.googleapis.com \
sts.googleapis.com \
cloudresourcemanager.googleapis.com
Under IAM & Admin
> Workload Identity Federation
, create a new workload identity pool. Provide a name and Pool ID, and enable the pool. Select OpenID Connect (OIDC)
as the provider, enter a name, Provider ID, and a issuer URL of your mock OIDC IdP. Leave the allowed audiences list empty - this enforces the aud
field in the ID token must exactly match the provider's full resource name.https://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID
If the issuer is not publicly accessible, you have the option to upload a JWK file (JSON) but, in our case, the mock OIDC IdP exposes the JWKS endpoint publicly.
In the provider attribute mapping section, we map google.subject
to assertion.sub
. Optionally, you can also map attribute.email
to assertion.email
, but the gcloud command below doesn't cover this scenario. This mapping allows us to refer to federated identities in IAM bindings using the subject (attribute.sub/gcs-sa
) or the email attribute (attribute.email/gcs-sa@PROJECT_ID.iam.gserviceaccount.com
).
Finally, create service account gcs-sa
with Storage Admin
role, map the external identity directly to a resource via the principalSet
URI, and configure service account impersonation. The format principalSet://...attribute.sub/gcs-sa
binds any OIDC token that comes from this workload identity pool and whose sub
claim matches gcs-sa
.
gcloud iam service-accounts add-iam-policy-binding gcs-sa@PROJECT_ID.iam.gserviceaccount.com \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/attribute.sub/gcs-sa"
You can also define principals as individuals and groups of identities - see this.
Google-issued access tokens are short-lived (default 3600 seconds). You can't currently configure this in the WIF provider, but downstream client requests can limit the scope. Our headless client requests the default cloud-platform
scope.
Building the Headless OIDC Client (Streamlit)
To simulate federated identity flows end-to-end, I built a minimal, interactive OIDC client using Streamlit. This client is deliberately headless and stateless to demonstrate the individual flows. The client offers the following features:
- Fetch OpenID Connect discovery metadata using the
.well-known/openid-configuration
endpoint from the mock IdP - Retrieve and parse the JWKS (JSON Web Key Set) using the
.well-known/jwks.json
endpoint to retrieve the IdP's public keys - Generate mock, signed ID tokens directly by specifying key identity claims like
sub
andemail
. The client simulates a user or workload asserting its identity, but doesn't actually perform a real authentication action - Exchange ID tokens via Google Cloud STS and receive a federated access token, simulating a validation of identity claims
- Impersonate a Google Cloud service account using the federated access token, validating that the workload identity pool mapping is correctly set up
- Use service account access token to invoke Google Cloud APIs and list the GCS buckets, giving final validation that the entire federated flow is working
Deploying the OIDC Client to Cloud Run
To make the OIDC client accessible at a stable URL over HTTPS, let's deploy it to Cloud Run, Google's fully managed container hosting platform. If you are using Google Cloud Shell, clone the GitHub repository, and change to the gcp-oidc-client
directory. Make sure you select the project and authenticate yourself.
gcloud auth login
gcloud config set project YOUR_PROJECT_ID
For ease of deployment, I have created the following Dockerfile
. Create a new Cloud Run service, select Continuously deploy from a repository
, and set up the remote repository with Cloud Build. Select Dockerfile, provide the source location, update container properties if necessary, and, optionally, configure one-click Identity-Aware Proxy (IAP) integration. Once deployed, set up a custom domain mapping if you wish. If all goes well, your OIDC client should now be ready.
# Use the official lightweight Python image.
FROM python:3.12-slim
# Allow statements and log messages to immediately appear in the logs
ENV PYTHONUNBUFFERED True
# Copy local code to the container image
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
ENV PORT=8501
CMD streamlit run ./app.py --server.port=${PORT} --server.address=0.0.0.0
Testing Federated Access Flow with the OIDC Client
Now that your OIDC client is up and running, let's test the available endpoints and various steps of the federated access flow. First, provide the OIDC Issuer URL
, select Get OpenID Configuration
, and click Submit
. You should see the same response as querying the .well-known
endpoint i.e. https://mock-oidc-idp.your-domain.com/.well-known/openid-configuration
.
Next, select Get JWKS
, and click Submit
. You should be able to retrieve the JWT public key set from the location set by the jwks_uri
attribute in the previous response i.e. https://mock-oidc-idp.your-domain.com/.well-known/jwks.json
. This path is specific to the mock OIDC IdP; other OIDC providers may use a different location.
Generate a signed ID token from the OIDC IdP by providing the Subject
and Email
fields. For our environment, let's set Subject
to gcs-sa
, and Email
to gcs-sa@your-project.iam.gserviceaccount.com
, and click Submit
. In the response, you'll get the raw ID token as well as the decoded claims. You can verify the claims at jwt.io too. You'll also get a raw access token and the corresponding decoded claims, but you can ignore that for now.
iss
field must match the mock OIDC IdP URL.The
aud
field in the generated ID token must exactly match the workload identity provider full resource name, something like: //
iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID
Next, exchange the signed ID token with Google Cloud STS for a federated access token. If you are testing the direct access flow, this token should be sufficient, else continue with the impersonation step. Ensure that the audience matches the workload identity provider resource name, as described above.
Use the federated access token to impersonate the service account with access to the Google Cloud resources (in this case, the Google Cloud Storage bucket). Ensure that the service account email is valid. You'll get a service account access token in return from Google Cloud IAM service.
Finally, use the service account access token to list all the Google Cloud Storage (GCS) buckets in the specified project. Ensure that your service account has privileges to list buckets. For this walkthrough, I've granted the Storage Admin
role to the gcs-sa
service account.
Well, that concludes the two-part series on secure federated access to Google Cloud. As with my previous post, I'd like to highlight that both the mock OIDC IdP and the client are designed solely for development and testing purposes. They have critical authentication and authorisations checks missing, and should not be used in production. Nevertheless, this entire walkthrough has helped me understand and appreciate both OIDC and workload identity federation much better. Feel free to clone the repo, deploy the IdP and client, and play around with different options, especially direct access, pool mappings, principalSet bindings, and attribute-based conditions.