Aller au contenu

Échange de jetons OAuth : distinguer la délégation de l'usurpation

Échange de jetons OAuth : distinguer la délégation de l’usurpation

Section intitulée « Échange de jetons OAuth : distinguer la délégation de l’usurpation »

Un service reçoit un appel authentifié d’un utilisateur, et doit lui-même appeler un troisième service pour répondre. Que présente-t-il ? Le jeton de l’utilisateur, qu’il n’a pas le droit de réutiliser ailleurs ? Son propre jeton de service, qui perd toute trace de l’utilisateur ? La RFC 8693, publiée en janvier 2020, normalise une troisième réponse : l’échange de jetons. Elle oblige surtout à poser une question qu’on élude d’ordinaire, et qui décide de la qualité de tous les journaux d’audit en aval : le service agit-il pour le compte de l’utilisateur, ou à sa place ?

→ Lire l’explication complète de la marmotte

Le service d’émission de jetons, et le problème qu’il résout

Section intitulée « Le service d’émission de jetons, et le problème qu’il résout »

La RFC définit un service d’émission de jetons, en anglais security token service, comme « un service capable de valider les jetons de sécurité qui lui sont fournis et d’émettre en réponse de nouveaux jetons de sécurité, ce qui permet aux clients d’obtenir les accréditations d’accès appropriées pour des ressources dans des environnements hétérogènes ou à travers des domaines de sécurité ». L’idée n’est pas neuve : les clients de services web utilisaient WS-Trust, en XML et SOAP, pour la même fonction. La RFC 8693 en transpose le principe au monde OAuth, en HTTP et JSON, parce que, dit-elle, « la tendance du développement web moderne a été vers les motifs REST et JSON ».

Le cas d’usage canonique mérite d’être cité tel quel, parce qu’il décrit à peu près toute architecture en services : « permettre à un serveur de ressources A d’appeler un service de dernier plan C pour le compte de l’utilisateur demandeur B ». Et la RFC souligne un point d’organisation qui surprend souvent : l’entité qui demande l’échange « est considérée comme le client dans le contexte de l’interaction d’échange de jetons », mais cela ne restreint pas l’usage de ce profil aux clients OAuth traditionnels. Un serveur de ressources peut parfaitement endosser le rôle de client pour troquer un jeton reçu contre « un nouveau jeton approprié à inclure dans un appel à un service de dernier plan », plus étroitement cadré, ou d’un type entièrement différent.

Un avertissement, posé dès la description de la requête, décide de la sécurité de tout le dispositif : « omettre l’authentification du client permet qu’un jeton compromis soit exploité, via un service d’émission, en d’autres jetons par quiconque possède le jeton compromis ». Un point d’échange de jetons ouvert aux clients non authentifiés est donc un amplificateur de compromission : un jeton volé y entre, un jeton plus utile en sort. L’authentification du client « permet des contrôles d’autorisation additionnels par le service d’émission quant aux entités autorisées à usurper ou à recevoir des délégations d’autres entités ». C’est la première ligne à vérifier dans une configuration.

Usurpation et délégation : la distinction qui décide de tout

Section intitulée « Usurpation et délégation : la distinction qui décide de tout »

La RFC consacre une section entière à cette différence, et ses définitions sont assez nettes pour être reprises mot pour mot.

L’usurpation. « Quand le principal A usurpe l’identité du principal B, A reçoit tous les droits que B possède dans un contexte de droits défini, et est indiscernable de B dans ce contexte. » Elle précise la conséquence : « pour toute entité recevant un tel jeton, elle traite en réalité avec B », et « il est vrai que certains membres du système d’identité peuvent avoir conscience que l’usurpation a lieu, mais ce n’est pas une exigence ». Autrement dit, du point de vue du service appelé et de son journal, l’appel vient de l’utilisateur. La capacité de A à usurper B peut être limitée en portée, en durée, voire à un usage unique, par le contenu du jeton ou par un mécanisme hors bande.

La délégation. « Avec la sémantique de délégation, le principal A conserve sa propre identité, distincte de B, et il est explicitement compris que, bien que B puisse avoir délégué certains de ses droits à A, toute action entreprise est entreprise par A représentant B. En un sens, A est un agent pour B. » Le jeton correspondant est dit composite, « parce qu’il est composé d’informations sur plusieurs sujets ».

La différence est invisible le jour de l’appel et décisive après. Avec l’usurpation, le journal du service C enregistre « B a fait ceci ». Avec la délégation, il enregistre « A a fait ceci pour B ». Quand il faut, six mois plus tard, déterminer si une action a été déclenchée par un humain, par un service d’intégration ou par un agent automatique, le premier journal ne répond pas. C’est la même perte d’information que celle produite par un compte de service partagé, exposée dans pourquoi supprimer les comptes de service partagés, et elle devient critique dès que des agents logiciels relaient des identités humaines, comme le détaillent la gouvernance des identités des agents logiciels autonomes et la sécurisation des actions irréversibles des agents LLM.

Le choix se fait par les paramètres de la requête. Le subject_token, obligatoire, « représente l’identité de la partie pour le compte de laquelle le jeton est demandé » ; l’actor_token, facultatif, « représente l’identité de la partie à qui les droits d’accès du jeton délivré sont délégués ». Présenter les deux demande une délégation ; n’en présenter qu’un laisse le serveur d’autorisation libre, et la pratique courante est alors l’usurpation. La RFC est claire sur le fait que la décision reste au serveur : « quand et si un jeton composite est délivré relève de la discrétion du serveur d’autorisation, de la politique applicable et de la configuration ».

La requête, et les paramètres qui cadrent le jeton obtenu

Section intitulée « La requête, et les paramètres qui cadrent le jeton obtenu »

La requête est un POST ordinaire vers le point de jeton, avec un type d’octroi d’extension valant urn:ietf:params:oauth:grant-type:token-exchange. Outre les deux jetons déjà décrits et leurs types, quatre paramètres façonnent le jeton obtenu, et ils valent qu’on s’y attarde parce qu’ils sont le seul mécanisme de réduction de privilège disponible.

resource est une adresse absolue « qui indique le service ou la ressource cible où le client a l’intention d’utiliser le jeton de sécurité demandé ». Son intérêt n’est pas cosmétique : il « permet au serveur d’autorisation d’appliquer la politique appropriée pour la cible, par exemple en déterminant le type et le contenu du jeton à délivrer, ou si et comment le jeton doit être chiffré ». audience joue le même rôle avec un nom logique plutôt qu’une adresse, à condition que « la valeur soit quelque chose que le client et le serveur d’autorisation comprennent tous deux » ; la RFC cite comme exemples un identifiant de client OAuth, un identifiant d’entité SAML ou un identifiant d’émetteur OpenID Connect. Les deux peuvent se combiner, et se répéter pour viser plusieurs cibles. scope restreint la portée dans le contexte du service visé. requested_token_type demande un type de jeton précis, faute de quoi le choix revient au serveur.

Ces paramètres sont la réponse technique au problème que DPoP contourne autrement : un jeton correctement restreint à sa cible ne se rejoue pas ailleurs. L’échange de jetons est précisément le mécanisme qui rend cette restriction praticable, puisqu’il permet d’obtenir un jeton par appel sortant au lieu de propager celui de l’entrée. Les dégâts du cas contraire sont décrits dans l’attaque de confusion de scope OAuth, et l’arbitrage des durées dans la durée de vie des jetons OAuth.

act, may_act, et la règle d’application des politiques

Section intitulée « act, may_act, et la règle d’application des politiques »

Trois revendications de jeton JWT portent la sémantique de délégation, et la troisième est celle qu’on oublie.

La revendication act « fournit un moyen, dans un JWT, d’exprimer qu’une délégation a eu lieu et d’identifier la partie agissante à qui l’autorité a été déléguée ». C’est un objet JSON dont les membres identifient l’acteur ; la RFC note que les revendications non identitaires comme exp, nbf et aud « ne sont pas significatives » à l’intérieur d’un act et ne s’y utilisent donc pas. Une chaîne de délégation s’exprime en imbriquant un act dans un autre : « le act le plus extérieur représente l’acteur courant, tandis que les act imbriqués représentent les acteurs antérieurs », le moins récent étant le plus profondément imbriqué. On obtient ainsi une piste d’audit intégrée au jeton, qui relie la demande initiale aux étapes de délégation successives.

Vient alors la règle la plus importante de la spécification pour qui implémente un service consommateur, et elle est impérative : « aux fins de l’application d’une politique de contrôle d’accès, le consommateur d’un jeton NE DOIT prendre en compte que les revendications de premier niveau du jeton et la partie identifiée comme acteur courant par la revendication act. Les acteurs antérieurs identifiés par des act imbriqués sont uniquement informatifs et ne doivent pas être pris en compte dans les décisions de contrôle d’accès. » L’historique sert à comprendre, pas à décider. Un service qui accorderait un droit parce qu’un acteur antérieur de la chaîne le possédait réintroduirait une escalade par composition, de la même famille que celle décrite dans l’audit de l’héritage des permissions.

La revendication may_act, enfin, est celle qui permet de contrôler la délégation en amont : elle « fait la déclaration qu’une partie est autorisée à devenir l’acteur et à agir pour le compte d’une autre partie ». Son usage prévu est net : lorsqu’un subject_token est présenté au point de jeton, le may_act qu’il contient « peut être utilisé par le serveur d’autorisation pour déterminer si le client, ou la partie identifiée dans l’actor_token, est autorisé à s’engager dans la délégation ou l’usurpation demandée ». Autrement dit, l’autorisation de déléguer est inscrite dans le jeton du sujet, et non dans une table de configuration du serveur d’autorisation. C’est le mécanisme à utiliser pour que « ce service peut agir pour cet utilisateur » soit une donnée vérifiable plutôt qu’une convention orale.

Il faut lire la section 5 pour mesurer ce que cette spécification ne fait pas. Elle énonce un principe de prudence sans détour : « la délégation comme l’usurpation introduisent des problèmes de sécurité spécifiques. Chaque fois qu’un principal reçoit les droits d’un autre principal, le potentiel d’abus est une préoccupation. » Et la seule atténuation qu’elle propose est celle de la restriction : « l’usage de la revendication scope, en plus d’autres contraintes typiques comme une durée de vie limitée, est suggéré pour atténuer ce potentiel d’abus, puisqu’il restreint les contextes dans lesquels les droits délégués peuvent être exercés ».

Trois choses sont explicitement hors du périmètre, et elles sont loin d’être des détails. La syntaxe, la sémantique et les caractéristiques de sécurité des jetons eux-mêmes, qu’ils soient présentés ou obtenus, sont « explicitement hors du périmètre ». Aucune exigence n’est posée « sur le modèle de confiance dans lequel une implémentation pourrait être déployée ». Et les questions de signature, de chiffrement ou d’exigence de jetons à preuve de possession sont renvoyées à des profils ou à « des décisions de politique prises au regard des besoins spécifiques des déploiements individuels ». Un déploiement d’échange de jetons est donc un ensemble de décisions locales, et l’absence de décision est elle-même une décision.

Les considérations de vie privée ajoutent deux obligations utiles. Les jetons « DOIVENT être transmis uniquement sur des canaux chiffrés », et surtout : « dans les cas où il est souhaitable d’empêcher la divulgation de certaines informations au client, le jeton DOIT être chiffré à destination de son destinataire prévu ». C’est le cas typique où le service intermédiaire ne doit pas lire ce qu’il transporte, et le mécanisme est celui de JWE. Enfin, les déploiements « DEVRAIENT déterminer la quantité minimale nécessaire de données » et n’inclure que celles-là, « ce qui peut aller, dans certains cas, jusqu’à ne représenter qu’un utilisateur anonyme ou pseudonyme ». Un jeton de dernier plan qui transporte le profil complet de l’utilisateur parce que c’était plus simple est une fuite en attente, et le principe général est celui de la minimisation de la collecte.

Reste une recommandation d’exploitation que la RFC n’écrit pas mais qui découle de tout ce qui précède : décider, service par service, entre délégation et usurpation, et l’écrire. Par défaut, la délégation est préférable, parce qu’elle conserve l’information qui permettra d’enquêter ; l’usurpation ne se justifie que lorsqu’un système en aval est incapable de comprendre un jeton composite, ce qui est une dette à inscrire quelque part plutôt qu’un choix d’architecture.

Sans échange de jetons, un service propage le jeton de l’utilisateur à tous les services qu’il appelle, et chacun de ces services peut le rejouer ailleurs avec la totalité des droits de l’utilisateur. Sans authentification du client au point d’échange, un jeton volé s’y transforme en autant de jetons plus utiles, et le service d’émission devient un amplificateur de compromission. Sans distinction écrite entre délégation et usurpation, les journaux des services de dernier plan n’enregistrent qu’un nom d’utilisateur, et il devient impossible de dire si une action a été déclenchée par un humain, par une intégration ou par un agent automatique. Sans resource, audience ou scope dans la requête, le jeton obtenu est aussi large que celui présenté, et l’échange n’a servi à rien. Sans la règle qui limite la décision d’accès aux revendications de premier niveau et à l’acteur courant, un consommateur accorde des droits au titre d’un acteur antérieur de la chaîne, ce qui recrée une escalade par composition. Et sans may_act, l’autorisation de déléguer n’est inscrite nulle part de vérifiable.

Une application moderne est faite de plusieurs services qui s’appellent entre eux. Quand un utilisateur se connecte, il obtient un jeton d’accès, c’est-à-dire une autorisation temporaire. Le premier service qui le reçoit a souvent besoin d’appeler un deuxième service pour faire son travail, et la question se pose de ce qu’il doit lui présenter.

La mauvaise solution, très répandue, consiste à faire circuler le jeton de l’utilisateur de service en service. Chacun détient alors une autorisation complète, utilisable n’importe où, et une seule fuite compromet tout. L’échange de jetons propose autre chose : le premier service rend le jeton de l’utilisateur à l’autorité qui l’a délivré, en lui disant quel service il doit appeler ensuite, et reçoit en retour un jeton plus étroit, valable uniquement pour cet appel.

Reste une question de forme, qui paraît secondaire et ne l’est pas. Le nouveau jeton peut dire deux choses différentes. Soit « je suis l’utilisateur » : c’est l’usurpation, et le service appelé note dans son journal que l’utilisateur a agi. Soit « je suis le service A, et j’agis pour le compte de l’utilisateur B » : c’est la délégation, et le journal garde les deux noms.

Le jour de l’appel, les deux produisent le même résultat. Six mois plus tard, quand il faut comprendre qui a déclenché une opération, seule la seconde répond. Et comme des programmes automatiques relaient désormais couramment l’identité d’une personne, l’écart devient important : sans cette distinction, on ne peut plus savoir si une action a été voulue par quelqu’un ou décidée par une machine.

Le document de référence prévoit une information pour chaque étape de la chaîne, et une règle de prudence : quand on décide d’accorder un droit, on ne regarde que l’utilisateur et l’acteur courant. Les acteurs précédents sont là pour l’enquête, pas pour la décision.

À lire aussi

Pages voisines