> ## 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.

# Rust SDK - Workload Identity Federation

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

For deployments that prefer not to manage static API keys, the Rust 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 `wif` is `None` (the default), the SDK uses the static `api_key` flow.

## Enabling

```rust theme={null}
use westyx_nexus::{NexusClient, NexusConfig, WifConfig};

let client = NexusClient::create(NexusConfig {
    api_key: String::new(), // empty - WIF provides auth
    endpoint: "https://blue-ocean-a5rx7.westyx.dev".to_string(),
    ttl: None,
    wif: Some(WifConfig {
        enabled: true,
        provider: "auto".to_string(), // auto-detect from environment
        token_source: None,
        audience: None,               // GCP/Azure-IMDS audience (see below)
        aws_region: None,             // aws_iam STS region (see below)
    }),
})?;
```

`api_key` becomes optional when WIF is enabled. If you provide both, the SDK uses WIF and treats `api_key` as a fallback if session refresh fails after the initial connect.

<Note>
  **Security note (v0.9.0-beta.1):** `endpoint` must use `https://` - `NexusClient::create` rejects 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-beta.1 - the token files are stat-ed and the metadata servers live-probed, \~1 s timeout)*:

| Provider string               | Auto-detect signal                                                                                 | Credential                                                                                                                   |
| ----------------------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `"kubernetes"`                | `/var/run/secrets/kubernetes.io/serviceaccount/token` exists                                       | Reads the projected service-account token file (rotated by kubelet)                                                          |
| `"aws"`                       | `AWS_WEB_IDENTITY_TOKEN_FILE` set **and the file exists** (EKS IRSA)                               | Reads the IRSA token file at `AWS_WEB_IDENTITY_TOKEN_FILE`                                                                   |
| `"azure"` (Workload Identity) | `AZURE_FEDERATED_TOKEN_FILE` set and the file exists (AKS)                                         | Reads the projected federated token file                                                                                     |
| `"gcp"`                       | GCE metadata server reachable (live probe)                                                         | Fetches a Google-signed OIDC identity token from the GCE metadata server (`.../identity?audience=westyx-nexus`)              |
| `"azure"` (IMDS)              | Azure IMDS reachable (live probe)                                                                  | Fetches an IMDS managed-identity token - **requires `audience: api://<client-id>`** (the generic default is refused)         |
| `"aws_iam"`                   | **not auto-detected - select explicitly** (resolvable AWS credentials alone are too weak a signal) | SigV4-signs an STS `GetCallerIdentity` request (never sent to AWS) - non-EKS AWS (ECS/Fargate, Lambda, plain EC2); see below |

You can also pin a specific provider:

```rust theme={null}
WifConfig {
    enabled: true,
    provider: "kubernetes".to_string(),
    token_source: None,
    audience: None,
    aws_region: None,
}
```

## AWS IAM (non-EKS AWS compute) *(v0.9.0-beta.1)*

ECS/Fargate tasks, Lambda functions, and plain EC2 instances have IAM credentials but no OIDC token. The `aws_iam` provider SigV4-signs an STS `GetCallerIdentity` request with the standard AWS credential chain - **never sending it to AWS** - and posts the signed headers + body to `/v1/auth/token-exchange`. 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>`).

It is gated behind the **`aws-iam` cargo feature** (off by default; pulls in `aws-config` + `aws-sigv4`):

```toml theme={null}
[dependencies]
westyx-nexus = { version = "0.10.1-beta.1", features = ["aws-iam"] }
```

```rust theme={null}
WifConfig {
    enabled: true,
    provider: "aws_iam".to_string(),
    token_source: None,
    audience: None,
    aws_region: Some("eu-central-1".to_string()), // optional - defaults to the AWS region chain + EC2 IMDS
}
```

Selecting `aws_iam` without the feature returns an actionable `NexusError`. The signed `X-Nexus-Server-ID` is your service's own endpoint host, verified by the backend against the host the request arrived on - so a captured signed request is valid for that ONE service only.

## Custom `token_source`

Provide a closure to bypass auto-detection entirely. This is the right hook for tests, custom CI flows, or topologies the SDK does not natively recognise.

```rust theme={null}
use westyx_nexus::{WifConfig, NexusError};

WifConfig {
    enabled: true,
    provider: "auto".to_string(),
    token_source: Some(Box::new(|| -> Result<String, NexusError> {
        // Anything that produces a JWT acceptable to /v1/auth/token-exchange.
        read_my_oidc_token().map_err(|e| NexusError::WifTokenExchangeFailed(e.to_string()))
    })),
    audience: None,
    aws_region: None,
}
```

When `token_source` is `Some`, it takes precedence over `provider`.

## Token exchange flow

Internally, the SDK runs this protocol:

1. **OIDC token fetch** - `WifConfig.token_source` (or the auto-detected built-in) returns a workload-identity JWT.
2. **Token exchange** - the SDK POSTs `{"oidc_token": "<jwt>"}` to `/v1/auth/token-exchange`. No `X-Nexus-API-Key` or `Authorization` header is sent on this request. The backend validates the OIDC issuer and signature, then returns:
   ```json theme={null}
   {
     "session_token": "<short-lived-nexus-jwt>",
     "token_type":    "Bearer",
     "expires_in":    3600,
     "service_kind":  "backend",
     "wif_provider":  "kubernetes"
   }
   ```
3. **Bearer auth on subsequent calls** - every `/v1/*` request carries `Authorization: Bearer <session_token>`.
4. **Pre-emptive refresh** - when a request finds the session expires within 60 s, the SDK refreshes (re-running steps 1-2) before issuing the call.

## SSE `auth_expiring` event

About 5 minutes before the session JWT expires, the backend emits an `auth_expiring` SSE control event. The background SSE thread reacts by triggering a non-blocking session refresh - it does **not** call `/sync`, and the event is not surfaced to user code. When the server force-closes the stream at expiry, the thread reconnects with the freshly-issued bearer.

## Errors

| Variant                                   | When                                                                                   |
| ----------------------------------------- | -------------------------------------------------------------------------------------- |
| `NexusError::WifNotConfigured`            | `wif.enabled = true` but no usable provider matched and no `token_source` was supplied |
| `NexusError::WifTokenExchangeFailed(msg)` | `POST /v1/auth/token-exchange` returned non-2xx or body could not be decoded           |
| `NexusError::SessionExpired`              | Session JWT expired and refresh failed; no static `api_key` fallback available         |

```rust theme={null}
match NexusClient::create(config) {
    Err(NexusError::WifNotConfigured) => {
        eprintln!("WIF enabled but no provider matched - check environment variables");
    }
    Err(NexusError::WifTokenExchangeFailed(msg)) => {
        eprintln!("token exchange failed: {}", msg);
    }
    Ok(client) => { /* proceed */ }
    Err(e) => return Err(e),
}
```

## Service kind awareness

The token-exchange response includes `service_kind` (`"frontend"` or `"backend"`). The SDK stores this and exposes it via `client.kind()`. If the resolved kind is `"frontend"`, `get_secret(...)` returns `NexusError::ServiceKindMismatch` regardless of key type - frontend services are public by design and must not hold secrets.

## Project-level vs service-level trust policies

The Nexus backend supports WIF 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)            |

At token exchange time the backend applies this resolution order:

1. Does a **service-level** trust policy match this workload's OIDC token claims? - if yes, authorise.
2. Does a **project-level** trust policy match? - if yes, authorise.
3. Neither matches - return 401.

This means you can configure **one project-level trust policy** and all services in the project will accept that workload's tokens. Each service SDK instance still uses its own `endpoint`, so each gets its own service's data.

<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 endpoint URL. If a process is compromised, an attacker could read configs and secrets of sibling services.

  For services handling sensitive data (payments, credentials, PII) consider a dedicated service-level trust policy instead.
</Warning>
