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

# Azure workload identity

> Use one Entra ID workload identity for Azure Blob Storage, Azure Vision OCR, and Azure OpenAI in a self-hosted deployment.

Reducto can call every Azure service with one Entra ID workload identity instead of keys. Use it when your Azure policy disables key (local) authentication on storage accounts or Azure AI services, as many Azure Government tenants require. Each Reducto pod exchanges its Kubernetes service-account token for Entra ID tokens, and every Azure client in the pod shares that identity and its token cache.

## What the identity covers

| Service | How Reducto selects the identity | Role to assign | Scope |
| - | - | - | - |
| Azure Blob Storage result links | Automatic when no account key is configured. See [Azure Blob Storage authentication](https://docs.reducto.ai/onprem/azure_blob_storage.md). | Storage Blob Delegator, and Storage Blob Data Reader | Storage account, and each result container |
| Azure Vision OCR | `AZURE_AI_SERVICES_AUTH=workload_identity` | Cognitive Services User | Each Vision resource |
| Azure OpenAI | `AZURE_AI_SERVICES_AUTH=workload_identity` | Cognitive Services OpenAI User | Each Azure OpenAI resource |

Normal blob uploads, downloads, and deletes still use `AZURE_STORAGE_CONNECTION_STRING`. See [Azure Blob Storage authentication](https://docs.reducto.ai/onprem/azure_blob_storage.md) for a SAS connection string on an account with shared-key access disabled.

## Set up the identity

1. Create a user-assigned managed identity in your Entra tenant.
2. Add a federated identity credential that trusts your cluster's OIDC issuer. Set the subject to `system:serviceaccount:<namespace>:<release>-reducto`. Its audience must equal `azureWorkloadIdentity.audience` (default `api://AzureADTokenExchange`). If your tenant created the credential with another audience, set the chart value to match.
3. Assign the roles in the table above for the services you use.
4. Mount the federated token on the pods, with the AKS webhook or the chart's `azureWorkloadIdentity` values. Follow [Mount the federated token](https://docs.reducto.ai/onprem/azure_blob_storage.md#mount-the-federated-token). With the webhook, label every Reducto component (`http`, `worker`, and every streaq worker pool). The chart fails the render when `AZURE_AI_SERVICES_AUTH=workload_identity` is set and a component has no identity.
5. Allow outbound HTTPS from the pods to the Entra authority host (`login.microsoftonline.us` in Azure Government; the `AzureActiveDirectory` service tag). Token exchange has no private endpoint, also when your Azure services use private endpoints.

```yaml Helm values (explicit projection) theme={null}
serviceAccount:
  create: true
azureWorkloadIdentity:
  enabled: true
  clientId: "<managed-identity-client-id>"
  tenantId: "<tenant-id>"
  authorityHost: https://login.microsoftonline.us/   # Azure Government
  audience: api://AzureADTokenExchange
env:
  AZURE_AI_SERVICES_AUTH: workload_identity
```

The chart sets `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_AUTHORITY_HOST`, and `AZURE_FEDERATED_TOKEN_FILE` on every Reducto pod.

## Azure AI services

`AZURE_AI_SERVICES_AUTH` controls Azure Vision OCR and Azure OpenAI together. It defaults to `key`.

| Value | Azure Vision OCR sends | Azure OpenAI sends |
| - | - | - |
| `key` | `AZURE_VISION_KEY`, or the `key` of each `AZURE_VISION_ARRAY` entry | `AZURE_OPENAI_API_KEY` |
| `workload_identity` | An Entra ID token of the workload identity | An Entra ID token of the workload identity |

With `workload_identity`:

* Remove `AZURE_VISION_KEY`, every `key` in `AZURE_VISION_ARRAY`, and `AZURE_OPENAI_API_KEY`. Reducto refuses to start with a key in this mode, so a leftover key cannot keep authenticating calls.
* Use each resource's custom subdomain as its endpoint, for example `https://<resource-name>.cognitiveservices.azure.us/` or `https://<resource-name>.openai.azure.us/`. Entra ID authentication does not work with a regional endpoint.
* Private endpoints work for the services. The custom subdomain resolves to the private IP address through your private DNS zone. Token exchange still needs outbound HTTPS to the Entra authority host.

Reducto checks this configuration in two places:

* `helm template` and `helm upgrade` fail, and name the component, when `AZURE_AI_SERVICES_AUTH=workload_identity` is set and a component has no workload identity.
* A pod fails to start when `AZURE_VISION_KEY`, `AZURE_OPENAI_API_KEY`, or a `key` in `AZURE_VISION_ARRAY` is still set in this mode.

Pods that call no Azure AI service, such as the inference pod, start without the identity. So a deployment outside the Helm chart that sets the mode with no identity starts, and its first OCR or Azure OpenAI call fails with an error that names the missing variables.

## Azure Government and other clouds

Reducto takes the cloud from `AZURE_AUTHORITY_HOST`, which the chart sets from `azureWorkloadIdentity.authorityHost`, and requests every Azure AI services token for that cloud's audience. A custom domain or a gateway in front of a service therefore gets the right audience.

| `AZURE_AUTHORITY_HOST` | Token audience |
| - | - |
| `https://login.microsoftonline.us/` | `https://cognitiveservices.azure.us` (Azure Government) |
| `https://login.chinacloudapi.cn/` | `https://cognitiveservices.azure.cn` (Azure China) |
| `https://login.microsoftonline.com/` or unset | `https://cognitiveservices.azure.com` |

With `AZURE_AI_SERVICES_AUTH=workload_identity`, an authority host of no known cloud fails the first OCR or Azure OpenAI call instead of sending a token for the wrong cloud. Blob Storage tokens use the storage account's own host as the audience, which works in every cloud.

## Troubleshooting

**OCR or LLM calls fail with an authentication error and no retry.** The identity is missing, its token file cannot be read, or Entra rejected the token exchange. Reducto does not retry it, because every attempt and every endpoint fails the same way. For OCR, the job fails at once with an internal error. For an LLM call, Reducto skips the Azure OpenAI model and tries your other configured models; if every model fails this way, the job fails with an internal error that names the identity. Check the token mount (`/var/run/secrets/azure/tokens/azure-identity-token`), the federated credential's issuer and subject, and the role assignments.

**Azure returns 401 with a valid token.** The token audience does not match the resource's cloud, or the endpoint is a regional endpoint. Check `azureWorkloadIdentity.authorityHost`, and use the custom subdomain of the resource.

**Calls time out getting a token.** The pods cannot reach the Entra authority host. Allow outbound HTTPS to it. Each token attempt waits at most 5 seconds to connect and 10 seconds to read, with one retry, so a page fails within about 30 seconds instead of hanging.

**Azure returns 403.** The identity lacks the role on that resource. Role assignments can take several minutes to apply.

## Roll back

Set `AZURE_AI_SERVICES_AUTH=key` and restore the keys, then restart the pods. A Reducto release older than the one that added `AZURE_AI_SERVICES_AUTH` ignores the setting and needs the keys, so restore the keys before you roll back the release. During an upgrade, disable key authentication on your Azure resources only after every Reducto pod runs the new release. Blob Storage signing switches back to account-key signing when you restore `AccountKey` in the connection string or `AZURE_STORAGE_ACCOUNT_KEY`.
