Gateway
Portée : namespaced · Charge de travail : LiteLLM via le litellm-operator, ou Wäg via le waeg-operator
La passerelle IA — la porte d'entrée du trafic de modèles, avec modèles, équipes, organisations, budgets, et export optionnel de traces vers Langfuse.
spec.type choisit l'implémentation. Sa valeur par défaut est litellm, donc le reste de cette page décrit la passerelle LiteLLM sauf mention contraire ; voir Passerelle Wäg (type: waeg) pour les différences.
Spec
| Champ | Type | Description |
|---|---|---|
type | litellm | waeg | Implémentation de la passerelle. Défaut litellm. |
waeg | object | Câblage propre à Wäg (topologie, plans ClickHouse/Redis, OpenFGA). Requis lorsque type: waeg |
secretsRef | LocalRef (optionnel) | SecretsManagement du même namespace à attendre. Omettez-le pour utiliser de simples Secrets Kubernetes que vous gérez vous-même — voir secretsRef |
database | object (requis) | Où LiteLLM stocke son état |
instance | object | Image/tag, réplicas, ressources, clés master/salt, SSO |
organization | object | Organisation LiteLLM + budgets |
teams[] | list | Équipes avec budgets par équipe |
models[] | list | Le catalogue de modèles |
guardrailRefs | []ObjectRef | CRs Guardrail à câbler dans ce gateway (sous licence guardrail ; émis comme LiteLLMGuardrail via le proxy de contrôle de licence de chaque garde-fou) |
observabilityRef | ObjectRef | Un Observability pour l'export de traces (câblé automatiquement si sous licence) |
observability | object | Fallback manuel du callback Langfuse |
sso | object | Connexion OIDC/SSO pour l'Admin UI de LiteLLM (voir SSO) |
enableEntraSSO | bool | Déprécié — utilisez sso avec provider: azure-entra |
database
| Champ | Description |
|---|---|
mode | postgresCluster (référence un PostgresCluster) ou external |
postgresClusterRef | Le cluster à utiliser (mode postgresCluster) |
databaseName | Base de données au sein du cluster partagé |
connectionSecretRef | Secret DATABASE_URL (mode external) |
models[]
| Champ | Description |
|---|---|
name / modelName / model | Nom d'affichage, nom de modèle LiteLLM, et identifiant de modèle du fournisseur |
refSecretKey | Clé dans le Secret d'identifiants de modèle contenant la clé d'API |
credentials.apiBase | Endpoint du fournisseur, pour tout fournisseur qui en exige un (Azure OpenAI / AI Foundry, auto-hébergé, un proxy) |
credentials.apiVersion | Version d'API du fournisseur, lorsqu'il en exige une (par ex. Azure OpenAI / AI Foundry) |
rpm / tpm / timeout / maxTokens | Limites par modèle (LiteLLM uniquement) |
provider / providerName / modelId / fallbacks / weight | Champs de catalogue propres à Wäg — voir Passerelle Wäg |
Lorsqu'un modèle nécessite un endpoint et/ou une version d'API spécifiques, définissez-les sous credentials — l'opérateur les transmet au gateway avec la clé d'API issue de refSecretKey. Gardez apiBase comme endpoint nu et mettez la version dans apiVersion (n'intégrez pas ?api-version= dans l'URL). C'est indépendant du fournisseur : l'opérateur émet ce que vous fournissez et ne traite aucun fournisseur de manière particulière.
instance.healthCheck
Règle les vérifications de santé (health checks) des modèles en arrière-plan de LiteLLM. Désactivé par défaut : GET /health sonde les modèles à la demande au lieu d'exécuter une boucle en arrière-plan. Les vérifications en arrière-plan génèrent un trafic amont périodique et peuvent entraîner des coûts ou atteindre les limites de débit chez certains fournisseurs — ne l'activez donc que si vous voulez que /health renvoie des résultats mis en cache.
| Champ | Type | Description |
|---|---|---|
enabled | bool | Active les vérifications de santé en arrière-plan. Par défaut false (désactivé). |
intervalSeconds | int | Secondes entre les vérifications (défaut LiteLLM 300). Ne s'applique que si enabled: true. |
instance:
healthCheck:
enabled: true
intervalSeconds: 300Correspond à generalSettings.backgroundHealthChecks / healthCheckInterval de la LiteLLMInstance. Lorsqu'elles sont désactivées, l'opérateur définit explicitement backgroundHealthChecks: false.
Ce qu'il émet
Le contrôleur résout la base de données, garantit la présence du litellm-operator (en attendant que ses CRD soient Established), puis émet, dans l'ordre :
LiteLLMInstance → LiteLLMOrganization → LiteLLMTeam(s)
→ LiteLLMCredential(s) → LiteLLMModel(s)(Tous dans le groupe d'API litellm.palena.ai/v1alpha1.)
Câblage de la base de données
postgresCluster— l'opérateur résout le cluster référencé, provisionne la base de données et le rôlelitellm, génère le mot de passe, et injecte le Secret d'identifiants. Aucun secret manuel n'est nécessaire.external— fournissez unconnectionSecretRefcontenant unDATABASE_URL.
Export de traces Langfuse
- Avec la fonctionnalité
auto-wiringet unobservabilityRef, l'opérateur câble le callback Langfuse de LiteLLM (hôte + clés publique/secrète) à partir de l'Observabilityréférencé pour que les traces s'exportent automatiquement. - Sans cela (ou avec un Langfuse community qui ne peut pas générer de clés de projet), créez le projet + la clé dans l'interface Langfuse et définissez
spec.observability.{host, callbackSecretRef}(cléspublicKey/secretKey).
Consultez Auto-Wiring pour les deux conditions impliquées.
SSO / OIDC login
spec.sso active l'authentification unique pour l'Admin UI de LiteLLM, traduite vers le bloc LiteLLMInstance.spec.sso. Les identifiants du client OAuth proviennent d'un Secret du même namespace (via SecretsManagement — jamais en ligne).
| Champ | Description |
|---|---|
issuerURL | URL de base de l'émetteur OIDC / de découverte |
clientSecretRef.name | Secret contenant le client OAuth (les clés par défaut sont client-id / client-secret, modifiables via clientIDKey / clientSecretKey) |
provider | generic-oidc (par défaut), azure-entra, google, ou okta |
tenantID | ID de répertoire/locataire (pour azure-entra) |
authorizationEndpoint / tokenEndpoint / userinfoEndpoint | Endpoints explicites — requis pour generic-oidc (LiteLLM n'effectue pas de découverte) |
scopes | Scopes demandés (par défaut openid, profile, email) |
providerName | Libellé d'affichage |
spec:
sso:
provider: generic-oidc
issuerURL: https://idp.example.com
authorizationEndpoint: https://idp.example.com/authorize
tokenEndpoint: https://idp.example.com/token
userinfoEndpoint: https://idp.example.com/userinfo
clientSecretRef: { name: gateway-oidc }Licence
Le SSO LiteLLM est gratuit jusqu'à 5 utilisateurs ; un SSO complet/illimité nécessite une licence LiteLLM Enterprise. L'opérateur Navique ne restreint pas le champ lui-même.
URL de redirection en accès public
LiteLLM construit la redirect_uri OAuth à partir de PROXY_BASE_URL, dérivée de l'ingress du Gateway. Pour le SSO en accès public, exposez le Gateway sur son hôte public avec TLS — spec.ingress.enabled: true, spec.ingress.host: <hôte-public> et spec.ingress.tls: true — afin que le callback se résolve en https://<hôte-public>/sso/callback. Enregistrez exactement cette URL auprès de votre IdP. Sans ingress.tls: true, la redirection retombe sur une adresse http/interne au cluster et le callback OAuth échoue. (status.endpoint de la LiteLLMInstance sous-jacente indique toujours l'URL .svc interne au cluster — c'est l'adresse de l'API d'administration de l'opérateur lui-même, pas la base de la redirection SSO.)
Le booléen déprécié enableEntraSSO: true fonctionne toujours lorsque sso n'est pas défini — il synthétise une configuration azure-entra lisant l'ancien Secret entra-sso-credentials. Préférez sso avec provider: azure-entra.
Authentification d'API par JWT & RBAC
spec.instance.jwtAuth active l'authentification d'API par JWT : LiteLLM valide un JWT porteur de votre IdP à chaque requête et mappe ses claims sur des rôles/équipes (distinct de sso, la connexion navigateur de l'interface d'administration). Associez-le à spec.instance.rolePermissions pour restreindre les modèles qu'un rôle peut appeler.
| Champ | Correspond à (litellm_jwtauth) |
|---|---|
jwtAuth.enabled | general_settings.enable_jwt_auth |
jwtAuth.publicKeyURL | point de terminaison JWKS (JWT_PUBLIC_KEY_URL) — requis : sans lui, LiteLLM ne peut valider aucun token |
jwtAuth.issuer | émetteur de token attendu iss (JWT_ISSUER) |
jwtAuth.audience | audience de token attendue aud (JWT_AUDIENCE) |
jwtAuth.userRolesJWTField | user_roles_jwt_field — claim JWT contenant la liste des rôles |
jwtAuth.userAllowedRoles | user_allowed_roles — rôles mappés sur un internal_user |
jwtAuth.enforceRBAC | enforce_rbac — refuser les appelants aux rôles non autorisés |
jwtAuth.userRoleJWTField | user_role_jwt_field (rôle unique) |
jwtAuth.userIDJWTField | user_id_jwt_field — claim servant d'identifiant utilisateur (sub / oid / preferred_username) |
jwtAuth.userIDUpsert | user_id_upsert — crée automatiquement l'utilisateur LiteLLM à la première connexion |
jwtAuth.teamIDsJWTField | team_ids_jwt_field |
jwtAuth.adminJWTScope | admin_jwt_scope |
rolePermissions.<rôle>.models / .routes | general_settings.role_permissions |
spec:
instance:
licenseSecretRef: { name: litellm-enterprise-license, key: license }
jwtAuth:
enabled: true
publicKeyURL: https://login.microsoftonline.com/<tenant>/discovery/v2.0/keys # REQUIS (JWKS)
issuer: https://login.microsoftonline.com/<tenant>/v2.0
audience: <client-id>
userIDJWTField: sub # claim identifiant l'utilisateur (sub / oid / preferred_username)
userIDUpsert: true # crée l'utilisateur LiteLLM à la première connexion
userRolesJWTField: roles
userAllowedRoles: ["basic_user"]
enforceRBAC: true
rolePermissions:
internal_user:
models: ["anthropic-claude"]Sur une passerelle Wäg, ce même bloc jwtAuth active le module JWT de plan de données propre à Wäg, dont les réglages spécifiques vivent sous spec.waeg.jwt — voir Authentification JWT du plan de données.
Enterprise uniquement
enable_jwt_auth, enforce_rbac et role_permissions sont des fonctionnalités LiteLLM Enterprise — renseignez instance.licenseSecretRef avec une licence LiteLLM Enterprise valide, sinon elles restent sans effet. Définir une entrée rolePermissions active general_settings.enforce_rbac pour que les restrictions s'appliquent.
Exemple
apiVersion: core.navique.com/v1alpha1
kind: Gateway
metadata:
name: gateway
namespace: forge-gateway
spec:
secretsRef: { name: forge-secrets }
database:
mode: postgresCluster
postgresClusterRef: { name: forge-pg, namespace: forge-data }
databaseName: litellm
instance:
image: { repository: ghcr.io/berriai/litellm, tag: v1.86.1 }
replicas: 1
masterKey: { autoGenerate: true }
saltKey: { autoGenerate: true }
observabilityRef: { name: observability, namespace: forge-langfuse }
organization:
name: navique-ag
maxBudget: 2000
budgetDuration: 30d
rpmLimit: 1000
tpmLimit: 200000
teams:
- { name: data-engineering, maxBudgetMonthly: 1000, budgetDuration: 30d }
models:
- name: gpt-5.4
modelName: gpt-5.4
model: azure/gpt-5.4 # id fournisseur/déploiement (ici : un déploiement Azure OpenAI)
refSecretKey: OPENAI_API_KEY
rpm: 300
tpm: 80000
timeout: 120
credentials:
apiBase: "https://forge-foundry.cognitiveservices.azure.com"
apiVersion: "2024-10-21"Passerelle Wäg (type: waeg)
Wäg est une passerelle IA alternative, pilotée par le waeg-operator embarqué (gateway.waeg.ai). C'est un produit différent, pas un clone de LiteLLM, et cette CRD ne prétend pas le contraire : tout ce qui suit est une différence réelle, et tout ce que Wäg ne sait pas faire est signalé, jamais abandonné en silence.
Ce qui change
| Sujet | LiteLLM | Wäg |
|---|---|---|
| Stockage | un Postgres (+ Redis optionnel) | deux plans par défaut (storageMode: split) : Postgres et ClickHouse. storageMode: single garde les analyses dans le Postgres du plan de contrôle et ne requiert aucun ClickHouse |
| Redis | optionnel | requis dès que l'API sert plus d'un réplica (quotas HA) |
| Autorisation | rôles du proxy / clés virtuelles | OpenFGA — externe, ou adossé à Postgres et déployé par l'opérateur |
| Modèle de processus | un Deployment de proxy | AllInOne, ou Split (api + worker optionnel) |
| Catalogue | un objet modèle | provider (connexion + credential) et alias de modèle qui le référence |
| Budgets | portés par les objets org/équipe | politiques de budget enracinées dans l'organisation, séparées |
spec.waeg
| Champ | Description |
|---|---|
topology | AllInOne (défaut) ou Split (*-api plus un Deployment *-worker optionnel) |
jobWorkers | Workers de tâches durables in-process (AllInOne uniquement) |
worker | Le Deployment de workers dédié (enabled, replicas, jobWorkers, resources) — exige topology: Split |
storageMode | Où vivent les analyses : split (défaut — ClickHouse) ou single (le Postgres du plan de contrôle, sans aucun ClickHouse) — voir Mode de stockage analytique |
clickhouse | Le plan analytique — requis pour storageMode: split, ignoré et non requis pour single : mode: ref vers un ClickHouseCluster, ou mode: external avec un Secret de connexion |
clickhouseDatabase | La base ClickHouse dans laquelle les analyses sont écrites en mode split. Défaut waeg ; l'opérateur la crée sur un cluster managed ou adopt |
redis | Le magasin de quotas HA : mode: ref vers une RedisInstance, ou external. Requis au-delà d'un réplica d'API |
openfga | apiUrl / apiUrlSecretRef externes, ou omettez-les et l'opérateur déploie un OpenFGA adossé à Postgres (image, replicas, resources, storeId, modelId) |
autoscaling | HPA de l'API sur CPU/mémoire (enabled, minReplicas, maxReplicas, cibles). La mise à l'échelle sur la profondeur de file nécessite KEDA et n'est pas câblée |
artifacts | Volume pour les tâches/médias : emptyDir (défaut), pvc (un claim existant) ou none |
bootstrapAdmin | Crée le premier administrateur de la console depuis un Secret (secretRef, emailKey, passwordKey) |
dataEncryptionKeySecretRef | La clé avec laquelle Wäg scelle les révisions de configuration (l'équivalent de la clé salt de LiteLLM) |
configYAML | Remplace l'amorce waeg.yaml. Valeurs non secrètes uniquement — le contenu atterrit dans une ConfigMap |
brandingConfigMapRef | Votre propre thème Enterprise (JSON) que l'opérateur envoie à l'API d'administration une fois Ready. Il remplace le thème Navique intégré — fonctionnalité sous licence custom-branding ; voir Habillage de la console |
defaultBranding | Applique le thème Navique intégré lorsque la passerelle possède une licence Enterprise et qu'aucun brandingConfigMapRef n'est défini. Défaut true |
modelAccess | L'ACL de modèles enracinée dans l'organisation (openByDefault, fallbackMode, grants[]). Exige spec.organization |
scim | Approvisionnement des utilisateurs en SCIM v2 (enabled, tokenSecretRef, defaultRole, defaultOrgID, orgSource, roleMap, orgMap) — voir Approvisionnement SCIM |
jwt | Les réglages JWT du plan de données propres à Wäg (appClaim, appClaimFallbacks, tenantClaim, requireRegisteredApplication, allowMasterKey, allowVirtualKeys, insecureSkipVerify) — voir Authentification JWT du plan de données |
Postgres provient toujours de spec.database — le même champ, résolu de la même façon. L'opérateur rassemble toutes les chaînes de connexion dans un unique Secret <gateway>-waeg-storage qu'il possède, et le référence par clé : aucune URL porteuse d'identifiants n'atterrit jamais dans la CR.
Mode de stockage analytique
Wäg conserve son plan de contrôle (configuration, clés, organisations, équipes, applications) dans Postgres, et son plan analytique (journaux de requêtes, usage, dépenses) dans un magasin choisi par spec.waeg.storageMode :
| Mode | Les analyses vivent dans | ClickHouse nécessaire |
|---|---|---|
split (défaut) | ClickHouse — spec.waeg.clickhouse doit le désigner | oui |
single | le même Postgres que le plan de contrôle (spec.database) | non |
split est le défaut de cet opérateur : c'est ce que la Gateway a toujours émis, et cela tient la charge. Le défaut de Wäg lui-même est single, et c'est son point de départ recommandé — c'est précisément la raison d'être de ce champ. Exiger un ClickHouse pour chaque passerelle imposait une dépendance que l'amont avait déjà abandonnée ; pour un déploiement modeste ou d'évaluation, elle double le nombre de datastores à exploiter, dimensionner et sauvegarder sans rien apporter.
Avec single, la WaegInstance émise ne porte aucun bloc clickhouse ; spec.waeg.clickhouse est ignoré et n'est pas requis.
Changer de mode ne migre pas l'historique analytique
Les deux plans sont des magasins distincts. Modifier storageMode sur une passerelle en service la pointe vers l'autre : les analyses déjà écrites restent où elles sont et disparaissent de la console. Ni l'opérateur ni Wäg ne les recopie. Choisissez un mode avant de collecter des données qui comptent, ou exportez-les d'abord.
single — Postgres uniquement
apiVersion: core.navique.com/v1alpha1
kind: Gateway
metadata:
name: waeg-gateway
namespace: forge-gateway
spec:
type: waeg
secretsRef: { name: forge-secrets }
database:
mode: postgresCluster
postgresClusterRef: { name: forge-pg, namespace: forge-data }
databaseName: waeg
instance:
replicas: 1
masterKey: { autoGenerate: true }
waeg:
storageMode: single # les analyses atterrissent dans forge-pg, à côté du plan de contrôle
# aucun bloc clickhouse — inutile, et un bloc placé ici serait ignorésplit — analyses dans ClickHouse
spec:
type: waeg
secretsRef: { name: forge-secrets }
database:
mode: postgresCluster
postgresClusterRef: { name: forge-pg, namespace: forge-data }
databaseName: waeg
instance:
replicas: 2
masterKey: { autoGenerate: true }
waeg:
storageMode: split # le défaut ; peut être omis
clickhouse: { mode: ref, ref: { name: forge-ch, namespace: forge-data } }
clickhouseDatabase: waeg # le défaut
redis: { mode: ref, ref: { name: forge-redis, namespace: forge-data } }Wäg ne crée que ses tables, jamais sa base de données. Sur un ClickHouseCluster en mode managed ou adopt, l'opérateur déclare cette base sous spec.databases et la crée pour vous ; sur un cluster external, vous devez la créer vous-même, sans quoi la passerelle s'arrête au démarrage avec Database waeg does not exist.
Modèles : provider + alias
Le catalogue de Wäg sépare la connexion amont de l'alias exposé aux clients ; chaque entrée de spec.models émet donc un WaegProvider et un WaegModel. Les modèles partageant un pilote partagent un provider.
| Champ | Description |
|---|---|
provider | Pilote du provider (openai, azure, anthropic, …). Par défaut le préfixe de model (azure/gpt-4o → azure), sinon openai |
providerName | Nomme l'entrée de catalogue du provider, pour que plusieurs modèles partagent une connexion. Par défaut le pilote |
modelId | Identifiant du modèle côté provider. Par défaut model sans son préfixe (azure/gpt-4o → gpt-4o) |
fallbacks | Alias de modèles à essayer en cas d'échec |
weight | Pondère ce déploiement face aux autres du même alias |
Ce qu'elle émet
WaegInstance → WaegOrganization → WaegBudget (limites org) → WaegTeam(s)
→ WaegProvider(s) → WaegModel(s) → WaegModelAccess(Le tout dans le groupe d'API gateway.waeg.ai/v1alpha1.)
Licence Enterprise
spec.instance.licenseSecretRef porte la licence Wäg Enterprise de la passerelle. Elle atteint l'instance sous la forme WaegInstance.spec.secrets.licenseKey ; la clé du Secret vaut license par défaut.
Chaque module Wäg Enterprise en dépend — SSO, SCIM, audit, CMEK, FIPS et habillage — et l'application de la règle est inconditionnelle : une image de passerelle liée à l'EE mais sans licence répond 402 license_required. Exécuter la build Enterprise ne suffit donc pas à elle seule ; si un module signale LicenseRequired, c'est précisément ce champ qui manque.
spec:
instance:
licenseSecretRef: { name: waeg-enterprise-license, key: license }La licence est référencée, jamais insérée en clair : laissez votre backend SecretsManagement matérialiser le Secret (ESO depuis Key Vault, ou un SealedSecret) comme n'importe quel autre identifiant. Dans un Stack, elle se déclare une seule fois via spec.gateway.licenseSecretRef et est transmise à la passerelle.
SSO et export de traces
Les deux fonctionnent, par des chemins différents de ceux de LiteLLM.
Le SSO (spec.sso) est appliqué via une CR WaegEnterpriseConfig, qui configure les modules Enterprise de Wäg par son API d'administration plutôt que par l'environnement de l'instance. L'opérateur lit le client id dans votre Secret et le pose comme champ simple (un client id est public par construction — il circule dans l'URL d'autorisation du navigateur), tandis que le client secret reste une référence de Secret, projetée sur le pod en WAEG_EE_OIDC_CLIENT_SECRET. C'est délibéré : la CR pourrait aussi écrire le secret dans le magasin de configuration scellé de la passerelle, ce qui le placerait dans ses révisions de configuration au lieu de le laisser dans Kubernetes.
L'URI de redirection est dérivée en <origine publique>/waeg/ui/v1/ee/sso/callback — la passerelle rejette toute valeur ne contenant pas ce chemin. Le SSO exige donc spec.ingress avec un hôte ; sans lui l'opérateur avertit plutôt que d'émettre un callback qui ne pourra jamais aboutir. Enregistrez exactement cette URL auprès de votre IdP.
Nécessite le build Enterprise et une licence ; la CR rapporte EnterpriseNotLinked ou LicenseRequired par module si l'un manque.
L'export de traces (observabilityRef / observability) est mappé sur WaegInstance.spec.observability.langfuse, les clés de projet étant fournies par référence de Secret. L'environnement est le seul chemin déclaratif de la passerelle — elle amorce son fichier de configuration au premier démarrage seulement, la console l'emportant ensuite — donc le sink est construit une fois au démarrage et un changement de clé fait rouler les pods. L'ordre de résolution est celui de LiteLLM : clés manuelles d'abord, puis câblage automatique sous licence depuis une Observability référencée.
Nécessite une passerelle récente
Les variables WAEG_LANGFUSE_* sont arrivées après la passerelle 1.0.0-rc.7. Une image plus ancienne rapporte LangfuseRequiresNewerGateway au lieu d'accepter un réglage qui ne prendrait jamais effet.
Approvisionnement SCIM
spec.waeg.scim active les points d'accès SCIM v2 de Wäg : votre IdP crée, met à jour et désactive directement les utilisateurs de la console, au lieu que quelqu'un le fasse à la main. SCIM est indépendant de spec.sso : il fonctionne sur une passerelle où aucune connexion interactive n'est configurée — la forme habituelle d'une intégration d'approvisionnement sans interface.
| Champ | Description |
|---|---|
enabled | Active les points d'accès. Défaut true ; désactivés, ils répondent 404 |
tokenSecretRef | Requis. Le jeton porteur que présente votre IdP. Clé scim-token par défaut |
defaultRole | Rôle console attribué à un utilisateur nouvellement approvisionné : viewer, operator ou admin |
defaultOrgID | Organisation Wäg dans laquelle atterrissent les utilisateurs approvisionnés. Par défaut, l'organisation de la passerelle elle-même lorsque spec.organization est défini |
orgSource | D'où provient l'organisation d'un utilisateur approvisionné : waeg (la passerelle décide), enterprise (la charge utile de l'IdP) ou none |
roleMap | Nom de groupe IdP → rôle console Wäg. Envoyé en remplacement intégral de la map |
orgMap | Valeur IdP → identifiant d'organisation Wäg. Remplacement intégral également |
roleMap et orgMap sont remplacées en totalité à chaque apply plutôt que fusionnées : la passerelle contient donc exactement ce qui est déclaré ici — supprimer une entrée supprime la correspondance.
Laisser defaultOrgID vide convient dès lors que la passerelle a une organization : les utilisateurs y atterrissent au lieu d'être sans organisation, ce qui compte car un utilisateur sans organisation manque toutes les autorisations de modèles enracinées dans l'organisation.
Les points d'accès se trouvent sous /waeg/admin/v1/ee/scim/v2 :
/waeg/admin/v1/ee/scim/v2/Users
/waeg/admin/v1/ee/scim/v2/Groups
/waeg/admin/v1/ee/scim/v2/ServiceProviderConfigPointez le connecteur SCIM de votre IdP sur cette URL de base, avec le jeton porteur issu de tokenSecretRef. Sans ce jeton, chaque appel répond 401.
Le jeton est une variable d'environnement, pas une valeur de configuration stockée
L'opérateur projette le jeton sur les pods de la passerelle sous le nom WAEG_EE_SCIM_TOKEN plutôt que de l'écrire dans le magasin de configuration scellé de la passerelle. Ce magasin ne peut jamais être relu : une copie écrite là deviendrait silencieusement obsolète dès la rotation du Secret. En variable d'environnement, faire tourner le Secret et laisser les pods redémarrer constitue toute la procédure de rotation.
Exige la licence Enterprise de la passerelle — sans elle le module répond 402 — et rien d'autre.
Pas la fonctionnalité sso-scim de la plateforme
La licence AI Core possède sa propre fonctionnalité sso-scim. Il s'agit d'une capacité de plateforme distincte, pas encore implémentée, sans rapport avec le module de passerelle décrit ici.
spec:
organization: { name: navique-ag }
instance:
licenseSecretRef: { name: waeg-enterprise-license, key: license }
waeg:
scim:
enabled: true
tokenSecretRef: { name: waeg-scim-token, key: scim-token }
defaultRole: viewer
orgSource: waeg
roleMap:
"AI Platform Admins": admin
"AI Platform Users": viewerAuthentification JWT du plan de données
Wäg peut valider un JWT porteur émis par votre IdP sur les requêtes du plan de données : les appelants présentent alors un jeton au lieu d'une clé — ou en plus d'elle. L'activation passe par le bloc partagé spec.instance.jwtAuth (le même champ que pour LiteLLM) ; le réglage fin se fait via spec.waeg.jwt, propre à Wäg.
spec.instance.jwtAuth | Module EE jwt de Wäg |
|---|---|
enabled | enabled |
publicKeyURL | jwksUrl — le point d'accès JWKS d'où proviennent les clés de signature |
issuer | issuer — l'iss attendu |
audience | audience — l'aud attendue |
userIDJWTField | subjectClaim — le claim qui identifie l'appelant |
spec.waeg.jwt | Description |
|---|---|
appClaim | Claim identifiant l'application appelante. Défaut azp |
appClaimFallbacks | Claims essayés dans l'ordre lorsque appClaim est absent. Remplacement intégral de la liste — une liste vide efface les valeurs par défaut de Wäg (appid, client_id) |
tenantClaim | Claim portant l'organisation / le tenant (sinon Wäg se rabat sur tenant_id, puis tid) |
requireRegisteredApplication | Rejette les jetons dont l'application n'est pas une WaegApplication enregistrée |
allowMasterKey | Laisse la clé maîtresse fonctionner sur le plan de données. Défaut true |
allowVirtualKeys | Laisse les clés virtuelles fonctionner sur le plan de données. Défaut true |
insecureSkipVerify | Accepte les signatures non vérifiées. Développement uniquement — Wäg le refuse en environnement de production et en présence de tout commutateur de durcissement |
Les commutateurs de durcissement peuvent empêcher la passerelle de démarrer
allowMasterKey: false et allowVirtualKeys: false restreignent le plan de données aux seuls JWT, et Wäg refuse de démarrer tant que le reste n'est pas cohérent : jwtAuth.enabled: true, une publicKeyURL (JWKS), une audience et insecureSkipVerify: false. Un commutateur de durcissement à moitié configuré met la passerelle hors service au lieu de se dégrader — définissez donc les deux moitiés dans le même apply.
allowVirtualKeys: false coupe aussi toutes les clés d'application, y compris celle de la ChatUI. Ne le changez qu'en connaissance de cause.
Les correspondances de claims propres à LiteLLM ne sont pas appliquées.userRolesJWTField, userRoleJWTField, userAllowedRoles, enforceRBAC, userIDUpsert, teamIDsJWTField et adminJWTScope n'ont pas d'équivalent chez Wäg : Wäg valide le jeton mais n'en extrait jamais de rôles ni d'équipes — l'autorisation vient d'OpenFGA et des applications enregistrées. Les définir est signalé sur la condition FeaturesSupported et dans status.unsupportedFeatures plutôt qu'abandonné en silence. Utilisez plutôt spec.waeg.jwt.requireRegisteredApplication et spec.waeg.modelAccess.
Exige la licence Enterprise de la passerelle (droit jwt_api) ; sans elle, le module répond 402.
spec:
instance:
licenseSecretRef: { name: waeg-enterprise-license, key: license }
jwtAuth:
enabled: true
publicKeyURL: https://login.microsoftonline.com/<tenant>/discovery/v2.0/keys
issuer: https://login.microsoftonline.com/<tenant>/v2.0
audience: <client-id>
userIDJWTField: sub # -> subjectClaim de Wäg
waeg:
jwt:
appClaim: azp
tenantClaim: tid
requireRegisteredApplication: true
# allowMasterKey: false # uniquement avec les quatre réglages ci-dessusHabillage de la console
Une console Wäg sous licence Enterprise porte par défaut le thème Navique. L'opérateur matérialise le thème intégré dans une ConfigMap qu'il possède — <nom-de-la-passerelle>-branding, clé theme-pack.json — et y pointe l'instance ; le waeg-operator l'envoie ensuite à l'API d'habillage.
| Situation | Résultat |
|---|---|
waeg.brandingConfigMapRef est défini, la licence inclut custom-branding | Votre thème l'emporte. L'opérateur ne crée rien de son côté ; CustomBranding=True |
waeg.brandingConfigMapRef est défini, la licence n'inclut pas custom-branding | Le thème Navique est appliqué et CustomBranding=False (CustomBrandingUnlicensed) en indique la raison. La référence est conservée : votre thème s'applique dès que la licence le permet |
waeg.defaultBranding: false | Retrait — la console conserve la livrée propre à Wäg |
Pas de instance.licenseSecretRef | Rien n'est créé du tout. Wäg refuse l'habillage sans licence : une ConfigMap ne ferait qu'annoncer un style que la console ne pourra jamais afficher |
| Sinon (le défaut) | Le thème Navique est appliqué |
La ConfigMap est réécrite à chaque réconciliation. Le thème est livré avec l'opérateur : une mise à jour de l'opérateur fait donc avancer la console au lieu de figer le thème installé en premier — ce qui rend aussi toute retouche manuelle inutile, la réconciliation suivante l'annule. Pour votre propre style (marque blanche, fonctionnalité sous licence custom-branding), publiez une ConfigMap et pointez brandingConfigMapRef dessus — ou utilisez l'habillage de la passerelle sur la page de la passerelle dans la console de gestion, qui valide le thème, en affiche un aperçu et le raccorde (sur le Stack de la passerelle lorsqu'un Stack la gère). Un Stack transmet spec.gateway.waeg.brandingConfigMapRef / defaultBranding.
spec:
instance:
licenseSecretRef: { name: waeg-enterprise-license, key: license }
waeg:
defaultBranding: true # le défaut ; false conserve la livrée de Wäg
# brandingConfigMapRef: { name: my-theme-pack, key: theme-pack.json }ChatUI se connecte en tant qu'application
Une ChatUI dont le gatewayRef pointe vers une passerelle Wäg est câblée automatiquement, mais via un objet différent de celui qu'utilise LiteLLM.
Wäg n'a pas de clé virtuelle autonome : une clé appartient à une application — un objet de tenancy enraciné dans l'organisation, avec son propre accès aux modèles, ses budgets et sa piste d'audit. L'opérateur enregistre donc la ChatUI comme WaegApplication et génère un WaegVirtualKey associé :
WaegApplication (identité de la chat UI) → WaegVirtualKey (son credential)La différence pratique tient à l'attribution : consommation, limites de débit et entrées d'audit sont imputées à « la chat UI » plutôt qu'à une clé anonyme, et l'application figure dans l'inventaire déployeur EU AI Act de Wäg avec une finalité déclarée.
Cela exige spec.organization sur le Gateway, car les applications sont enracinées dans l'organisation. Sans elle, la ChatUI est refusée avec ce motif plutôt que laissée en attente d'une clé qui ne pourra jamais être générée — définissez plutôt spec.gateway.{url, apiKeySecretRef} pour utiliser une clé que vous avez générée vous-même.
La rotation des credentials fonctionne comme pour LiteLLM : incrémenter l'index de rotation génère une nouvelle clé dans un Secret neuf plutôt que d'écraser sur place. L'application reste stable d'une rotation à l'autre — c'est l'identité, pas le credential.
Chiffrement en transit
Dès que la CA de la plateforme a émis un certificat, l'opérateur active WaegInstance.spec.tls : la passerelle sert alors du HTTPS dans le pod — le trafic est chiffré jusqu'au pod, pas seulement jusqu'à l'edge. La passerelle n'a qu'un seul listener, donc tout bascule ensemble et le waeg-operator suit : sondes, appProtocol du Service, status.endpoint, le ServiceMonitor et l'URL de scale KEDA.
Le bundle CA de la plateforme l'accompagne. Les appels d'API d'administration de l'opérateur lui-même empruntent cette connexion : sans la CA ils échoueraient à la vérification x509 contre la CA interne au cluster, emportant le branding et toutes les CR produit.
Nécessite la passerelle 1.0.0-rc.9 ou plus récente
Le TLS dans le pod ne fonctionne qu'à partir de la passerelle 1.0.0-rc.9. Les images antérieures paniquaient au démarrage dès que spec.tls était activé (rustls trouvait deux fournisseurs cryptographiques liés et aucun installé). Le waeg-operator refuse de l'activer sur une image plus ancienne et rapporte TLSUnsupportedGateway plutôt que de livrer une boucle de crash : épingler un instance.image plus ancien laisse donc la passerelle en clair au lieu de la casser. waeg-operator 1.3.0 utilise rc.9 par défaut, le chemin par défaut est donc sain.
Garde-fous
spec.guardrailRefs fonctionne sur une passerelle Wäg, mais la forme diffère. LiteLLM attend un objet par mode d'exécution ; Wäg en attend trois :
WaegGuardrailItem (un par Guardrail) → WaegGuardrailChain (les ordonne)
→ WaegGuardrailBinding (applique la chaîne)L'opérateur les émet tous les trois en scope: platform : aucune organisation n'est requise. Le binding utilise mode: floor, il s'applique donc toujours — un binding org ou équipe ajouté plus tard dans la console Wäg ne peut pas désactiver un garde-fou posé par la plateforme.
Trois réglages de l'item sont déterminants ; l'opérateur les choisit pour vous :
| Réglage | Pourquoi |
|---|---|
role | Les moteurs transform réécrivent le contenu. Configuré en policy, Wäg traite la réécriture comme une décision de masquage et la substitution en cours de flux n'a silencieusement pas lieu. Les moteurs du catalogue déclarent leur propre rôle |
onError | Repris de unreachableFallback (défaut fail_closed). Wäg est fail-open par défaut : un moteur qui répond délibérément 502 verrait sinon le prompt d'origine transmis au fournisseur |
mode | Wäg a une phase during_call absente de LiteLLM. Un moteur transform en a besoin, sinon les réponses en flux ne sont pas réécrites. Le pseudonymizer embarqué tourne sur les trois phases avec Wäg |
Les garde-fous externes exigent spec.waeg.path. Wäg envoie à une URL donnée en entier, là où LiteLLM ajoute /beta/litellm_basic_guardrail_api — l'opérateur ne peut donc pas deviner où un endpoint que vous gérez sert le protocole Wäg. Sans ce chemin, le garde-fou est ignoré et un Event l'indique, plutôt que l'opérateur devine un chemin et fasse échouer chaque analyse pendant que la passerelle paraît saine. Les moteurs du catalogue fournissent le leur (le pseudonymizer sert /v1/waeg/check).
spec.waeg sur un Guardrail porte aussi role, timeoutMs, forwardIdentity et forwardSessionId en option. Les deux derniers comptent pour les moteurs à état de session : les correspondances de noms du pseudonymizer ne restent cohérentes d'un tour à l'autre que si Wäg transmet une identité — sinon il retombe sur l'identifiant de requête, qui relie une seule requête mais pas une conversation.
Ce qui n'est pas appliqué
Quelques champs de cette CRD ne sont toujours pas appliqués à une passerelle Wäg. En définir un ne casse rien : il est listé dans status.unsupportedFeatures, la condition FeaturesSupported passe à False avec le motif UnsupportedByBackend, et un Event est émis.
| Champ | Pourquoi |
|---|---|
instance.saltKey | Wäg n'a pas de clé salt — utilisez waeg.dataEncryptionKeySecretRef |
instance.storePromptsInLogs | Aucun réglage d'exécution équivalent |
instance.healthCheck | Wäg expose des sondes /healthz et /readyz fixes, sans boucle de fond configurable |
instance.jwtAuth — les correspondances de claims uniquement | L'authentification JWT elle-même est appliquée (voir Authentification JWT du plan de données). Seuls userRolesJWTField, userRoleJWTField, userAllowedRoles, enforceRBAC, userIDUpsert, teamIDsJWTField et adminJWTScope sont refusés : Wäg autorise via OpenFGA et les applications enregistrées plutôt qu'en extrayant des rôles du jeton |
instance.rolePermissions | Wäg n'a pas de role_permissions façon LiteLLM — utilisez waeg.modelAccess pour des ACL de modèles enracinées dans l'organisation |
models[].rpm / tpm / timeout / maxTokens | L'objet modèle de Wäg ne porte ni limite de débit ni limite de tokens, et la passerelle n'a aucun timeout par modèle. Une limite par modèle est un WaegBudget avec scope: model (un pool à l'échelle de l'organisation pour l'alias), que cet opérateur n'émet pas |
organization.budgetDuration | Les politiques de budget Wäg n'ont pas de champ de période |
Hors spec du Gateway, les resources d'identité sont pleinement supportées sur Wäg : Identity, Organization et Team émettent WaegUser, WaegOrganization, WaegTeam ainsi que les politiques WaegBudget au niveau de l'organisation et de l'équipe. Pointez-les vers ce Gateway avec type: gateway et elles suivront son spec.type — voir Choix du backend.
Deux champs d'identité ne sont pas appliqués sur Wäg : les teamRefs et le budget d'une Identity. Wäg garde l'appartenance sur l'équipe (indiquez l'adresse dans les members de la Team), et une limite par utilisateur est un WaegBudget avec scope: user. Une Identity Wäg reçoit également un Secret de mot de passe de console généré et ne signale Ready qu'une fois le WaegUserSynced — voir Comptes Wäg.
Rejeté en amont
Plutôt que d'émettre une ressource qui se bloquerait, l'opérateur refuse ceci avec Ready=False et le motif InvalidTarget :
type: waegsansspec.waegou sansspec.waeg.clickhousewaeg.worker.enabledavectopology: AllInOne- plus d'un réplica d'API sans
waeg.redis waeg.modelAccesssansspec.organization
Exemple
apiVersion: core.navique.com/v1alpha1
kind: Gateway
metadata:
name: waeg-gateway
namespace: forge-gateway
spec:
type: waeg
secretsRef: { name: forge-secrets }
database:
mode: postgresCluster
postgresClusterRef: { name: forge-pg, namespace: forge-data }
databaseName: waeg # ne jamais partager un schéma avec une passerelle LiteLLM
instance:
replicas: 2
masterKey: { autoGenerate: true }
licenseSecretRef: { name: waeg-enterprise-license, key: license } # débloque chaque module EE
jwtAuth:
enabled: true
publicKeyURL: https://login.microsoftonline.com/<tenant>/discovery/v2.0/keys
issuer: https://login.microsoftonline.com/<tenant>/v2.0
audience: <client-id>
userIDJWTField: sub
waeg:
topology: Split
worker: { enabled: true, replicas: 2, jobWorkers: 4 }
storageMode: split # le défaut ; `single` se passe totalement de ClickHouse
clickhouse: { mode: ref, ref: { name: forge-ch, namespace: forge-data } }
redis: { mode: ref, ref: { name: forge-redis, namespace: forge-data } }
# openfga omis -> l'opérateur déploie un OpenFGA adossé à Postgres
# defaultBranding vaut true par défaut -> le thème Navique est appliqué
scim:
enabled: true
tokenSecretRef: { name: waeg-scim-token, key: scim-token }
defaultRole: viewer
roleMap: { "AI Platform Admins": admin }
jwt:
appClaim: azp
requireRegisteredApplication: true
modelAccess:
openByDefault: false
fallbackMode: deny
grants:
- models: [premium, economy]
organization: { name: navique-ag, maxBudget: 2000, rpmLimit: 1000 }
teams:
- { name: data-engineering }
models:
- name: gpt-5-4
modelName: premium
model: azure/gpt-5.4 # -> provider « azure », modelId « gpt-5.4 »
refSecretKey: OPENAI_API_KEY
credentials: { apiBase: "https://forge-foundry.openai.azure.com/openai/v1" }
fallbacks: [economy]Vérifier ce qui a été ignoré :
kubectl -n forge-gateway get gateway waeg-gateway \
-o jsonpath='{.status.conditions[?(@.type=="FeaturesSupported")].message}'Dans un Stack
Stack.spec.gateway.type: waeg la sélectionne pour tout un Stack. Le Stack exige alors datastores.clickhouse.enabled (l'admission le rejette sinon), câble automatiquement ses propres ClickHouse et Redis comme second plan, et nomme la base logique de la passerelle waeg pour qu'elle n'atterrisse jamais sur un schéma LiteLLM. Stack.spec.gateway.waeg transmet les réglages de topologie/worker/OpenFGA/autoscaling, ainsi que scim et jwt ; la licence Enterprise se déclare une seule fois via Stack.spec.gateway.licenseSecretRef, et Stack.spec.gateway.jwtAuth active l'authentification JWT du plan de données.
Status
status.url expose l'endpoint de la passerelle (public lorsque l'ingress est activé, sinon interne au cluster), plus la disponibilité de l'instance, les compteurs de modèles/équipes, et la disponibilité de la base de données. status.unsupportedFeatures liste tout champ configuré que le type de passerelle choisi ne peut pas honorer (toujours vide pour type: litellm) ; la condition FeaturesSupported le reflète.
Notes de licence
Les équipes, organisations et budgets font partie de la fonctionnalité multi-tenancy ; plus d'un Gateway requiert une limite gateways supérieure à la valeur par défaut Community de 1. Consultez Éditions et licences.