> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runlayer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Workload Identity

> Map operator ServiceAccounts to IRSA (EKS) or Workload Identity (GKE) with a per-feature permission matrix

The operator creates **dedicated Kubernetes ServiceAccounts** per workload. It never embeds long-lived cloud keys. You annotate those SAs with IRSA role ARNs (EKS) or GKE Workload Identity GSA emails.

Platform namespace default: `runlayer-<customerId>` (override with `spec.deploy.platformNamespace`).

## ServiceAccount naming

Pattern: `{RunlayerInstance.metadata.name}-{component}` in the **platform** namespace.

| ServiceAccount                    | Components                                                          | Cloud annotation target   |
| --------------------------------- | ------------------------------------------------------------------- | ------------------------- |
| `{instance}-backend`              | backend Deployment, migration Job, **frontend** (shares backend SA) | IRSA role / GSA           |
| `{instance}-worker`               | worker Deployment                                                   | IRSA role / GSA           |
| `{instance}-audit-consumer`       | `components.auditConsumer`                                          | dedicated role/GSA        |
| `{instance}-siem-export`          | `components.siemExport`                                             | dedicated role/GSA        |
| `{instance}-session-materializer` | `components.sessionMaterializer`                                    | dedicated role/GSA        |
| `{instance}-toolguard`            | `components.toolguard`                                              | usually none (in-cluster) |

Example: CR `metadata.name: acme-prod` → SA `acme-prod-backend` in `runlayer-acme`.

OIDC / WI trust subject (EKS):

```text theme={null}
system:serviceaccount:runlayer-acme:acme-prod-backend
```

GKE principal form:

```text theme={null}
principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/runlayer-acme/sa/acme-prod-backend
```

## Annotate via `RunlayerInstance`

CR field JSON name is `serviceAccount` (singular):

### EKS (IRSA)

```yaml theme={null}
spec:
  serviceAccount:
    backend:
      annotations:
        eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/runlayer-acme-backend
    worker:
      annotations:
        eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/runlayer-acme-worker
    auditConsumer:
      annotations:
        eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/runlayer-acme-audit-consumer
    siemExport:
      annotations:
        eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/runlayer-acme-siem-export
    sessionMaterializer:
      annotations:
        eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/runlayer-acme-session-materializer
```

Trust policy must allow your cluster OIDC provider with `sub` = `system:serviceaccount:<ns>:<sa>` and audience `sts.amazonaws.com`.

### GKE (Workload Identity)

```yaml theme={null}
spec:
  serviceAccount:
    backend:
      annotations:
        iam.gke.io/gcp-service-account: runlayer-acme-backend@PROJECT.iam.gserviceaccount.com
    worker:
      annotations:
        iam.gke.io/gcp-service-account: runlayer-acme-worker@PROJECT.iam.gserviceaccount.com
    auditConsumer:
      annotations:
        iam.gke.io/gcp-service-account: runlayer-acme-audit@PROJECT.iam.gserviceaccount.com
    siemExport:
      annotations:
        iam.gke.io/gcp-service-account: runlayer-acme-siem@PROJECT.iam.gserviceaccount.com
    sessionMaterializer:
      annotations:
        iam.gke.io/gcp-service-account: runlayer-acme-sm@PROJECT.iam.gserviceaccount.com
```

Also bind the GSA `roles/iam.workloadIdentityUser` (or equivalent) to the KSA principal.

## Permission matrix

Grant only what you enable. Buckets/streams are customer-owned — scope ARNs tightly.

### Base platform (typical)

| SA              | AWS actions (illustrative)                                                           | GCP roles / actions                                             |
| --------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
| backend, worker | Bedrock invoke (if `BEDROCK_ENABLED`); optional Secrets Manager get for sync tooling | Vertex/other LLM as configured; Secret Manager accessor if used |
| backend, worker | S3 List/Get/Put(/Delete/PutObjectTagging) on configured app buckets                  | `storage.objectAdmin` (or finer) on those buckets               |

### AI Watch binary cache

| SA              | AWS                                                                            | GCP                                    |
| --------------- | ------------------------------------------------------------------------------ | -------------------------------------- |
| backend, worker | `s3:ListBucket`, `s3:GetObject`, `s3:PutObject` on `BINARY_PACKAGES_S3_BUCKET` | Object get/create/list on cache bucket |

### Audit publish (when stream-backed)

| SA              | AWS                                                | GCP                                     |
| --------------- | -------------------------------------------------- | --------------------------------------- |
| backend, worker | `kinesis:PutRecord` / `PutRecords` on audit stream | `roles/pubsub.publisher` on audit topic |

### Audit consumer

| SA                          | AWS                                                            | GCP                                                      |
| --------------------------- | -------------------------------------------------------------- | -------------------------------------------------------- |
| `{instance}-audit-consumer` | Kinesis subscribe (EFO), DynamoDB checkpoint R/W, S3 DLQ write | `roles/pubsub.subscriber` on subscription; GCS DLQ write |

### SIEM export

| SA                       | AWS                                                           | GCP                                       |
| ------------------------ | ------------------------------------------------------------- | ----------------------------------------- |
| `{instance}-siem-export` | Stream read + checkpoint + DLQ + **destination** bucket write | Pub/Sub subscribe + destination GCS write |

### Session materializer

| SA                                | AWS                                                                           | GCP                                            |
| --------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------- |
| `{instance}-session-materializer` | Hook-events stream read, DynamoDB checkpoints, DLQ write, session-payload R/W | Ordered subscription consume + payload/DLQ GCS |
| backend, worker                   | Hook-events **publish** + session-payload **read**                            | Topic publish + payload read                   |

### Agents (AgentCore on AWS)

| SA              | AWS                                                                                                                                         |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| backend, worker | `bedrock-agentcore:InvokeAgentRuntime`, `StopRuntimeSession` (names may vary by API); S3 Put + **PutObjectTagging** on `agents.filesBucket` |

### IRSA trust sample (EKS)

```hcl theme={null}
data "aws_iam_policy_document" "backend_assume" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]
    principals {
      type        = "Federated"
      identifiers = [module.eks.oidc_provider_arn]
    }
    condition {
      test     = "StringEquals"
      variable = "${module.eks.oidc_provider}:sub"
      values   = ["system:serviceaccount:runlayer-acme:acme-prod-backend"]
    }
    condition {
      test     = "StringEquals"
      variable = "${module.eks.oidc_provider}:aud"
      values   = ["sts.amazonaws.com"]
    }
  }
}
```

## Verify

### EKS

```bash theme={null}
kubectl -n runlayer-acme get sa acme-prod-backend -o yaml
# expect eks.amazonaws.com/role-arn

kubectl -n runlayer-acme exec deploy/acme-prod-backend -c backend -- \
  aws sts get-caller-identity
# Arn should be the IRSA role
```

### GKE

```bash theme={null}
kubectl -n runlayer-acme get sa acme-prod-backend -o yaml
# expect iam.gke.io/gcp-service-account

kubectl -n runlayer-acme exec deploy/acme-prod-backend -c backend -- \
  curl -sH "Metadata-Flavor: Google" \
  http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email
```

Missing annotations or trust subjects show up as `AssumeRoleWithWebIdentity: AccessDenied` (AWS) or permission denied on Pub/Sub/GCS (GCP).

## Related

* [External dependencies](/deployment/external-dependencies)
* [Runlayer Operator](/deployment/runlayer-operator)
* [Kubernetes prerequisites](/deployment/kubernetes-prerequisites)
