Identity, Organization & Team
Scope: namespaced · Licensed (identity-management feature + organizations / teams caps)
These resources manage identities across the platform's components — the gateway (LiteLLM or Wäg), Langfuse, and LibreChat — as Kubernetes objects, so access and tenancy are declarative and auditable rather than configured by hand in each tool.
| Resource | Manages |
|---|---|
User | A person / seat — the natural key (email) tying that person's accounts together |
Identity | One per-backend account for a person, on the gateway, Observability (Langfuse), or LibreChat |
Organization | A top-level tenant / billing boundary |
Team | A group within an organization, with budgets and model access |
People vs. accounts
A User is a person (a licensed seat). An Identity is one of that person's backend accounts. The license users cap counts User seats; Identity accounts are uncapped, and each must point at a User via userRef. One person with a gateway key, a Langfuse membership, and a LibreChat login is one seat, three Identity objects.
Identity
An Identity provisions a single account for a person on one backend. Its spec.type selects which component the account lives in:
type | Emits |
|---|---|
gateway | Recommended. Follows the referenced Gateway's own spec.type — see Choosing the backend |
litellm | LiteLLM user resources (LiteLLMUser), checked against the referenced Gateway |
waeg | Wäg console user (WaegUser), checked against the referenced Gateway — see Wäg accounts |
observability | Membership of the Observability's Langfuse organization (needs a Langfuse Enterprise licence) |
librechat | LibreChat user / role configuration |
Spec
| Field | Type | Description |
|---|---|---|
type | enum (required) | gateway / litellm / waeg / observability / librechat |
userRef | { name } (required) | The owning User in the same namespace |
email | string (optional) | Account email; defaults to the owning User's spec.email |
displayName | string | Human-readable name |
userId | string | Stable external user id for the backend |
role | string | Backend role. On Wäg, exactly admin or platform_admin grants the cluster-wide platform-admin privilege — nothing else is inferred |
budget | object | Per-account budget (where the backend supports it; not applied on Wäg) |
teamRefs | list | Teams this account belongs to (LiteLLM; not applied on Wäg) |
gatewayRef | ObjectRef | The Gateway this account lives on. Required for type: gateway / litellm / waeg, and it must be in the same namespace — see Admission rules |
observabilityRef | ObjectRef | An Observability (for type: observability) |
chatUIRef | ObjectRef | A ChatUI (for type: librechat) |
passwordSecretRef | SecretKeyRef | Password Secret (for type: librechat) |
Choosing the backend: type: gateway
Identity, Organization and Team all accept type: gateway, and it is the value to use for new resources. A gateway-backed identity resource targets a Gateway — and that Gateway already knows which implementation it runs. With type: gateway the operator reads the backend from the referenced Gateway's own spec.type, which is the one source that cannot drift.
spec:
type: gateway # resolved from the Gateway below
gatewayRef: { name: forge-gateway }The product-named values litellm and waeg still work and are not scheduled for removal. What changed is that they are now checked against the Gateway instead of trusted. Declaring a type that contradicts the Gateway is refused with Ready=False:
type=litellm does not match Gateway "forge-gateway", which is type=waeg —
set type: gateway to follow the Gateway, or point gatewayRef at a litellm gatewayThe message names the declared type, the Gateway, the Gateway's actual type, and both ways out: follow the Gateway with type: gateway, or point gatewayRef at a gateway of the type you declared.
Why this check exists
Nothing used to compare the two. An Organization / Team / Identity declaring type: litellm against a Wäg gateway was accepted, and the operator emitted a LiteLLMOrganization / LiteLLMTeam / LiteLLMUser whose instanceRef named a Wäg instance — an object that binds to nothing and materializes in no gateway.
On a cluster where both operators are installed that apply succeeds: the CRD exists, the object is stored, and nothing ever surfaces the mistake. The account simply never appears in the gateway. The mismatch is now a refusal instead of a silently orphaned object.
Admission rules
Two rules are enforced by the API server on Identity, Team andOrganization, so a bad reference is refused at kubectl apply rather than hours later in a status condition.
1. A gateway-backed type requires gatewayRef.
type gateway/litellm/waeg requires gatewayRefgatewayRef used to be optional on every one of these resources. A Team with type: waeg and no gatewayRef was admitted and only failed at reconcile with "a gateway-backed identity resource requires gatewayRef" — the same information, hours later.
2. gatewayRef.namespace must be empty.
gatewayRef.namespace must be empty: the Gateway must live in the same namespace as this resourceBoth the litellm-operator and the waeg-operator resolve the upstream instanceRefnamespace-locally, so a cross-namespace gatewayRef could never bind to anything. Put the Gateway in the same namespace as the identity resources that target it, and set only { name: … }.
LibreChat accounts
LibreChat has no API to create accounts, so an Identity with type: librechat is provisioned by running LibreChat's bundled create-user script as a short-lived Kubernetes Job. The Job is cloned from the running LibreChat container (same image and environment), so it reaches the same MongoDB automatically.
- The referenced
ChatUI(spec.chatUIRef) must be Ready and in the same namespace as theIdentity. - Provisioning is idempotent and create-only: an existing account is treated as success, and later edits to the
Identitydo not change an account that already exists. - Password: set
spec.passwordSecretRef(keypasswordby default) to use a known password. If you omit it, the operator generates one and stores it in an owned Secret named<identity>-librechat-password(keypassword). Retrieve it withkubectl get secret <identity>-librechat-password -o jsonpath='{.data.password}' | base64 -dand rotate it after first login. The password is never passed on the command line.
Wäg accounts
Identity, Team and Organization are fully supported on a Wäg gateway. They emit WaegUser, WaegTeam, WaegOrganization and the org- and team-scoped WaegBudget policies (all in gateway.waeg.ai/v1alpha1).
Console password. Wäg requires a password to create a console user. For a Wäg-backed Identity the operator generates one into an owned Secret named <identity-name>-waeg-password, key password, and points the WaegUser at it via spec.passwordSecretRef. Read it back for the person's first login with:
kubectl get secret <identity>-waeg-password \
-o jsonpath='{.data.password}' | base64 -dThe Secret is generated once and never rotated automatically — rotating the Secret rotates the console password. Nothing else needs to be set; there is no field to supply your own password for a Wäg account.
Ready means the account exists. A Wäg identity resource reports Ready only once the emitted WaegUser itself reports Synced=True. Applying the upstream CR used to be treated as success, which hid exactly the failure above: with no password Secret the WaegUser sat permanently at Synced=False — "passwordSecretRef is required to create a new console user", while the Identity showed green and the console account did not exist. A green Identity now means the gateway accepted the account.
Team members. Wäg addresses a team member by WaegUser, never by email. For a member who also has an Identity on the same gateway, the team points at that Identity's WaegUser — one console user per person. Only an address with no Identity gets a separate membership WaegUser (named <team>-<email-slug>), and it is deleted again when the address leaves the team. Removing it never deletes the person's Wäg account: Wäg deletes a directory user only when the CR being deleted created it, which a membership user (which carries no password) never does.
Not applied on Wäg — each one is reported as an Event (UnsupportedByBackend), never silently dropped:
| Field | Why |
|---|---|
Identity.spec.teamRefs | Wäg keeps membership on the team, not the user. Add the person's address to the Team's spec.members instead |
Identity.spec.budget | A Wäg per-user cap is a WaegBudget with scope: user, which this operator does not emit |
Organization.spec.members | WaegOrganization models no members — org membership is an Admin API endpoint with no CR. A Team's members are applied |
spec.budget.budgetDuration (Organization / Team) | Wäg budget policies have no period field |
spec.budget.models (Organization / Team) | Model allow-lists are the separate WaegModelAccess ACL, not a budget field |
Organization & Team
| Resource | Manages |
|---|---|
Organization | A top-level tenant / billing boundary |
Team | A group within an organization, with budgets and model access |
Each carries a type that selects which component's identity it drives:
type | Emits |
|---|---|
gateway | Recommended. Follows the referenced Gateway's own spec.type |
litellm | LiteLLMOrganization / LiteLLMTeam |
waeg | WaegOrganization / WaegTeam, plus the matching WaegBudget policy |
observability | Langfuse org / project / membership resources (teams are scaffolded) |
Both accept a gatewayRef — required for gateway / litellm / waeg, and same-namespace only (Admission rules). Organization also exposes an observabilityRef to point at the Observability workload when type: observability.
A Team references its parent through spec.organizationRef ({ name }, same namespace). On LiteLLM the parent is optional; on Wäg it is required — Wäg teams are always rooted at an organization. A type: waeg Team without it is rejected at admission (spec.organizationRef: Required value); a type: gateway Team on a Wäg gateway is refused at reconcile instead, since admission cannot see the Gateway's type. The parent's own type is resolved the same way, so an Organization of type: gateway on a Wäg gateway is a valid parent for a Wäg team; an Organization on the other product is reported as a mismatch.
Example
One Gateway, one organization, one team, one person — and none of the identity resources name the gateway product. Switching the Gateway between litellm and waeg leaves all four unchanged.
apiVersion: core.navique.com/v1alpha1
kind: Organization
metadata:
name: acme
namespace: forge
spec:
type: gateway # resolved from the Gateway
gatewayRef: { name: forge-gateway } # same namespace — no `namespace:` field
displayName: ACME Corp
budget:
maxBudget: 500
---
apiVersion: core.navique.com/v1alpha1
kind: Team
metadata:
name: platform
namespace: forge
spec:
type: gateway
gatewayRef: { name: forge-gateway }
organizationRef: { name: acme } # required on Wäg, optional on LiteLLM
displayName: Platform Team
members:
- email: alice@example.com # on Wäg, membership lives here
role: admin
---
apiVersion: core.navique.com/v1alpha1
kind: User
metadata:
name: alice
namespace: forge
spec:
email: alice@example.com
displayName: Alice Example
---
apiVersion: core.navique.com/v1alpha1
kind: Identity
metadata:
name: alice-gateway
namespace: forge
spec:
type: gateway
userRef: { name: alice } # the licensed seat, same namespace
gatewayRef: { name: forge-gateway }
role: admin
# email omitted → inherits alice@example.com from the UserOn a type: waeg gateway this also produces the generated console password in alice-gateway-waeg-password — see Wäg accounts.
How it relates to the Gateway
A Gateway can declare an inline organization and teams[]. The identity resources are the broader, cross-component mechanism: they let you manage organizations, teams, people (User), and their backend accounts (Identity) once and have them reflected consistently across the gateway, Langfuse, and LibreChat, including across multiple gateways.
Licensing
Identity management is part of the multi-tenancy value and requires the identity-management feature plus the relevant organizations / teams instance caps in the License. The users cap counts User seats — not Identity accounts, which are uncapped. In the Community edition, use the inline organization / teams[] on a single Gateway.
See Editions & Licensing.