Aller au contenu

Identité & délégation

Toute la chaîne d’attribution repose sur une distinction : un journal qui remonte à « marie.dupont » sans pouvoir établir que c’était bien elle ne prouve rien.

Prouvée (OIDC)Déclarée (--insecure-declared-identity)
principal_issuer dans le journall’émetteur réelcli://declared
Expirationissue du claim expaucune
Vérifiésignature, iss, aud, exp, nbfrien
Usageproductiondéveloppement uniquement

Le mode déclaré exige un drapeau dont le nom dit ce qu’il est, et sans aucune configuration d’identité la passerelle refuse de démarrer, sans repli silencieux vers un mode anonyme.

Quatre choix de conception à connaître :

  • Aucun appel réseau. Le JWKS vit à l’intérieur d’un bundle d’identité signé, distribué par le plan de contrôle aux côtés du bundle de politiques. La passerelle reste déployable en air-gap et n’ajoute aucune surface sortante.
  • Le bundle d’identité est signé lui aussi. Qui peut écrire ce fichier peut se forger une identité : la même menace qu’un fichier de règles non signé, méritant la même réponse.
  • L’algorithme vient du JWKS, jamais de l’en-tête du jeton. La faille JWT classique, qui consiste à déclarer HS256 et à signer en HMAC avec la clé publique de l’IdP, est couverte par un test dédié.
  • L’expiration est réévaluée à chaque acte, et pas seulement à l’ouverture de session. Si le jeton a été renouvelé sur disque, la passerelle enregistre une nouvelle délégation ; sans quoi un acte sous jeton renouvelé paraîtrait autorisé par une délégation déjà expirée.

Un jeton qui dit « marie.dupont » ne dit pas qui agissait en son nom. L’échange de jetons RFC 8693, si. Obsign l’enregistre :

Mode du jetonChaîne d’acteursprincipal_kind
Jeton utilisateuru:marie.duponthuman
Échange de jetons (claim act)support-copilot → u:marie.dupontdelegated_human
client_credentialsbatch-agentmachine

Le troisième cas est celui que personne ne couvre : un agent batch, sans aucun humain derrière lui, qui supprime en production. Il est reconnu par les marqueurs que les IdP visés émettent réellement (sub == client_id, l’idtyp: "app" d’Entra ID, le préfixe de nom d’utilisateur service-account- de Keycloak), et la politique peut alors l’exprimer :

@id("destructive_requires_human")
forbid (principal, action == Action::"tool_call", resource)
when { resource.destructive && !context.has_human_delegation };

« Aucun agent ne détruit quoi que ce soit sans un humain identifiable au bout de la chaîne. » Cedar reçoit aussi context.actor_chain, context.delegation_depth (pour borner les topologies multi-agents) et context.principal_kind.

Rien n’est spécifique à un fournisseur, et c’est délibéré. Keycloak, Entra ID, Okta et Ping sont des fournisseurs OIDC : ils fonctionnent tels quels. Ce qui varie, c’est chaque fournisseur range l’information ; le bundle d’identité décrit donc les chemins de claims, avec * pour les segments dépendant du client :

"claims": {
"subject": "/sub",
"scopes": ["/scope", "/scp"],
"groups": ["/groups", "/roles",
"/realm_access/roles",
"/resource_access/*/roles"],
"client_id": ["/client_id", "/azp"]
}

Ces valeurs par défaut couvrent Keycloak et Entra ID. Le cas Keycloak mérite d’être signalé : les rôles ne sont jamais à plat. Un mapping naïf renvoie des groupes vides, et aucune règle principal in Group::"dba" ne correspond jamais, sans erreur visible.

Les marqueurs machine suivent le même langage de chemins et vivent à l’intérieur du bundle signé (format obsign-identity/2) : élargir ce qui compte comme humain doit être signé comme tout autre changement d’autorisation.

Deux points côté realm Keycloak, à faire une fois : un mapper d’audience (les jetons d’accès par défaut portent aud: "account", que la passerelle refuse à juste titre), et le token exchange activé sur le client de l’agent pour le claim act (officiellement supporté depuis Keycloak 26.2). Pour SAML, placez Keycloak devant en pont SAML↔OIDC, et la passerelle n’a jamais à savoir que SAML existe.

Les fournisseurs font tourner leurs clés de signature couramment. La passerelle ne fait aucun appel réseau : la rotation consiste donc à recharger depuis le disque le bundle d’identité signé, là où le plan de contrôle a publié la nouvelle version.

  • Déclenchée par un kid inconnu — le signal exact d’une rotation, si bien que le rechargement ne coûte rien en fonctionnement nominal. Une seule nouvelle tentative.
  • La signature est revalidée à chaque rechargement — sans quoi écrire le fichier suffirait à injecter un JWKS.
  • Un bundle invalide ne met jamais la passerelle à terre — déploiement raté, fichier tronqué, mauvaise signature : la configuration précédente reste en vigueur.
  • Fréquence bornée — une tentative par seconde, détectée par empreinte du contenu (la granularité de mtime peut manquer des écritures rapprochées).
  • Le rechargement est lui-même enregistré — un enregistrement config_reload entre dans la chaîne d’audit, tentatives rejetées comprises : déposer un JWKS malveillant sur le disque est une attaque, et la tentative est précisément ce qu’une investigation veut voir.