# Workload Identity Federation with Google Cloud

Authenticate to the Stripe API from Google Cloud without a long-lived API key.

> This feature is in private preview. To join the preview, [request access](https://docs.stripe.com/workload-identity-federation.md#request-access).

Google Cloud uses the concept of service accounts, which maps to the concept of service principals in Stripe. By default, every Google service account can get Google-signed identity tokens, and the issuer URL is always **https://accounts.google.com**.

## Before you begin

- Create a Stripe [organization](https://docs.stripe.com/get-started/account/orgs.md) with [private preview access](https://docs.stripe.com/workload-identity-federation.md#request-access) to WIF.
- Create a Google Cloud project with permissions to create service accounts.
- To set up with the CLI, use [Cloud Shell](https://shell.cloud.google.com) or install the [gcloud CLI](https://cloud.google.com/sdk/docs/install). To set up with Terraform, install [Terraform](https://developer.hashicorp.com/terraform/install) 1.5 or later.

## Create a service account

#### Google Cloud console

### Create the service account 

1. Open [console.cloud.google.com](https://console.cloud.google.com/) and select your project from the project picker at the top.
2. Go to **IAM & Admin** > **Service Accounts** and click **Create service account**.
3. Enter a name, such as `stripe-caller`. The ID and email fill in automatically.
4. Click **Create and continue**. Skip the “Grant this service account access to project” step, because calling Stripe needs no Google Cloud permissions, and click **Done**.
5. Open the new service account. On its **Details** tab, copy two values:
   - **Email**, such as `stripe-caller@<PROJECT_ID>.iam.gserviceaccount.com`.
   - **Unique ID**, a long number such as `104892101234567890123`. This becomes the token’s subject, or `sub`.

### Allow local testing (optional) 

To [test your workload locally](https://docs.stripe.com/workload-identity-federation/google-cloud.md#test-locally) from Cloud Shell, grant your Google account permission to impersonate the service account. Skip this step in production.

1. In the [Google Cloud console](https://console.cloud.google.com/), go to **APIs & Services** > **Library**, search for **IAM Service Account Credentials API**, and click **Enable**. Impersonation won’t work without it.

2. Go to **IAM & Admin > Service Accounts** and open the service account. On the **Principals with access** tab, select **Grant access**.

3. Enter your Google account email and add two roles:

   - **Service Account OpenID Connect Identity Token Creator**: Lets you mint identity tokens as the service account.
   - **Service Account Token Creator**: Lets the gcloud CLI generate access tokens during impersonation.

4. Click **Save**. Role changes can take 1 or 2 minutes to take effect.

#### CLI

Run the following commands in [Cloud Shell](https://shell.cloud.google.com) or in a terminal where you’re signed in to the gcloud CLI. The commands create the service account, then print the issuer URL and subject that you need in Stripe.

```bash
PROJECT_ID=$(gcloud config get-value project)
SA=stripe-caller@${PROJECT_ID}.iam.gserviceaccount.com

gcloud services enable iam.googleapis.com
gcloud iam service-accounts create stripe-caller --display-name="Stripe caller"

echo "Issuer URL: https://accounts.google.com"
echo "Subject:    $(gcloud iam service-accounts describe "$SA" --format='value(uniqueId)')"
```

### Allow local testing (optional) 

To [test your workload locally](https://docs.stripe.com/workload-identity-federation/google-cloud.md#test-locally) from Cloud Shell, grant your Google account permission to impersonate the service account. Skip this step in production. Role changes can take 1 or 2 minutes to take effect.

```bash
gcloud services enable iamcredentials.googleapis.com

ME=$(gcloud config get-value account)
for ROLE in roles/iam.serviceAccountOpenIdTokenCreator roles/iam.serviceAccountTokenCreator; do
  gcloud iam service-accounts add-iam-policy-binding "$SA" --member="user:${ME}" --role="$ROLE"
done
```

#### Terraform

First, enable the IAM API, which Terraform uses to create the service account. To [test your workload locally](https://docs.stripe.com/workload-identity-federation/google-cloud.md#test-locally), also enable the IAM Service Account Credentials API, which lets you impersonate the service account.

```bash
gcloud services enable iam.googleapis.com

# Local testing only
gcloud services enable iamcredentials.googleapis.com
```

Save the following configuration as `main.tf`. It creates the service account and outputs the issuer URL and subject that you need in Stripe.

```hcl
terraform {
  required_version = ">= 1.5"

  required_providers {
    google = {
      source  = "hashicorp/google"
      version = ">= 5.0"
    }
  }
}

variable "project_id" {
  description = "The Google Cloud project to create the service account in."
  type        = string
}

variable "service_account_id" {
  description = "The ID of the service account your workload runs as."
  type        = string
  default     = "stripe-caller"
}

variable "tester_email" {
  description = "Optional. A Google account to allow to impersonate the service account, for local testing. Leave unset in production."
  type        = string
  default     = null
}

provider "google" {
  project = var.project_id
}

# The identity your workload runs as. Calling Stripe needs no Google Cloud roles.
resource "google_service_account" "stripe_caller" {
  account_id   = var.service_account_id
  display_name = "Stripe caller"
  description  = "Authenticates to the Stripe API with Workload Identity Federation."
}

# Local testing only: let a person mint identity tokens as the service account.
resource "google_service_account_iam_member" "tester" {
  for_each = var.tester_email == null ? toset([]) : toset([
    "roles/iam.serviceAccountOpenIdTokenCreator",
    "roles/iam.serviceAccountTokenCreator",
  ])

  service_account_id = google_service_account.stripe_caller.name
  role               = each.value
  member             = "user:${var.tester_email}"
}

output "stripe_issuer_url" {
  description = "The issuer URL for your service principal in Stripe."
  value       = "https://accounts.google.com"
}

output "stripe_subject" {
  description = "The subject for your service principal in Stripe."
  value       = google_service_account.stripe_caller.unique_id
}

output "service_account_email" {
  description = "The service account to attach to your workload."
  value       = google_service_account.stripe_caller.email
}
```

If you run Terraform outside Cloud Shell, sign in with Application Default Credentials first. The Google provider for Terraform uses these credentials, and signing in to the gcloud CLI doesn’t set them. Cloud Shell provides them automatically.

```bash
gcloud auth application-default login
```

Initialize Terraform and apply the configuration, with your project ID in place of the placeholder:

```bash
terraform init
terraform apply -var="project_id=<PROJECT_ID>"
```

To [test your workload locally](https://docs.stripe.com/workload-identity-federation/google-cloud.md#test-locally) from Cloud Shell, also pass your Google account email. This lets your account impersonate the service account. Don’t set `tester_email` in production.

```bash
terraform apply -var="project_id=<PROJECT_ID>" -var="tester_email=<YOUR_EMAIL>"
```

Terraform prints the `stripe_issuer_url` and `stripe_subject` outputs. You need them when you create a service principal in Stripe.

## Attach the service account to your workload

If you’re testing, skip this step and [create a service principal in Stripe](https://docs.stripe.com/workload-identity-federation/google-cloud.md#create-service-principal), then [test your workload locally](https://docs.stripe.com/workload-identity-federation/google-cloud.md#test-locally).

- **Cloud Run**: go to the service, click **Edit & deploy new revision**, and select the service account under **Security**.
- **Compute Engine**: Stop the instance, click **Edit**, and select the service account under **Identity and API access**.
- **GKE**: Configure Workload Identity on the cluster and annotate the Kubernetes service account with `iam.gke.io/gcp-service-account=<SERVICE_ACCOUNT_EMAIL>`. Grant the Kubernetes service account the `roles/iam.workloadIdentityUser` role on the Google service account so it can obtain identity tokens:
  ```bash
  gcloud iam service-accounts add-iam-policy-binding <SERVICE_ACCOUNT_EMAIL> \
    --role=roles/iam.workloadIdentityUser \
    --member="serviceAccount:<PROJECT_ID>.svc.id.goog[<NAMESPACE>/<KSA_NAME>]"
  ```

## Create a service principal in Stripe

1. In the Dashboard, go to **Organization** > **Developers** > **API authentication** > [Service principals](https://dashboard.stripe.com/org/service-principals).
2. Click **Create service principal**.
3. Select an existing workload identity provider, or add a new one.
   - Enter the `Issuer URL` from your workload identity provider.
4. Paste the subject of your service from your cloud provider. You can add multiple subjects if your service principal is acting on behalf of multiple workloads.
5. Assign permissions to your service principal. Select between common permission templates, or assign custom permissions to specific API resources.
6. Name your service principal and review its details and assigned permissions.
7. After you create the service principal, copy its client ID `oacli_...`. Use this client ID when you exchange tokens in the next step.

For Google Cloud, use the following values:

- **Issuer URL**: `https://accounts.google.com`
- **Subject**: The numeric unique ID of the service account, such as `104892101234567890123`

## Authenticate requests to the Stripe API

> During private preview, Workload Identity Federation with Google Cloud is only supported using the Ruby SDK.

The Stripe SDKs automatically handle the creation and exchange of short-lived access tokens for your workload to make calls to the Stripe API from your cloud provider. Make sure to include the Workload Identity Federation gem `stripe-workload-identity` in addition to the main `stripe` gem. Install the private-preview builds of both gems:

```bash
bundle add stripe --version "20.1.0.pre.alpha.4"
bundle add stripe-workload-identity --version "0.1.0.pre.alpha.2"
```

To start, initialize the Stripe client and pass in the service principal’s client ID along with the `stripe_context` to specify the account context for the request.

> #### Replace the placeholder IDs
> 
> Before you run this code, replace both placeholders with your own values:
> 
> - `oacli_...`: Your service principal’s client ID, which you copied after you [created the service principal](https://docs.stripe.com/workload-identity-federation/google-cloud.md#create-service-principal).
> - `acct_...`: The ID of the Stripe account in your organization that the request acts on. See [Specify request context](https://docs.stripe.com/context.md).

```ruby
require "stripe"
require "stripe/workload_identity"

client = Stripe::StripeClient.for_workload_identity(
  "oacli_...", # Replace with your service principal's client ID
  Stripe::WorkloadIdentity::GcpWorkloadIdentity.new,
  stripe_context: "acct_..." # Replace with your Stripe account ID
)

customer = client.v1.customers.create(
  {
    name: "Jenny Rosen",
    email: "jenny@example.com",
  }
)

puts customer.id
```

## Test your workload locally

You can test directly from the Google Cloud Shell to call the Stripe API from a test script. First, make sure you [allowed local testing](https://docs.stripe.com/workload-identity-federation/google-cloud.md#create-service-account) when you created the service account.

Paste this into Cloud Shell to check that you can get an identity token as the service account. The first line sets `SA` to your service account’s email:

- If you named the service account something other than `stripe-caller`, change it in the first line.
- If you created the service account with Terraform, replace the first line with `export SA=$(terraform output -raw service_account_email)`, and run it from the folder that contains your `main.tf`.

```bash
export SA=stripe-caller@$(gcloud config get-value project).iam.gserviceaccount.com

JWT=$(gcloud auth print-identity-token \
  --impersonate-service-account=$SA \
  --audiences=https://access.stripe.com/wif --include-email)

echo "$JWT" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null; echo
```

The decoded output shows your service account’s email and a numeric sub. If you get a permission error, wait a minute for the role grant to take effect and try again.

In production, your workload runs as the service account, and `GcpWorkloadIdentity` gets an identity token for it automatically. Cloud Shell runs as your Google account instead, so `GcpWorkloadIdentity` would get a token for your account, and Stripe would reject it because it doesn’t match your service principal’s subject.

To test from Cloud Shell, the following script replaces `GcpWorkloadIdentity` with `ImpersonatedGcpIdentity`, a small class that uses the gcloud CLI to get an identity token as the service account. This works because you allowed your Google account to impersonate the service account when you [created it](https://docs.stripe.com/workload-identity-federation/google-cloud.md#create-service-account).

> #### Replace the placeholder IDs
> 
> In the first two lines of the following commands, replace both placeholders with your own values:
> 
> - `oacli_...`: Your service principal’s client ID, which you copied after you [created the service principal](https://docs.stripe.com/workload-identity-federation/google-cloud.md#create-service-principal).
> - `acct_...`: The ID of the Stripe account in your organization that the request acts on. See [Specify request context](https://docs.stripe.com/context.md).
> 
> The script stops with an error if you leave either placeholder unchanged.

Paste the following into Cloud Shell, in the same session as the previous step so that `SA` is set. It writes the test script to `stripe_wif_test.rb`, installs the Stripe gem, and runs the script.

```bash
export STRIPE_CLIENT_ID=oacli_...  # Replace with your service principal's client ID
export STRIPE_ACCOUNT=acct_...     # Replace with your Stripe account ID

cat > stripe_wif_test.rb <<'EOF'
require "stripe"

# For local testing only. In production, use Stripe::WorkloadIdentity::GcpWorkloadIdentity.
class ImpersonatedGcpIdentity
  def provider
    "gcp"
  end

  def identity_assertion
    command = [
      "gcloud", "auth", "print-identity-token",
      "--impersonate-service-account", ENV.fetch("SA"),
      "--audiences", "https://access.stripe.com/wif",
      "--include-email", "--verbosity", "error",
    ]
    IO.popen(command, &:read).strip
  end
end

# Stops the script if a placeholder ID wasn't replaced.
def required_env(name)
  value = ENV.fetch(name, "")
  abort "Set " + name + " to your own value before running this script." if value.empty? || value.end_with?("...")
  value
end

client = Stripe::StripeClient.for_workload_identity(
  required_env("STRIPE_CLIENT_ID"),
  ImpersonatedGcpIdentity.new,
  stripe_context: required_env("STRIPE_ACCOUNT")
)

customer = client.v1.customers.create(
  {
    name: "Jenny Rosen",
    email: "jenny@example.com",
  }
)

puts customer.id
EOF

[ -f Gemfile ] || bundle init
bundle config set --local path vendor/bundle
bundle add stripe --version "20.1.0.pre.alpha.4"
bundle exec ruby stripe_wif_test.rb
```

The script prints the ID of the new customer, such as `cus_...`.

## See also

- [Workload Identity Federation](https://docs.stripe.com/workload-identity-federation.md)
- [Service principals](https://docs.stripe.com/service-principals.md)
