# Workload Identity Federation

Authenticate WarpStream Agents to the control plane with short-lived, federated OIDC tokens instead of a long-lived static Agent Key.

By default, a WarpStream Agent authenticates to the control plane with a long-lived Agent Key (`aks_...`) that you provision and set via `WARPSTREAM_AGENT_KEY`. Long-lived secrets have to be distributed, rotated, and revoked out of band, and a leaked key is valid until you notice and rotate it.

**Workload Identity Federation** removes the static secret. Instead, the Agent proves its identity using a token minted by its own cloud platform (AWS or GCP) and exchanges it with the control plane for a **short-lived, cluster-scoped credential**. There is no static secret to store on the Agent, and access is tied to the cloud IAM identity the Agent already runs as.

### How it works

1. On startup, the Agent asks its cloud platform for a signed OIDC token identifying the workload:
   * **AWS** — the Agent calls STS [`GetWebIdentityToken`](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetWebIdentityToken.html) using its ambient IAM role. The role's ARN ends up in the token's `sub` claim.
   * **GCP** — the Agent fetches an identity token from the [instance metadata server](https://cloud.google.com/compute/docs/instances/verifying-instance-identity). The workload's service account email ends up in the token's `email` claim.
2. The Agent exchanges that OIDC token with the WarpStream control plane. The requested audience is always your **Virtual Cluster ID** (`vci_...`).
3. The control plane looks up the **federation binding(s)** you configured on that Virtual Cluster and, for the first binding whose issuer and claim rules match the token, mints a short-lived Agent credential (a `wsa_` token) scoped to that cluster.
4. The Agent uses the `wsa_` token exactly like an Agent Key and refreshes it automatically in the background before it expires.

A binding never grants access on a valid signature alone — every configured claim rule must match, so only the specific IAM role or service account you name can authenticate.

### Supported identity providers

Workload Identity Federation currently supports the following token sources, selected with the `WARPSTREAM_AGENT_WORKLOAD_IDENTITY_TOKEN_SOURCE` Agent environment variable:

| Provider | `WARPSTREAM_AGENT_WORKLOAD_IDENTITY_TOKEN_SOURCE` | OIDC issuer                                |
| -------- | ------------------------------------------------- | ------------------------------------------ |
| AWS      | `aws`                                             | `https://<uuid>.tokens.sts.global.api.aws` |
| GCP      | `gcp`                                             | `https://accounts.google.com`              |

Support for additional providers will be added over time. [Reach out](https://www.warpstream.com/contact-us) if you need one that isn't listed yet.

### Before you begin

You will need:

* A Virtual Cluster (note its `vci_...` ID).
* Permission to create bindings on that cluster: a WarpStream **Application Key** for the API/Terraform, generated in the admin console. See [Secrets Overview](/warpstream/reference/secrets-overview).
* Agents running in a cloud environment with an identity you control — an AWS IAM role (for example an EKS IRSA / Pod Identity role) or a GCP service account.

Setup is three steps: prepare the cloud identity, create the federation binding, then point the Agent at Workload Identity.

## Step 1 — Prepare the cloud identity

<Tabs>

<TabItem label="AWS">

**1. Enable outbound identity federation on your AWS account.** This is a one-time, account-wide action that provisions your account's OIDC issuer URL. See [Getting started with outbound identity federation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_outbound_getting_started.html).

```bash wrap
aws iam enable-outbound-web-identity-federation
```

The response contains your account-specific **issuer URL**, which looks like `https://<uuid>.tokens.sts.global.api.aws`. You'll need it in Step 2. If you enabled the feature previously, retrieve the URL again at any time:

```bash wrap
aws iam get-outbound-web-identity-federation-info
```

You can also find it in the IAM console under **Access management → Account settings → Outbound identity federation**.

**2. Grant the Agent's IAM role permission to mint tokens.** Attach a policy allowing `sts:GetWebIdentityToken` to the role your Agents run as. Restrict the audience to your Virtual Cluster ID so the role can only mint tokens for WarpStream:

```json wrap
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "MintWarpStreamWorkloadIdentityToken",
      "Effect": "Allow",
      "Action": "sts:GetWebIdentityToken",
      "Resource": "*",
      "Condition": {
        "ForAllValues:StringEquals": {
          "sts:IdentityTokenAudience": "vci_XXXXXXXXXX"
        },
        "NumericLessThanEquals": {
          "sts:DurationSeconds": "3600"
        }
      }
    }
  ]
}
```

To let one role authenticate to several WarpStream clusters, list each Virtual Cluster ID as an allowed audience:

```json wrap
"ForAllValues:StringEquals": {
  "sts:IdentityTokenAudience": ["vci_YYYYYYYYYY", "vci_ZZZZZZZZZZ"]
}
```

</TabItem>

<TabItem label="GCP">

No extra IAM permissions are required — any workload can fetch its own identity token from the GCP metadata server.

Just make sure your Agents run **as the service account you intend to authorize**, and note that service account's email (for example `warp-agent@my-project.iam.gserviceaccount.com`). You'll use it in the binding's claim rule in Step 2.

* On GKE, associate the Agent's Kubernetes service account with the Google service account using [Workload Identity Federation for GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity).
* On Compute Engine, this is the VM's attached service account.

</TabItem>

</Tabs>

## Step 2 — Create the federation binding

A binding tells the control plane which OIDC issuer and claims to accept for a given Virtual Cluster. Create it with Terraform (recommended) or the API.

### Terraform

Use the [`warpstream_workload_identity_federation`](https://registry.terraform.io/providers/warpstreamlabs/warpstream/latest/docs/resources/workload_identity_federation) resource (provider `v2.7.10`+):

<Tabs>

<TabItem label="AWS">

```hcl wrap
resource "warpstream_workload_identity_federation" "aws_agents" {
  virtual_cluster_id         = "vci_XXXXXXXXXX"
  name                       = "aws-agents"
  issuer_url                 = "https://<uuid>.tokens.sts.global.api.aws"
  read_only                  = false
  max_credential_ttl_seconds = 3600

  claim_match_rules = [
    {
      claim_path = "sub"
      # Replace with the ARN of the AWS IAM role your Agents are deployed with.
      expected_value = "arn:aws:iam::123456789012:role/warp-agent"
    },
  ]
}
```

</TabItem>

<TabItem label="GCP">

```hcl wrap
resource "warpstream_workload_identity_federation" "gcp_agents" {
  virtual_cluster_id         = "vci_XXXXXXXXXX"
  name                       = "gcp-agents"
  issuer_url                 = "https://accounts.google.com"
  read_only                  = false
  max_credential_ttl_seconds = 3600

  claim_match_rules = [
    {
      claim_path = "email"
      # Replace with the email of the GCP service account your Agents are deployed with.
      expected_value = "warp-agent@my-project.iam.gserviceaccount.com"
    },
  ]
}
```

</TabItem>

</Tabs>

### API

Send a `POST` to `create_workload_identity_federation` with your Application Key in the `warpstream-api-key` header:

<Tabs>

<TabItem label="AWS">

```bash wrap
curl https://api.warpstream.com/api/v1/create_workload_identity_federation \
  -H 'warpstream-api-key: XXXXXXXXXX' \
  -H 'Content-Type: application/json' \
  -d '{
    "virtual_cluster_id": "vci_XXXXXXXXXX",
    "name": "aws-agents",
    "issuer_url": "https://<uuid>.tokens.sts.global.api.aws",
    "read_only": false,
    "max_credential_ttl_seconds": 3600,
    "claim_match_rules": [
      { "claim_path": "sub", "expected_value": "arn:aws:iam::123456789012:role/warp-agent" }
    ]
  }'
```

</TabItem>

<TabItem label="GCP">

```bash wrap
curl https://api.warpstream.com/api/v1/create_workload_identity_federation \
  -H 'warpstream-api-key: XXXXXXXXXX' \
  -H 'Content-Type: application/json' \
  -d '{
    "virtual_cluster_id": "vci_XXXXXXXXXX",
    "name": "gcp-agents",
    "issuer_url": "https://accounts.google.com",
    "read_only": false,
    "max_credential_ttl_seconds": 3600,
    "claim_match_rules": [
      { "claim_path": "email", "expected_value": "warp-agent@my-project.iam.gserviceaccount.com" }
    ]
  }'
```

</TabItem>

</Tabs>

List the bindings on a cluster:

```bash wrap
curl https://api.warpstream.com/api/v1/list_workload_identity_federations \
  -H 'warpstream-api-key: XXXXXXXXXX' \
  -H 'Content-Type: application/json' \
  -d '{"virtual_cluster_id": "vci_XXXXXXXXXX"}'
```

Delete a binding by its `wif_...` ID (from the list response):

```bash wrap
curl https://api.warpstream.com/api/v1/delete_workload_identity_federation \
  -H 'warpstream-api-key: XXXXXXXXXX' \
  -H 'Content-Type: application/json' \
  -d '{"virtual_cluster_id": "vci_XXXXXXXXXX", "id": "wif_XXXXXXXXXX"}'
```

:::note
You can create multiple bindings on one Virtual Cluster — for example, one per availability zone if each zone's Agents run as a different IAM role or service account. Binding names must be unique within a cluster. The control plane tries each binding and mints a credential for the first one whose claims match.
:::

## Step 3 — Configure the Agent

Set the token source on the Agent and **remove the static Agent Key** — the two are mutually exclusive.

<Tabs>

<TabItem label="AWS">

```bash wrap
WARPSTREAM_AGENT_WORKLOAD_IDENTITY_TOKEN_SOURCE=aws
```

</TabItem>

<TabItem label="GCP">

```bash wrap
WARPSTREAM_AGENT_WORKLOAD_IDENTITY_TOKEN_SOURCE=gcp
```

</TabItem>

</Tabs>

The equivalent command-line flag is `-workloadIdentityTokenSource aws` (or `gcp`). Do not also set `WARPSTREAM_AGENT_KEY`, `WARPSTREAM_AGENT_KEY_PATH`, or `WARPSTREAM_API_KEY`; the Agent will refuse to start if a static key is combined with a workload identity token source.

On startup the Agent exchanges a token per region and begins refreshing it automatically. If authentication fails, the Agent logs the exchange failure; the most common causes are a missing IAM permission (AWS), the wrong role/service-account identity in the claim rule, or the account's issuer URL not matching the binding.

## Reference

### Binding fields

| Field                        | Required | Description                                                                                                                                            |
| ---------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `virtual_cluster_id`         | yes      | The Virtual Cluster (`vci_...`) the binding grants access to.                                                                                            |
| `name`                       | yes      | Human-readable name, unique within the cluster (3–128 characters).                                                                                     |
| `issuer_url`                 | yes      | HTTPS URL of the OIDC issuer whose tokens this binding accepts (your AWS account issuer, or `https://accounts.google.com`).                             |
| `claim_match_rules`          | yes      | One or more rules; **all** must match. See below.                                                                                                       |
| `read_only`                  | no       | If `true`, the minted credential is read-only. Defaults to `false`.                                                                                     |
| `max_credential_ttl_seconds` | no       | Maximum lifetime of a minted credential, between `60` (1 minute) and `86400` (24 hours). Defaults to `3600`.                                            |
| `audience`                   | —        | Read-only. Always the Virtual Cluster ID; derived by the control plane and not configurable. This is the audience the Agent's OIDC token must request. |

Bindings are immutable — to change a binding, delete it and create a new one.

### Claim matching

Each rule is a `claim_path` / `expected_value` pair:

* **`claim_path`** is a dot-separated path into the token's claims (e.g. `sub`, `email`, or a nested `context.namespace`). If a single path segment itself contains a literal dot, wrap that segment in double quotes, e.g. `"kubernetes.io".namespace`.
* **`expected_value`** is matched **case-sensitively**. A single trailing `*` acts as a prefix wildcard (`arn:aws:iam::123456789012:role/warp-*`); a `*` anywhere else is treated literally.

All rules in a binding are ANDed together, and at least one rule is always required — a token is never accepted on a valid signature and audience alone.

### Common claims

| Cloud | `issuer_url`                              | Claim to match | Example value                                          |
| ----- | ----------------------------------------- | -------------- | ------------------------------------------------------ |
| AWS   | `https://<uuid>.tokens.sts.global.api.aws` | `sub`          | `arn:aws:iam::123456789012:role/warp-agent`            |
| GCP   | `https://accounts.google.com`             | `email`        | `warp-agent@my-project.iam.gserviceaccount.com`        |

For the full list of claims available in an AWS token, see [Understanding token claims](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_outbound.html).