Identité héritée de bout en bout : Context Authority, délégations, mandats, accès d'urgence
English version: en/identity-context-delegation.md
Sections 7, 8.1 et 8.2 du kit. Exigences IAM_-007, IAM_-008, IAM_-009, IAM_-010, IAM_-011, IAM_-013, IAM_-014, IAM_-017, AGEN-014, RELE-005, AUDI-001, AUDI-002, AUDI-003, AUDI-005. API : control-plane-v1.md, section « Context Authority, delegations, mandates and break glass ». Réglages : configuration.md.
Règle absolue
Chaque agent, sous-agent et application gouvernée porte de façon vérifiable l'utilisateur d'origine (root_principal_id, on_behalf_of), sa propre identité technique (workload_identity) et la chaîne de délégation (delegation_chain). Chaque action applique l'intersection des droits de l'utilisateur, de l'agent, de la délégation, de la ressource (OpenFGA ou les tuples intégrés) et de la politique (OPA ou le moteur intégré). Aucun chemin n'accorde davantage.
Context Authority (eai.domains.context)
POST /v1/context/issuesigne un Execution Context v1 (contracts/v1/execution-context.schema.json, tous les groupes de champs du kit 8.1). Format : JWS compact, en-tête{alg, kid, typ: eai-ctx+jws}, en-tête et charge utile en JSON canonique RFC 8785 (eai.domains.context.jcs), algorithme ES256 (ECDSA P-256) ou EdDSA (Ed25519),kid= empreinte RFC 7638. Revendications :iss,aud(liste),iat,exp,jti,sub,tid,ctx.- Le bloc d'identité porte
token_reference(méthode et identifiant de l'identifiant de connexion, ou empreinte du sujet), jamais le jeton brut : le contrat refuse une valeur qui ressemble à un JWT. POST /v1/context/verifycontrôle dans l'ordre : structure et forme canonique, algorithme, clé connue et non révoquée, signature, émetteur, audience (parmiEAI_CONTEXT_AUDIENCES), durée de vie (EAI_CONTEXT_LEEWAY_SECONDS), tenant, enregistrement d'émission (eai.execution_contexts: jti inconnu ou révoqué refusé), empreinte du document, chaîne de délégation. Chaque refus est audité (context.verifydeny) dans sa propre transaction.- Clés : la clé privée vient d'OpenBao (
EAI_CONTEXT_SIGNING_KEY_REF, production) ou deEAI_CONTEXT_SIGNING_KEY(dev et tests, refusée en prod). Seule la JWK publique est stockée (eai.context_signing_keys). Changer de clé vaut rotation : l'ancienne est retirée et vérifie encore jusqu'à sa dernière signature plusEAI_CONTEXT_MAX_TTL_SECONDS;POST /v1/context/keys/{kid}/revoke(platform_admin) l'arrête immédiatement.GET /v1/context/jwkspublie les clés qui peuvent vérifier. - Refus par défaut (fail closed) : sans clé, clé illisible, OpenBao injoignable, clé révoquée ou registre des clés indisponible, réponse 503
context_authority_unavailable, rien n'est signé ni accepté. AvecEAI_CONTEXT_REQUIRED=1(obligatoire en prod), un run sans contexte signé est refusé.
Délégations (eai.domains.delegation)
POST /v1/delegations: un utilisateur ou une application délègue des scopes, des audiences, des restrictions de ressources, un plafond de classification,max_depth, une expiration etredelegation_allowedàagent:,app:,service:ousubagent:. Les scopes doivent être détenus par l'appelant et la classification ne dépasse pas son habilitation. Un agent ne peut pas créer de délégation racine.POST /v1/delegations/{id}/restrict: un enfant est toujours couvert par son parent (scopes, audience, ressources avec*comme seul joker, test d'inclusion sûr), classification non supérieure, expiration non postérieure, profondeur = parent + 1 dans la limite demax_depth, et seulement si le parent autorise la redélégation.POST /v1/delegations/{id}/revokerévoque tous les descendants (chain @> [id]) et leurs contextes signés ;POST /v1/delegations/revoke-principalrévoque tout ce qui a pour racine un utilisateur.- Chaque table de tenant (
delegations,execution_contexts,agent_mandates,break_glass_grants) a la RLS activée et forcée (V115 à V118).
Runtime (eai.domains.agents, eai.domains.delegation.runtime)
- Un run (
POST /v1/agents/{id}/runs) enregistreroot_principal_id,on_behalf_of,delegation_id,workload_identityetmandate_id(V119). Sansdelegation_id, une délégation implicite est créée : scopes de l'appelant intersectés avec les scopes déclarés de l'agent, classification la plus basse. - Un sous-agent (
POST /v1/runs/{id}/subagents) reçoit une délégation restreinte depuis celle du parent, intersectée avec les scopes du sous-agent : jamais plus large. - Chaque étape sensible (
tool_call,model_call,subagent,act,approval) revalide la chaîne de délégation, la session, le mandat et une décision de politique avant d'être enregistrée : une révocation ou un changement de politique prend effet à l'action suivante (403delegation_revoked,session_revoked,mandate_revoked,policy_denied, audités). POST /v1/delegations/authorizerend la décision couche par couche (chaîne, session, utilisateur, agent, délégation, classification, audience, ressource, relation, politique) ;allowseulement si toutes les couches autorisent ; une erreur du moteur de relations vaut refus.
Agents planifiés : mandats (IAM_-011)
Un run planifié agit sous un mandat approuvé lié à une identité de service active (POST /v1/mandates, approuvé par une autre personne via /v1/approvals/{id}/decide). Sa délégation a pour racine service:<nom> pour le compte de mandate:<id> : aucune identité humaine n'est inventée. Sans mandat, le planificateur saute le run (skipped: mandate_required, audité schedule.run deny). Révoquer le mandat révoque ses délégations.
Accès d'urgence (IAM_-013) et quatre yeux (IAM_-014)
POST /v1/break-glass: rôles et scopes, justification, durée (au plusEAI_BREAK_GLASS_MAX_SECONDS). L'approbation exige deux approbateurs distincts (security_officeroutenant_admin), jamais le demandeur ni le bénéficiaire. Une fois approuvé, l'accès est actif, expire seul (break_glass.expire), peut être révoqué, et chaque délégation créée sous lui y est liée et ne lui survit jamais.POST /v1/break-glass/{id}/contextsigne un contexte portantauthorization.break_glass.- Approbations : le demandeur, l'utilisateur pour le compte duquel agit l'agent (
on_behalf_of) et lesexcluded_approversde la demande ne peuvent jamais approuver (403separation_of_duties) ; un quorum ne compte que des approbateurs distincts (409already_decided).
Audit (AUDI-001 à AUDI-005)
La Context Authority et le runtime lient un contexte d'identité (variable de contexte de eai.audit, eai.domains.context.state). Chaque événement d'audit du même tenant écrit pendant ce lien est un Audit Event 1.1 avec on_behalf_of, root_principal_id, delegation (id, parent, chaîne, profondeur, mandat, accès d'urgence), agent (run, run parent, profondeur, indicateur sous-agent), workload, session, causal (requête, exécution, action, trace, span, événement parent) et authorization.decision_ids, approvals.approval_ids. Ces champs font partie de la chaîne de hachage (audit_events.context, V119) ; les événements écrits hors contexte vérifié restent des Audit Event 1.0.
Tests
services/control-plane/tests/test_identity_context_delegation.py (signature, émetteur, audience, expiration, tenant, révocation, rotation, refus par défaut, délégations plus étroites, algèbre des motifs, droits du sous-agent, révocation pendant un run, session fermée, utilisateur révoqué, changement de politique, mandat, accès d'urgence, quatre yeux, champs d'audit, RLS), contracts/tests/test_contracts.py (contrats), tests/test_agents_execution.py (planificateur avec mandat).
Limites
- SPIFFE :
workload_identity.spiffe_idvient deEAI_WORKLOAD_SPIFFE_IDetcaller_spiffe_idde l'appelant SPIFFE authentifié ;test_workload_spiffe_identity_reaches_run_context_and_auditprouve qu'il atteint le run, le contexte signé et l'événement d'audit. Le SVID lui-même est délivré par SPIRE, hors du Control Plane. - Référence OpenBao : la lecture KV est testée contre un OpenBao simulé (
test_signing_key_is_read_from_the_openbao_reference_and_never_exposed: clé lue, jamais stockée ni renvoyée, secret absent et référence mal formée refusés) ; aucun serveur OpenBao réel n'est contacté par les tests. - OPA : la couche politique utilise
eai.policy.evaluate(OPA s'il est configuré, moteur intégré sinon).test_opa_decision_is_taken_before_each_action_with_the_original_userl'exécute contre un point d'accès HTTP OPA local : OPA reçoiton_behalf_ofetdelegation_id, un changement de politique refuse l'action suivante, un OPA injoignable vaut refus.