Kinoko's TIL Log

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

  1. AWS IAM OIDC Provider – tells AWS “I trust tokens issued by this GKE cluster”; requires the GKE OIDC issuer URL and thumbprint
  2. AWS IAM Role + Trust Policy – defines which GKE service account can assume this role; the condition typically requires the sub claim to match system:serviceaccount:<namespace>:<ksa-name>
  3. 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:

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: token

Terraform 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.

#gcp #security #til

← Back to Main Page