Pull images from Google Artifact Registry on EKS clusters using Workload Identity Federation.
You've got an EKS cluster, but your container images live in Google Artifact Registry. The cluster can't pull them because it doesn't have GCP credentials.
The old way: create a GCP service account key, store it as a Kubernetes secret, and configure imagePullSecrets on every deployment. That's a lot of YAML, and now you've got a credential that never expires sitting in your cluster.
This tool takes a different approach. It uses Workload Identity Federation to exchange the node's AWS IAM role for a short-lived GCP access token. No long-lived keys. No secrets to rotate. No imagePullSecrets everywhere.
kubelet needs image from *.pkg.dev
│
▼
┌─────────────────────────────────┐
│ gar-credential-provider │
│ │
│ 1. Read AWS credentials │
│ (from IMDS) │
│ │
│ 2. Exchange for GCP token │
│ (via STS) │
│ │
│ 3. Return Docker credentials │
│ (oauth2accesstoken:token) │
└─────────────────────────────────┘
│
▼
kubelet pulls image
The binary implements the kubelet credential provider API. When kubelet tries to pull an image from a matching registry (like us-central1-docker.pkg.dev), it calls this binary via stdin/stdout. The binary fetches a GCP access token and returns it as Docker credentials.
Tokens are cached by kubelet (default: 50 minutes), so you're not hitting the STS endpoint on every pull.
| Source | How it works |
|---|---|
| AWS (EKS) | Reads credentials from IMDS, signs GetCallerIdentity, GCP verifies with AWS STS |
| OIDC | Reads JWT from file or URL, GCP verifies signature (for other clusters) |
helm repo add mondu https://mondu-ai.github.io/helm-charts-community
helm repo update
helm install gar-credential-provider mondu/gar-credential-provider \
--namespace kube-system \
--set gcp.audience="//iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" \
--set gcp.serviceAccountEmail="gar-reader@your-project.iam.gserviceaccount.com"That's it. The chart deploys a DaemonSet that configures each node automatically.
Under the hood, the binary runs in install mode on each node:
- Copies itself to
/etc/eks/image-credential-provider/ - Writes the GCP WIF credential config
- Updates kubelet's credential provider configuration
- Restarts kubelet to pick up changes
- Labels the node with
gar-credential-provider/version
New nodes get configured automatically when they join the cluster.
gcloud iam workload-identity-pools create my-pool \
--location="global" \
--display-name="My Pool"gcloud iam workload-identity-pools providers create-aws aws-provider \
--location="global" \
--workload-identity-pool="my-pool" \
--account-id="AWS_ACCOUNT_ID"gcloud artifacts repositories add-iam-policy-binding my-repo \
--location="us-central1" \
--member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/my-pool/attribute.aws_role/arn:aws:sts::AWS_ACCOUNT_ID:assumed-role/my-eks-node-role" \
--role="roles/artifactregistry.reader"gcloud iam workload-identity-pools create-cred-config \
projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/my-pool/providers/aws-provider \
--aws \
--output-file=gcp-credential-config.jsonIf you'd rather not use Helm, grab the binary from releases and drop it in kubelet's credential provider directory.
Create the GCP WIF config file (see config examples below).
Update kubelet's --image-credential-provider-config:
{
"apiVersion": "kubelet.config.k8s.io/v1",
"kind": "CredentialProviderConfig",
"providers": [
{
"name": "gar-credential-provider",
"matchImages": ["*.pkg.dev"],
"defaultCacheDuration": "50m",
"apiVersion": "credentialprovider.kubelet.k8s.io/v1",
"args": ["--config=/etc/eks/image-credential-provider/gcp-credential-config.json"]
}
]
}Restart kubelet.
AWS (EKS)
{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/aws-PROVIDER_ID",
"subject_token_type": "urn:ietf:params:aws:token-type:aws4_request",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": {
"environment_id": "aws1",
"regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
}
}OIDC token file
{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": {
"file": "/var/run/secrets/tokens/gcp-token"
}
}MIT