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

# Python SDK - Workload Identity Federation

> Bearer-JWT auth with Kubernetes, AWS, GCP, and Azure for the Python SDK.

For deployments that prefer not to manage static API keys, the Python SDK can authenticate via **Workload Identity Federation (WIF)**. The SDK fetches a workload OIDC token from the running environment, exchanges it for a Nexus session JWT, and uses `Authorization: Bearer <jwt>` on every subsequent API call.

WIF is opt-in. When `NexusConfig.wif` is `None` (the default), the SDK falls back to the static `api_key` flow and behaves exactly as in v0.1.0.

Both `NexusClient` (sync) and `AsyncNexusClient` (asyncio) support WIF.

## Enabling

```python theme={null}
from westyx_nexus import NexusClient, NexusConfig, WIFConfig

client = NexusClient.create(NexusConfig(
    base_url="https://blue-ocean-a5rx7.westyx.dev",
    wif=WIFConfig(enabled=True),  # provider="auto" by default
))
```

`api_key` becomes optional when WIF is enabled.

<Note>
  **Security note (v0.9.0):** `base_url` must use `https://` - `NexusClient.create` / `AsyncNexusClient.create` reject plain-http endpoints (loopback/localhost excepted for development), since credentials travel on every request.
</Note>

## Supported providers

When `provider` is `"auto"` the SDK probes the environment in this order and picks the first provider whose credential material is actually present *(v0.9.0 - file stats + live metadata probes, \~1 s timeout; earlier versions keyed on environment variables absent on real cloud nodes)*:

| Provider constant                        | Auto-detect signal                                                                                                                            | Credential                                                                                                                                                                                       |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `WIF_PROVIDER_KUBERNETES`                | `/var/run/secrets/kubernetes.io/serviceaccount/token` exists                                                                                  | reads the projected service-account token file (rotated by kubelet)                                                                                                                              |
| `WIF_PROVIDER_AWS`                       | `AWS_WEB_IDENTITY_TOKEN_FILE` set **and the file exists** (EKS IRSA)                                                                          | reads the IRSA token file at that path                                                                                                                                                           |
| `WIF_PROVIDER_AZURE` (Workload Identity) | `AZURE_FEDERATED_TOKEN_FILE` set and the file exists (AKS)                                                                                    | reads the projected federated token file                                                                                                                                                         |
| `WIF_PROVIDER_GCP`                       | `metadata.google.internal` reachable (live probe)                                                                                             | GETs `metadata.google.internal/computeMetadata/v1/.../identity?audience=<aud>` with `Metadata-Flavor: Google`                                                                                    |
| `WIF_PROVIDER_AZURE` (IMDS)              | `169.254.169.254/metadata` reachable (live probe)                                                                                             | GETs an Azure IMDS managed-identity token - **requires `audience="api://<client-id>"`** (your app registration's Application ID URI; the generic default is refused because Azure AD rejects it) |
| `WIF_PROVIDER_AWS_IAM`                   | **not auto-detected - select explicitly** (resolvable AWS credentials alone, e.g. a dev laptop's `~/.aws/credentials`, are too weak a signal) | SigV4-signs an STS `GetCallerIdentity` request (never sent to AWS) that Nexus replays to prove your IAM role - works on **ECS/Fargate, Lambda, and plain EC2**                                   |

The OIDC providers are httpx-only; `aws_iam` uses `botocore` (the optional `[aws-iam]` extra) for the credential/region chain and the SigV4 signer.

You can also pin a specific provider:

```python theme={null}
from westyx_nexus import WIF_PROVIDER_KUBERNETES

WIFConfig(enabled=True, provider=WIF_PROVIDER_KUBERNETES)
```

## AWS IAM (non-EKS AWS compute)

ECS/Fargate tasks, Lambda functions, and plain EC2 instances have IAM credentials but no OIDC token, so the OIDC providers cannot serve them. The `aws_iam` provider closes this gap. It requires the optional `botocore` dependency:

```sh theme={null}
pip install 'westyx-nexus-sdk[aws-iam]'
```

```python theme={null}
from westyx_nexus import WIF_PROVIDER_AWS_IAM

WIFConfig(
    enabled=True,
    provider=WIF_PROVIDER_AWS_IAM,
    aws_region="eu-central-1",  # optional - defaults to AWS_REGION / shared config / EC2 IMDS
)
```

The SDK SigV4-signs an STS `GetCallerIdentity` request with the standard AWS credential chain (task role, instance profile, env) - **never sending it to AWS** - and posts the signed headers + body to `/v1/auth/token-exchange` as `{"provider":"aws_iam","aws_sts_request":...}`. Nexus replays it against a pinned STS endpoint and matches the caller's IAM role against an `aws_iam` trust policy (subject = `arn:aws:iam::<account>:role/<name>`).

Nothing else to configure: the SDK signs your service's own host (the `base_url` host) into `X-Nexus-Server-ID` inside the signature, and Nexus verifies it against the host the request arrived on - so a captured signed request is valid for that ONE service only. Selecting `aws_iam` without `botocore` installed raises an actionable error naming the extra.

## Custom token source

Provide a `token_source` callable to bypass auto-detection. The callable may be sync (returns `str`) or async (returns `Awaitable[str]`); the async client awaits it automatically.

<CodeGroup>
  ```python title="Sync" theme={null}
  def my_token_source() -> str:
      return read_my_oidc_token()

  WIFConfig(enabled=True, token_source=my_token_source)
  ```

  ```python title="Async" theme={null}
  async def my_async_source() -> str:
      return await fetch_oidc_token_async()

  WIFConfig(enabled=True, token_source=my_async_source)
  ```
</CodeGroup>

When `token_source` is set it takes precedence over `provider`.

## Audience

GCP and Azure require an `audience` claim when issuing the OIDC token:

```python theme={null}
WIFConfig(
    enabled=True,
    provider="gcp",
    audience="westyx-nexus",
)
```

The default is `"westyx-nexus"` if unset.

**Azure IMDS is the exception:** the generic default is refused with a clear error, because Azure AD rejects it as a `resource`. On the IMDS path (plain VM / App Service - no federated token file) you MUST set `audience` to your app registration's Application ID URI (`api://<client-id>`). On AKS with Azure Workload Identity the projected federated token file (`$AZURE_FEDERATED_TOKEN_FILE`) is preferred automatically and no `audience` is needed.

## Token exchange flow

1. **OIDC token fetch** - the configured token source returns a workload-identity JWT.
2. **Token exchange** - the SDK POSTs `{"oidc_token": "<jwt>"}` to `/v1/auth/token-exchange`. The backend validates the OIDC issuer/signature and returns:
   ```json theme={null}
   {
     "session_token":  "<jwt>",
     "token_type":     "Bearer",
     "expires_in":     3600,
     "service_kind":   "backend",
     "wif_provider":   "kubernetes"
   }
   ```
3. **Session storage** - the SDK caches the session token in memory and uses `Authorization: Bearer <session>` on all subsequent `/sync` and `/stream` requests.
4. **Auto-refresh** - \~60 s before expiry the SDK silently re-runs steps 1-2 from a background thread (sync) or task (async). The active SSE stream is not interrupted.
5. **`auth_expiring` event** - when the server emits this control event over SSE, the SDK pre-emptively refreshes the session before the server force-closes the stream.

## Split httpx clients

The SDK uses **two separate `httpx.Client` instances** - one for short requests (`/sync`, `/token-exchange`, with `timeout=cfg.timeout_seconds`) and one for the long-lived SSE stream (with `timeout=None`). This prevents a consumer-supplied short timeout from killing the stream after a few seconds.

## Project-level vs service-level trust policies

The Nexus backend supports trust policies at two scopes:

| Scope             | What it covers                               | Recommended for                                   |
| ----------------- | -------------------------------------------- | ------------------------------------------------- |
| **Project-level** | All services in the project share one policy | Most teams - no per-service cloud identity needed |
| **Service-level** | A specific policy bound to one service only  | High-security services (payments, PII)            |

<Warning>
  A project-level trust policy is less restrictive than a service-level one. Any workload that satisfies the policy can connect to any service in the project by targeting its `base_url`. For services handling sensitive data (payments, credentials, PII) consider a dedicated service-level trust policy.
</Warning>
