Aller au contenu principal

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/issue signe 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/verify contrôle dans l'ordre : structure et forme canonique, algorithme, clé connue et non révoquée, signature, émetteur, audience (parmi EAI_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.verify deny) dans sa propre transaction.
  • Clés : la clé privée vient d'OpenBao (EAI_CONTEXT_SIGNING_KEY_REF, production) ou de EAI_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 plus EAI_CONTEXT_MAX_TTL_SECONDS ; POST /v1/context/keys/{kid}/revoke (platform_admin) l'arrête immédiatement. GET /v1/context/jwks publie 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é. Avec EAI_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 et redelegation_allowed à agent:, app:, service: ou subagent:. 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 de max_depth, et seulement si le parent autorise la redélégation.
  • POST /v1/delegations/{id}/revoke révoque tous les descendants (chain @> [id]) et leurs contextes signés ; POST /v1/delegations/revoke-principal ré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) enregistre root_principal_id, on_behalf_of, delegation_id, workload_identity et mandate_id (V119). Sans delegation_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 (403 delegation_revoked, session_revoked, mandate_revoked, policy_denied, audités).
  • POST /v1/delegations/authorize rend la décision couche par couche (chaîne, session, utilisateur, agent, délégation, classification, audience, ressource, relation, politique) ; allow seulement 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 plus EAI_BREAK_GLASS_MAX_SECONDS). L'approbation exige deux approbateurs distincts (security_officer ou tenant_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}/context signe un contexte portant authorization.break_glass.
  • Approbations : le demandeur, l'utilisateur pour le compte duquel agit l'agent (on_behalf_of) et les excluded_approvers de la demande ne peuvent jamais approuver (403 separation_of_duties) ; un quorum ne compte que des approbateurs distincts (409 already_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_id vient de EAI_WORKLOAD_SPIFFE_ID et caller_spiffe_id de l'appelant SPIFFE authentifié ; test_workload_spiffe_identity_reaches_run_context_and_audit prouve 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_user l'exécute contre un point d'accès HTTP OPA local : OPA reçoit on_behalf_of et delegation_id, un changement de politique refuse l'action suivante, un OPA injoignable vaut refus.