---
title: Usar WIF con Kubernetes
url: https://platform.claude.com/docs/es/manage-claude/wif-providers/kubernetes
description: Autentícate en la Claude API desde clústeres de Kubernetes autogestionados usando tokens de cuenta de servicio proyectados.
---

Los clústeres de Kubernetes autogestionados (kubeadm, k3s, OpenShift y distribuciones on-premises) firman "JSON Web Tokens" (tokens web JSON), o JWTs, de OIDC para cada pod mediante [tokens de cuenta de servicio proyectados](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#serviceaccount-token-volume-projection). El servidor de API del clúster actúa como el emisor OIDC, y el claim `sub` de cada token sigue la forma `system:serviceaccount:<namespace>:<service-account>`. Puedes encontrar la URL del emisor de tu clúster leyendo su documento de descubrimiento:

```bash cURL
kubectl get --raw /.well-known/openid-configuration | jq -r .issuer
```

<Note>
  El mecanismo de esta página (token de cuenta de servicio proyectado, servidor de API del clúster como emisor OIDC) es nativo de Kubernetes en sí, por lo que subyace a toda distribución de Kubernetes. Si ejecutas en un servicio de Kubernetes administrado, las guías de proveedores de nube explican dónde encontrar la URL del emisor administrada por el proveedor: [AWS (EKS)](https://platform.claude.com/docs/es/manage-claude/wif-providers/aws#use-eks-projected-service-account-tokens), [Google Cloud (GKE)](https://platform.claude.com/docs/es/manage-claude/wif-providers/gcp) o [Azure (AKS)](https://platform.claude.com/docs/es/manage-claude/wif-providers/azure). Si tu clúster ejecuta SPIRE, el SPIRE OIDC Discovery Provider es el emisor en lugar del servidor de API del clúster; consulta [SPIFFE](https://platform.claude.com/docs/es/manage-claude/wif-providers/spiffe). Para cualquier otra distribución o un proveedor administrado que no aparezca allí, sigue esta guía y usa la URL del emisor que reporta tu clúster.
</Note>

## Requisitos previos

* Familiaridad con los [conceptos de WIF](https://platform.claude.com/docs/es/manage-claude/workload-identity-federation#concepts): cuentas de servicio, emisores de federación y reglas de federación.

* Un clúster de Kubernetes con el flag [`--service-account-issuer`](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/) configurado en el servidor de API. La mayoría de las distribuciones lo establecen por defecto; los clústeres kubeadm normalmente usan `https://kubernetes.default.svc.cluster.local`. Tu equipo de plataforma puede confirmar el valor si no tienes acceso directo a la configuración del servidor de API.

* Una de las siguientes opciones para que Anthropic pueda validar las firmas de los tokens:

  * El endpoint JWKS del emisor es accesible desde internet pública por HTTPS en el puerto 443, o
  * Puedes obtener el JWKS desde dentro del clúster y registrarlo en modo `inline` (cubierto en [Configurar Anthropic](https://platform.claude.com/docs/es/manage-claude/wif-providers/kubernetes#configure-anthropic)).

* Permiso para crear cuentas de servicio, emisores de federación y reglas de federación en la Claude Console para tu organización de Anthropic.

## Configurar Kubernetes

Proyecta un token de cuenta de servicio en tu pod con la audiencia y la duración que espera tu regla de federación. La proyección `serviceAccountToken` escribe un JWT nuevo en la ruta de montaje y lo rota antes de que transcurran los `expirationSeconds`.

```yaml Pod
apiVersion: v1
kind: Pod
metadata:
  name: inference-worker
  namespace: inference
spec:
  serviceAccountName: inference-worker
  volumes:
    - name: anthropic-token
      projected:
        sources:
          - serviceAccountToken:
              audience: https://api.anthropic.com
              expirationSeconds: 3600
              path: token
  containers:
    - name: app
      image: your-registry/inference-worker:latest
      env:
        - name: ANTHROPIC_IDENTITY_TOKEN_FILE
          value: /var/run/secrets/anthropic.com/token
        - name: ANTHROPIC_FEDERATION_RULE_ID
          value: fdrl_...
        - name: ANTHROPIC_ORGANIZATION_ID
          value: 00000000-0000-0000-0000-000000000000
        - name: ANTHROPIC_SERVICE_ACCOUNT_ID
          value: svac_...
        - name: ANTHROPIC_WORKSPACE_ID  # required when the rule covers multiple workspaces
          value: wrkspc_...
      volumeMounts:
        - name: anthropic-token
          mountPath: /var/run/secrets/anthropic.com
          readOnly: true
```

El token emitido para este pod lleva `sub: "system:serviceaccount:inference:inference-worker"` y `aud: ["https://api.anthropic.com"]`.

## Configurar Anthropic

En la Claude Console, ve a la página [Workload identity](https://platform.claude.com/settings/workload-identity-federation) (`platform.claude.com/settings/workload-identity-federation`), haz clic en **Connect workload** y selecciona el mosaico **Kubernetes**. El asistente te guía para registrar el emisor, crear una cuenta de servicio y crear una regla de federación.

El asistente crea estos recursos por ti. Usa los siguientes valores, ya sea que los ingreses en el asistente o los envíes a la [Admin API](https://platform.claude.com/docs/es/manage-claude/wif-admin-api):

**Emisor de federación:** Muchos clústeres autogestionados usan una URL de emisor como `https://kubernetes.default.svc.cluster.local` que no es accesible desde internet pública. Si ese es el caso de tu clúster, elige la fuente JWKS **inline** y pega las claves del clúster. Obtenlas desde dentro del clúster:

```bash cURL
kubectl get --raw /openid/v1/jwks
```

Luego configura el emisor con el contenido del arreglo `keys` devuelto (no el envoltorio `{"keys": [...]}` que lo rodea):

```json
{
  "name": "onprem-k8s",
  "issuer_url": "https://kubernetes.default.svc.cluster.local",
  "jwks": {
    "type": "inline",
    "keys": [{ "kty": "RSA", "kid": "...", "n": "...", "e": "AQAB" }]
  }
}
```

En modo `inline`, la `issuer_url` solo se compara con el claim `iss` del JWT; Anthropic nunca intenta acceder a ella. Si tu emisor es accesible públicamente, usa `"jwks": {"type": "discovery"}` en su lugar.

<Warning>
  Con claves `inline` eres responsable de actualizar el emisor cuando el clúster rote su clave de firma de cuentas de servicio. La rotación es poco frecuente (normalmente solo durante actualizaciones del clúster), pero los intercambios de tokens fallan con un error de firma hasta que envíes el nuevo JWKS.
</Warning>

**Regla de federación:** Haz coincidir el claim `sub` de la cuenta de servicio y la audiencia que estableciste en el token proyectado.

```json
{
  "name": "onprem-inference",
  "issuer_id": "fdis_...",
  "match": {
    "subject_prefix": "system:serviceaccount:inference:inference-worker",
    "audience": "https://api.anthropic.com"
  },
  "target": {
    "type": "service_account",
    "service_account_id": "svac_..."
  },
  "workspace_id": "wrkspc_...",
  "oauth_scope": "workspace:developer",
  "token_lifetime_seconds": 600
}
```

Sé tan específico como lo permita la carga de trabajo. Relaja `subject_prefix` a `system:serviceaccount:inference:*` (el `*` final lo convierte en una coincidencia por prefijo) solo si todas las cuentas de servicio del namespace deben mapearse a la misma cuenta de servicio de Anthropic. Agrega el ID `fdrl_...` de la regla a la variable de entorno `ANTHROPIC_FEDERATION_RULE_ID` de tu pod.

## Obtener y usar el token

La especificación del pod en [Configurar Kubernetes](https://platform.claude.com/docs/es/manage-claude/wif-providers/kubernetes#configure-kubernetes) establece `ANTHROPIC_IDENTITY_TOKEN_FILE` en la ruta de montaje proyectada, junto con `ANTHROPIC_FEDERATION_RULE_ID`, `ANTHROPIC_ORGANIZATION_ID`, `ANTHROPIC_SERVICE_ACCOUNT_ID` y `ANTHROPIC_WORKSPACE_ID`. Con esto configurado, el SDK lee el token desde el disco en cada intercambio y renueva el token de acceso de Anthropic automáticamente.

<CodeGroup>
  ```bash cURL
  JWT=$(cat "$ANTHROPIC_IDENTITY_TOKEN_FILE")

  ACCESS_TOKEN=$(curl -sS https://api.anthropic.com/v1/oauth/token \
    -H "content-type: application/json" \
    --data @- <<JSON | jq -r .access_token
  {
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": "$JWT",
    "federation_rule_id": "$ANTHROPIC_FEDERATION_RULE_ID",
    "organization_id": "$ANTHROPIC_ORGANIZATION_ID",
    "service_account_id": "$ANTHROPIC_SERVICE_ACCOUNT_ID",
    "workspace_id": "$ANTHROPIC_WORKSPACE_ID"
  }
  JSON
  )

  curl https://api.anthropic.com/v1/messages \
    -H "authorization: Bearer $ACCESS_TOKEN" \
    -H "anthropic-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "messages": [{"role": "user", "content": "Hello, Claude"}]
    }' | jq -r '.content[] | select(.type == "text") | .text'
  ```

  ```python Python
  import anthropic

  # Lee ANTHROPIC_IDENTITY_TOKEN_FILE, ANTHROPIC_FEDERATION_RULE_ID,
  # ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID y ANTHROPIC_WORKSPACE_ID
  # del entorno del pod.
  client = anthropic.Anthropic()

  message = client.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      messages=[{"role": "user", "content": "Hello, Claude"}],
  )
  print(next(block.text for block in message.content if block.type == "text"))
  ```

  ```typescript TypeScript
  import Anthropic from "@anthropic-ai/sdk";

  // Lee ANTHROPIC_IDENTITY_TOKEN_FILE, ANTHROPIC_FEDERATION_RULE_ID,
  // ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID y ANTHROPIC_WORKSPACE_ID
  // del entorno del pod.
  const client = new Anthropic();

  const message = await client.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Hello, Claude" }]
  });
  for (const block of message.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
  ```

  ```go Go
  // Lee ANTHROPIC_IDENTITY_TOKEN_FILE, ANTHROPIC_FEDERATION_RULE_ID,
  // ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID y ANTHROPIC_WORKSPACE_ID
  // del entorno del pod.
  client := anthropic.NewClient()

  message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 1024,
  	Messages: []anthropic.MessageParam{
  		anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, Claude")),
  	},
  })
  if err != nil {
  	panic(err)
  }
  for _, block := range message.Content {
  	if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
  		fmt.Println(textBlock.Text)
  		break
  	}
  }
  ```

  ```java Java
  AnthropicClient client = AnthropicOkHttpClient.fromEnv();

  var message = client.messages().create(MessageCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024)
          .addUserMessage("Hello, Claude")
          .build());

  IO.println(message.content());
  ```

  ```csharp C#
  // Lee ANTHROPIC_IDENTITY_TOKEN_FILE, ANTHROPIC_FEDERATION_RULE_ID,
  // ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID y ANTHROPIC_WORKSPACE_ID
  // del entorno del pod.
  using var client = new AnthropicClient();

  var message = await client.Messages.Create(new()
  {
      Model = Model.ClaudeOpus5_5,
      MaxTokens = 1024,
      Messages = [new() { Role = Role.User, Content = "Hello, Claude" }],
  });
  foreach (var block in message.Content)
  {
      if (block.Value is TextBlock textBlock)
      {
          Console.WriteLine(textBlock.Text);
      }
  }
  ```

  ```bash CLI
  # Lee ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  # ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID y ANTHROPIC_IDENTITY_TOKEN_FILE
  ant messages create \
    --model claude-opus-5-5 \
    --max-tokens 1024 \
    --message '{role: user, content: "Hello, Claude"}'
  ```

  ```php PHP
  use Anthropic\Client;

  // Lee ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  // ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID y ANTHROPIC_IDENTITY_TOKEN_FILE
  $client = new Client();

  $message = $client->messages->create(
      model: 'claude-opus-5-5',
      maxTokens: 1024,
      messages: [['role' => 'user', 'content' => 'Hello, Claude']],
  );
  $textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
  echo $textBlock->text, PHP_EOL;
  ```

  ```ruby Ruby
  require "anthropic"

  # Lee ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID,
  # ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID y ANTHROPIC_IDENTITY_TOKEN_FILE
  client = Anthropic::Client.new

  message = client.messages.create(
    model: "claude-opus-5-5",
    max_tokens: 1024,
    messages: [{role: "user", content: "Hello, Claude"}]
  )
  puts message.content.find { it.type == :text }.text
  ```
</CodeGroup>

## Verificar la configuración

Un intercambio exitoso devuelve un `access_token` que comienza con `sk-ant-oat01-` y un valor `expires_in` en segundos. Si el intercambio falla con la respuesta opaca `401` `authentication_error` (mensaje `Authentication failed`), revisa la [página de historial de autenticación](https://platform.claude.com/settings/workload-identity-federation?tab=history) en la Claude Console para ver el motivo del rechazo y consulta [Solucionar un intercambio fallido](https://platform.claude.com/docs/es/manage-claude/wif-reference#troubleshoot-a-failed-exchange); la causa más común del lado de Kubernetes es una discrepancia de claves JWKS (para el modo `inline`, vuelve a obtenerlas con `kubectl get --raw /openid/v1/jwks` y actualiza el emisor).

## Delimitar el alcance de tu regla

<Warning>
  Un `subject_prefix` de `system:serviceaccount:*` coincide con todas las cuentas de servicio del clúster, por lo que cualquier pod puede obtener un token federado de Anthropic. Sin un matcher de `audience`, la regla también coincide con los tokens de audiencia predeterminada del clúster, que todos los pods ya tienen proyectados.
</Warning>

Restringe el bloque `match` de la regla al alcance más estrecho que se ajuste a tu caso de uso:

* **Fija el namespace y el nombre de la cuenta de servicio:** Usa el valor completo `system:serviceaccount:<namespace>:<name>` sin `*` final.
* **Establece siempre una audiencia:** Exige `audience` en la regla y establece el mismo valor en la proyección `serviceAccountToken` del pod para que los tokens de audiencia predeterminada sean rechazados.
* **Usa una regla separada por namespace:** Crea una regla y una cuenta de servicio de Anthropic distintas para cada namespace en lugar de ampliar una sola regla.
* **Limita los emisores con JWKS inline a un solo clúster:** Cuando varios clústeres comparten una URL de emisor, registra el JWKS de cada clúster como su propio emisor de federación y vincula las reglas únicamente a ese emisor.

## Próximos pasos

* [Workload Identity Federation](https://platform.claude.com/docs/es/manage-claude/workload-identity-federation): conceptos, el flujo de intercambio de tokens y opciones de configuración del SDK.
* [Referencia de WIF](https://platform.claude.com/docs/es/manage-claude/wif-reference): variables de entorno, modos de fuente JWKS y modos de coincidencia de reglas.
