Aller au contenu principal

Authentification et autorisation

Cette page explique comment la Navin Platform reconnaît une identité (utilisateur, clé d'API, service), comment elle en déduit le tenant, puis comment elle décide d'une action : rôles, portées, habilitation, relations OpenFGA, politiques OPA et délégations. Source : services/control-plane/src/eai/auth.py, services/control-plane/src/eai/policy.py, services/control-plane/src/eai/integrations/openfga.py, services/control-plane/src/eai/domains/identity.

English version: en/authentication-authorization.md

Identifiants acceptés​

eai.auth.authenticate lit les identifiants dans cet ordre ; le premier présent décide, aucun autre n'est essayé ensuite.

OrdreIdentifiantMéthode (auth_method)Principal obtenu
1Authorization: Bearer eai_<préfixe>_<secret>api_keyapikey:<id>, type service
2Authorization: Bearer eais_<préfixe>_<secret> (session du portail ouverte par POST /v1/auth/login)passwordlocal:<identifiant>, type user
3Authorization: Bearer <JWT OIDC>oidcsub du jeton, type user
4X-API-Key: eai_<préfixe>_<secret>api_keycomme 1
5X-SPIFFE-ID (seulement avec EAI_AUTH_SPIFFE=true et un pair de EAI_TRUSTED_PROXIES)spiffeservice:<nom>, type service
6X-Dev-Tenant, X-Dev-User, X-Dev-Roles (seulement avec EAI_AUTH_DEV=true hors prod)devvaleur de X-Dev-User

Sans identifiant valide, la réponse est 401 unauthenticated avec WWW-Authenticate: Bearer. Chaque 401 compte dans le budget d'échecs de l'adresse IP (EAI_RATE_LIMIT_AUTH_FAILURES_PER_MINUTE) ; au-delà, toute tentative de cette IP reçoit 429 jusqu'à la fin de la fenêtre. Un jeton de plus de 8192 caractères est refusé avant tout décodage. GET /v1/identity/whoami renvoie l'identité résolue (identifiant, type, tenant, rôles, portées, habilitation, méthode).

Identité humaine : Keycloak et OIDC​

  • Le jeton est vérifié contre les clés JWKS de EAI_OIDC_JWKS_URL ou découvertes depuis EAI_OIDC_ISSUER (cache de 300 s, une seule relecture par fenêtre de 30 s pour un kid inconnu). Revendications obligatoires : exp, iat, iss, sub, et aud quand EAI_OIDC_AUDIENCE est défini. Seuls les algorithmes de EAI_JWT_ALGORITHMS passent, jamais HS* ni none.
  • Rôles : les rôles du jeton (rôles de royaume et du client Keycloak) qui figurent dans eai.auth.ROLES ; un rôle inconnu est ignoré. Les rôles attribués dans la plateforme (eai.memberships) s'y ajoutent, sauf platform_admin. Sans aucun rôle, l'utilisateur est viewer.
  • Groupes d'administrateurs de l'annuaire : un groupe de EAI_OIDC_PLATFORM_ADMIN_GROUPS (défaut navin-platform-admins) donne platform_admin ; un groupe <EAI_OIDC_TENANT_ADMIN_GROUP_PREFIX><slug> (défaut navin-admins-) donne tenant_admin sur ce tenant. La comparaison ignore la casse et ne retient que les noms exacts.
  • Habilitation : la revendication clearance si elle vaut public, internal, confidential, restricted ou secret, sinon internal. Portées : la revendication scope, ou * si elle est absente ou contient openid.
  • MFA, SAML et déconnexion sont portés par Keycloak : voir identity-policy-plane.md.

Comptes locaux du portail​

POST /v1/auth/login vérifie le mot de passe (scrypt) et ouvre une session eais_ affichée une seule fois ; seule son empreinte sha256 est stockée (eai.local_sessions). Durée : EAI_LOCAL_SESSION_TTL_SECONDS. Verrouillage après EAI_LOCAL_AUTH_MAX_FAILURES mots de passe faux pour EAI_LOCAL_AUTH_LOCKOUT_SECONDS. Tant que must_change_password est vrai, la session n'atteint que POST /v1/auth/password, POST /v1/auth/logout, GET /v1/me et GET /v1/identity/whoami (403 password_change_required ailleurs). Changer de mot de passe révoque les autres sessions du compte. Chaque résultat est audité.

Clés d'API​

OpérationRouteRôles
ListerGET /v1/api-keystenant_admin, security_officer, developer (un developer ne voit que ses clés)
CréerPOST /v1/api-keystenant_admin, developer
Faire tournerPOST /v1/api-keys/{key_id}/rotatetenant_admin, security_officer, developer (ses clés seulement)
RévoquerDELETE /v1/api-keys/{key_id}tenant_admin, security_officer, developer (ses clés seulement)
  • Format eai_<12 hexadécimaux>_<secret>, vérifié avant toute lecture de la base ; seule l'empreinte sha256 est stockée (eai.api_keys), comparée en temps constant. La valeur complète n'est affichée qu'à la création et à la rotation.
  • Une clé porte ses rôles, ses portées, son habilitation, un projet, un environnement et une expiration. On ne délègue jamais plus que ce qu'on détient : seul un platform_admin donne platform_admin, un appelant sans tenant_admin ne donne que ses propres rôles, et chaque portée doit être détenue par l'appelant (403 scope_not_held).
  • La rotation émet une nouvelle clé (mêmes nom, portées et rôles) et révoque l'ancienne, ou la fait expirer après grace_seconds. Codes de refus : 401 invalid_api_key, api_key_revoked, api_key_expired.

Identités de service (SPIFFE)​

  • Une identité de service (eai.service_identities) lie un identifiant SPIFFE à un tenant, des rôles, des portées et une habilitation. Création et suppression : POST /v1/service-identities et DELETE /v1/service-identities/{identity_id} (tenant_admin, security_officer), avec les mêmes gardes de rôles et de portées que les clés d'API.
  • X-SPIFFE-ID n'est cru que d'un pair de confiance (side-car du maillage) ; un identifiant inconnu ou inactif reçoit 401 unknown_spiffe_id. Le SVID est délivré par SPIRE et vérifié en TLS 1.3 par eai.domains.identity.spiffe (liste blanche, refus par défaut).
  • POST /v1/service-identities/{identity_id}/assume donne à un appelant les droits d'une identité de service, jamais plus que les siens : rôles détenus des deux côtés (jamais platform_admin), portées accordées par l'identité et détenues par l'appelant (sinon 403 service_identity_scope_exceeds), habilitation la plus basse des deux.

Résolution du tenant​

Le tenant vient toujours de l'identifiant, jamais du corps de la requête :

  • clé d'API, session locale et identité de service : le tenant enregistré avec l'identifiant ;
  • jeton OIDC : la revendication tenant_id (identifiant) ou tenant (slug), sinon le seul tenant administré par les groupes de l'annuaire. Plusieurs groupes d'administration sans revendication donnent 403 tenant_required ; un platform_admin peut agir sans tenant.

Un tenant inconnu répond 401 unknown_tenant, un tenant non actif 403 tenant_suspended. Après l'authentification, le registre des kill switches est relu : identifiant révoqué (401 credential_revoked), identité suspendue (403 identity_suspended), tenant suspendu (403 tenant_suspended). Dans la base, eai.db.tx positionne le tenant de la transaction et la RLS forcée filtre chaque ligne.

Rôles​

Quinze rôles (eai.auth.ROLES, contrainte de eai.memberships dans sql/postgres/V161). platform_admin passe tout contrôle require_role (Principal.has_role), sauf les contrôles explicites qui l'excluent (déchiffrement des charges d'audit). Les capacités ci-dessous sont relevées dans les contrôles de rôles du code et dans portal/src/lib/kit.ts (ROLES).

RôleCe qu'il peut faire (exemples relevés dans le code)
platform_adminCréer les tenants, routes du Platform Manager et coffre-fort IT du Control Plane (seul rôle admis), licences, révocation des clés de la Context Authority ; jamais le déchiffrement des charges d'audit
tenant_adminAdministrer le tenant : membres, correspondances de groupes et profils de quota, clés d'API, identités de service, politiques, réglages de sécurité, rétention, DLP, clés de chiffrement, risques, politique de sécurité des fichiers ; approbateur par défaut, responsable SOC, lecteur de preuves
security_officerPosture et réglages de sécurité, politiques, relations et identités de service, révocation des clés, DLP, clés de chiffrement, rétention, revue de quarantaine, sécurité des fichiers, déchiffrement des charges d'audit, sceaux et conservations légales, analyste et responsable SOC, approbation d'accès d'urgence
auditorLire l'audit, les décisions, l'état des moteurs, les correspondances de groupes, les analyses de fichiers ; déchiffrer les charges d'audit avec une raison ; sceaux et conservations légales ; ne modifie rien d'autre
developerCréer ses clés d'API et ses secrets, simuler les politiques et lire leur historique sans publier, évaluations de risque et exceptions, enregistrer un consentement temps réel ; approbateur par défaut
finopsLire et administrer budgets, quotas et coûts ; demander une approbation
agentIdentité technique d'un agent : demander une approbation, enregistrer un consentement, URL présignées de stockage, lecture des modèles
viewerLecture des modèles, de la FinOps et de l'observabilité ; rôle par défaut sans autre rôle
soc_analystAnalyste SOC : alertes, détections, incidents, réactions permises par le garde de proportionnalité
ai_governance_officerÉvaluations de risque, modèles et agents, qualité et écrans de sécurité en lecture
data_stewardPolitiques de rétention, effacement, conservation légale, revue de quarantaine RAG
application_ownerAgents et applications dont il est propriétaire (relation owner)
ai_platform_engineerAdministration et désactivation des fournisseurs, modèles et routes LLM
business_ownerApprobateur par défaut des demandes (cas d'usage, actions sensibles)
support_engineerObservabilité et lecture des modèles, sans écriture

La matrice de séparation est prouvée par services/control-plane/tests/test_iam_role_matrix.py ; le détail des rôles métiers est dans identity-policy-plane.md.

Portées et habilitation​

  • Portées : require_scope exige une portée du jeton ou de la clé ; les motifs (admin:*, *) sont admis (403 insufficient_scope).
  • Habilitation : cinq niveaux ordonnés public < internal < confidential < restricted < secret. Le moteur de politique compare la classification des données à l'habilitation du sujet (clearance_required, classification_max).

Relations OpenFGA​

  • Modèle : deploy/policies/openfga/model.fga. Types : user, group, tenant, project, agent, model, tool, knowledge_base, dataset, prompt, connector, secret, collection, document, service_identity. Relations : owner, admin (tenant), editor, member, viewer, can_use, can_invoke, can_read.
  • require_relation vérifie la ressource, puis son projet, puis le tenant. Niveau du tenant : tenant_admin = admin, developer et security_officer = editor, les autres = member. Un refus est audité (authz.deny) et répond 403 relation_required.
  • Sans EAI_OPENFGA_URL, les tuples sont dans eai.authz_tuples (tuples directs, user:*, un niveau de groupes, owner implique editor implique viewer). Avec OpenFGA, objets et sujets non utilisateurs sont préfixés par l'identifiant du tenant. Gestion : POST /v1/authz/tuples, DELETE /v1/authz/tuples, POST /v1/authz/check (tuples contextuels jamais stockés).
  • Panne d'OpenFGA : 502 authz_backend_error, refus audité avec la raison backend_unavailable.

Politiques OPA et moteur intégré​

eai.policy.evaluate décide avant chaque action sensible : le registre des kill switches d'abord, puis les politiques du tenant (une règle deny qui s'applique l'emporte ; des règles allow qui visent l'action sans s'appliquer refusent), puis les gardes de base (habilitation, plafond de classification, régions), puis le défaut EAI_POLICY_DEFAULT ou le réglage policy_default du tenant. Chaque décision est enregistrée (eai.policy_decisions, GET /v1/policies/decisions) et auditée.

Paquet OPAFichierDécide
eai.agent_autonomydeploy/policies/opa/agent_autonomy.regoRuns d'agents et actions autonomes
eai.data_accessdeploy/policies/opa/data_access.regoAccès RAG et données (ACL, habilitation)
eai.gateway_accessdeploy/policies/opa/gateway_access.regoPoints d'entrée des passerelles hors modèles et outils
eai.model_invocationdeploy/policies/opa/model_invocation.regoChaque appel de modèle
eai.tool_invocationdeploy/policies/opa/tool_invocation.regoChaque appel d'outil
eai.soc_responsedeploy/policies/opa/soc_response.regoOrdres de confinement SOC
eai.libdeploy/policies/opa/lib.regoFonctions communes, surcharges par tenant

Une obligation (dlp, approval) est remontée par POST /v1/policies/evaluate. Publication, simulation et révisions : identity-policy-plane.md.

Délégations, mandats et accès d'urgence​

Chaque action d'agent applique l'intersection des droits de l'utilisateur d'origine, de l'agent, de la délégation, de la ressource et de la politique : voir identity-context-delegation.md. Les routes d'administration des délégations sont réservées à tenant_admin et security_officer dans le portail (delegationAdmins).

Correspondances de groupes​

eai.domains.identity.group_mappings applique, à chaque authentification OIDC (EAI_OIDC_GROUP_SYNC=true), les correspondances du tenant aux groupes du jeton (revendications groups et entra_groups) : appartenance à un projet, rôle et profil de quota. Ce qu'une correspondance a accordé est enregistré (eai.group_mapping_grants) et retiré quand le groupe quitte le jeton ; les appartenances données à la main ne sont jamais touchées.

OpérationRouteRôles
Lister, lireGET /v1/identity/group-mappings, GET /v1/identity/group-mappings/{mapping_id}tout membre du tenant
Créer, modifier, supprimerPOST /v1/identity/group-mappings, PATCH /v1/identity/group-mappings/{mapping_id}, DELETE /v1/identity/group-mappings/{mapping_id}tenant_admin
Profils de quotaGET /v1/identity/quota-profiles, POST /v1/identity/quota-profileslecture : membre ; écriture : tenant_admin

Rôles qu'une correspondance peut donner par l'API : tenant_admin, security_officer, finops, developer, auditor, agent, viewer ; jamais platform_admin.

Tests​

services/control-plane/tests/test_security_auth.py (JWT, clés d'API), services/control-plane/tests/test_iam_role_matrix.py (séparation des rôles), services/control-plane/tests/test_iam_openfga_real.py, services/control-plane/tests/test_iam_fail_closed.py, services/control-plane/tests/test_iam_keycloak_real.py.