Skip to content

feat: add Google Workload Identity Federation - #72

Open
NGQ-Hiro wants to merge 9 commits into
Matatika:masterfrom
Kaligo:WIF
Open

feat: add Google Workload Identity Federation#72
NGQ-Hiro wants to merge 9 commits into
Matatika:masterfrom
Kaligo:WIF

Conversation

@NGQ-Hiro

@NGQ-Hiro NGQ-Hiro commented Jul 14, 2026

Copy link
Copy Markdown

Google Workload Identity Federation (WIF) with AWS

tap-google-sheets supports authenticating to the Google Sheets and Drive APIs
using Google Workload Identity Federation (WIF). WIF is a Google Cloud feature
that allows Google APIs to trust external identities — in this case AWS — without
needing a long-lived Google service account key.

This is intended for deployments running on AWS — most notably EKS with IRSA
(IAM Roles for Service Accounts) — and local development using AWS SSO.

How it works

WIF does not replace AWS credentials — it reuses them to prove your identity to
Google. The flow is:

AWS credentials (from boto3 provider chain)
  │
  │  boto3 resolves creds:
  ├── EKS pod:   IRSA → sts:AssumeRoleWithWebIdentity → temp keys
  ├── local dev: AWS SSO session → temp keys
  └── EC2:       IMDS instance profile → temp keys
  │
  ▼
Sign an AWS STS GetCallerIdentity request  (no HTTP call — pure crypto)
  │
  ▼
POST sts.googleapis.com/v1/token
  subject_token = signed GetCallerIdentity request
  Google STS calls AWS to verify your identity
  → issues a short-lived federated Google token
  │
  ▼
POST iamcredentials.googleapis.com/generateAccessToken  (if impersonation configured)
  → impersonate a service account → SA token
  │
  ▼
Authorization: Bearer <token>  →  Google Sheets API ✓

Key points:

  • WIF is a Google feature — your AWS identity is the proof, Google does the trust verification.
  • No Google SA key is stored — the AWS identity replaces it.
  • boto3 is required — it handles the full AWS credential chain including IRSA,
    SSO, static env vars, and IMDS.
  • Service account impersonation is required when the Google Sheet is shared with
    a specific SA email (federated principals have no email, so they cannot be added as
    sheet collaborators directly).

Prerequisites

  1. A Google WIF pool and provider configured to trust your AWS account/role
    (created in Google Cloud Console → IAM → Workload Identity Federation).
  2. The WIF external-account credential JSON downloaded from Google Cloud Console.
  3. If using impersonation: a service account whose email is shared on the target Sheet,
    with the roles/iam.workloadIdentityUser binding on the WIF pool.
  4. boto3 installed (included automatically for Python ≥ 3.10).
  5. On EKS: IRSA configured on the pod (AWS_WEB_IDENTITY_TOKEN_FILE + AWS_ROLE_ARN).
  6. On local: an active AWS SSO session (aws sso login --profile <profile>).
  7. Outbound HTTPS access from the pod to *.googleapis.com (both sts.googleapis.com
    and iamcredentials.googleapis.com must be reachable).

Configuration

Setting Type Default Description
workload_identity boolean false Enable WIF authentication. Takes priority over oauth_credentials when true.
workload_identity_credentials string JSON string of the WIF external-account credential.
workload_identity_credentials_file string Path to the WIF credential JSON file. Used when workload_identity_credentials is not set.

When workload_identity is true, exactly one of workload_identity_credentials
or workload_identity_credentials_file must be provided; otherwise the tap
raises a ValueError.

Example config

{
  "workload_identity": true,
  "workload_identity_credentials": "{\"type\":\"external_account\",\"audience\":\"//iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/aws-provider\",\"subject_token_type\":\"urn:ietf:params:aws:token-type:aws4_request\",\"token_url\":\"https://sts.googleapis.com/v1/token\",\"service_account_impersonation_url\":\"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/my-sa@my-project.iam.gserviceaccount.com:generateAccessToken\",\"credential_source\":{\"environment_id\":\"aws1\",\"regional_cred_verification_url\":\"https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15\"}}",
  "sheets": [{"sheet_id": "<spreadsheet_id>"}]
}

Or point the tap at a credential file:

{
  "workload_identity": true,
  "workload_identity_credentials_file": "/var/secrets/google/wif-credentials.json",
  "sheets": [{"sheet_id": "<spreadsheet_id>"}]
}

Authentication priority

The tap selects an authentication method in the following order:

  1. Workload Identity Federation — if workload_identity is true.
  2. OAuth 2.0 — if client_id, client_secret, and refresh_token are present.
  3. Proxy refresh — fallback.

Limitations & notes

  • Only AWS external-account WIF is supported. Other WIF sources (GCP, Azure,
    OIDC identity pool) raise NotImplementedError.
  • The requested scopes are hardcoded to read-only Sheets and Drive access.
  • The pod must have outbound HTTPS to *.googleapis.com. A Connection reset by peer
    error on sts.googleapis.com or iamcredentials.googleapis.com indicates a
    network/firewall issue — not a code bug.

NGQ-Hiro and others added 9 commits July 6, 2026 22:49
- Updated `pyproject.toml` to include `google-auth` dependency.
- Introduced `WorkloadIdentityAuthenticator` for authenticating using Google Workload Identity Federation.
- Modified `GoogleSheetsBaseStream` to utilize `WorkloadIdentityAuthenticator` when credentials are provided.
- Added new configuration options for workload identity credentials in the tap's schema.
The default google-auth AWS credential source can fail two ways when IMDS
is unavailable: a RefreshError (proxy returns an error page) or a
TransportError (connection times out). Only RefreshError was caught, so
timeout environments never reached the botocore fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant