OIDC Federation from GKE to AWS S3
The Point
An application deployed on GKE can use OIDC federation to exchange a Kubernetes-issued JWT token for temporary AWS credentials, eliminating the need to hardcode AWS Access Keys.
Explanation
What OIDC does in this scenario
OIDC (OpenID Connect) is essentially a protocol for “let me prove this identity is real.” In this use case, GKE acts as the OIDC Identity Provider (IdP), and AWS is the party that trusts what GKE says.
The overall flow looks like this:
GKE Pod
│
│ 1. Kubernetes automatically mounts a Service Account JWT token on the Pod
│ (this token contains the workload's identity info, signed by GKE)
│
▼
AWS STS (AssumeRoleWithWebIdentity)
│
│ 2. Submit the JWT token to AWS STS
│ AWS validates the token against GKE's OIDC endpoint
│
▼
Temporary AWS credentials (Access Key + Secret + Session Token)
│
│ 3. Use temporary credentials to operate on S3
│
▼
S3 Bucket ✓sequenceDiagram
participant Pod as GKE Pod
participant STS as AWS STS
participant OIDC as GKE OIDC
participant S3
Note over Pod: K8s mounts JWT token
Pod->>STS: AssumeRoleWithWebIdentity(JWT)
STS->>OIDC: Verify signature
OIDC-->>STS: OK
STS-->>Pod: Temporary credentials (with TTL)
Pod->>S3: Upload CSV
S3-->>Pod: 200 OK
What needs to be configured in Terraform
- AWS IAM OIDC Provider – tells AWS “I trust tokens issued by this GKE cluster”; requires the GKE OIDC issuer URL and thumbprint
- AWS IAM Role + Trust Policy – defines which GKE service account can assume this role; the condition typically requires the
subclaim to matchsystem:serviceaccount:<namespace>:<ksa-name> - S3 Permission – attach the S3 read/write policy to this IAM Role
Go STS package upgrade
In the Go application, use AssumeRoleWithWebIdentity, passing in the JWT token path read from the Pod (typically /var/run/secrets/kubernetes.io/serviceaccount/token). After exchanging for temporary credentials, initialize the S3 client. The package upgrade was mainly to ensure compatibility with SDK v2’s credential provider interface.
Knowledge Sugar
Why not just use AWS Access Keys? Hardcoding keys has rotation issues, leakage risks, and makes it hard to audit which workload is accessing what. OIDC federation’s temporary credentials have a TTL and expire automatically, making them significantly more secure.
What’s inside the JWT token? The token issued by GKE is a standard JWT. The payload contains:
iss: OIDC issuer (the GKE cluster’s URL)sub:system:serviceaccount:<namespace>:<name>exp: expiration time
When AWS STS receives the token, it fetches the public key from the iss URL’s /.well-known/openid-configuration endpoint to verify the signature.
How is the JWT token mounted into the Pod?
This is a built-in Kubernetes mechanism – every Pod automatically gets a Service Account token mounted at creation, no extra config needed. However, the default token doesn’t specify an audience, which AWS STS won’t accept. So you need to mount a dedicated one using a projected volume in the Pod spec:
1volumes:
2 - name: aws-token
3 projected:
4 sources:
5 - serviceAccountToken:
6 audience: sts.amazonaws.com # here!
7 expirationSeconds: 3600
8 path: tokenTerraform only handles the AWS side (OIDC Provider, IAM Role); the token mounting itself is done by Kubernetes.
The official name for this pattern: Workload Identity Federation GCP’s own Workload Identity uses the same principle, just with GCP resources. For cross-cloud scenarios (GKE -> AWS), OIDC serves as the universal standard that bridges them.