Invalider des jetons dans les systèmes distribués
Sur cette page
Construire des systèmes rapides n’est pas simple, et l’approche habituelle pour gagner en performance est le calcul distribué. Ça pose des problèmes particuliers de sécurité, surtout pour l’authentification et l’autorisation. Les JSON Web Tokens d’OAuth 2 règlent le problème, mais ils compliquent la révocation.
L’authentification distribuée chez SEEK
SEEK connaît bien le modèle distribué. Presque tout ce que tu consultes est servi par des dizaines de micro-services écrits dans des langages différents et maintenus par des équipes différentes.
Sidecar : une tâche cohérente rattachée à l’application principale, généralement placée dans son propre processus ou conteneur, qui offre une interface homogène aux services quel que soit leur langage.
Chez SEEK, on a réglé ça avec un sidecar placé à côté de nos applications. Le sidecar valide les jetons, accepte ou rejette les requêtes entrantes et transmet le contexte de l’utilisateur à l’application.
Pour servir les nombreux langages de SEEK, l’interface homogène choisie est HTTP. Presque tous nos langages l’implémentent, et les en-têtes permettent d’enrichir les requêtes avec des informations en plus.
On peut aussi voir le sidecar d’authentification comme un proxy.
Un exemple du patron sidecar avec deux groupes de tâches dans un environnement cloud à mise à l’échelle automatique. Ici, une requête valide passe par le sidecar jusqu’à la tâche applicative, et une requête non autorisée s’arrête au sidecar. Dans ce scénario, le sidecar d’authentification utilise un JSON Web Key Set (JWKS) en cache pour valider la signature des jetons. On valide donc les jetons de façon distribuée, sans appeler le fournisseur d’identité à chaque requête.
Ce modèle est rapide et scalable, mais il a un défaut : un utilisateur bloqué peut continuer à faire des requêtes jusqu’à l’expiration de son JSON Web Token (JWT). La spécification ne recommande aucune durée d’expiration, mais en pratique les jetons sont de courte durée, environ 15 minutes.
La révocation des jetons
La RFC7009, la spécification de révocation de jetons, fait partie du standard OAuth 2. Elle décrit le processus de révocation, assez basique.
Elle prévoit un point d’accès que le client appelle pour soumettre le jeton à révoquer. Ensuite, les demandes de validation de ce jeton sont rejetées, puisqu’il est considéré comme invalide.
Cette spécification ne fonctionne qu’avec des jetons opaques, pas avec des JSON Web Tokens. Le problème existe dans beaucoup d’entreprises, SEEK compris. Notre architecture distribuée rend très difficile de vérifier efficacement les jetons auprès d’une source centrale. C’est pour ça qu’on signe des jetons à courte durée de vie et qu’on accepte le risque qu’un utilisateur bloqué puisse encore faire des requêtes jusqu’à l’expiration du sien.
Autre difficulté : il faut un moyen de désigner le jeton. Un JSON Web Token contient tout ce qu’il faut pour identifier l’utilisateur. On l’emballe, on le signe et on l’envoie, on ne le stocke pas. Il faut donc ajouter une revendication pour identifier le jeton, et c’est le rôle du JWT ID.
L’invalidation distribuée des jetons
Invalider des jetons dans un système distribué n’est pas simple, mais un filtre de Bloom réduit fortement le coût de la vérification des JWT ID invalidés auprès d’une source centrale.
Les jetons sont en général de courte durée, et on ne s’attend dans aucun cas à devoir bloquer 100 000 000 de jetons. Un filtre de Bloom les fait tenir sans mal dans environ 500 Mo de mémoire, avec une probabilité de faux positif de 1 sur 999 925 224.
Pour ce scénario, je travaille sur oauth-revokerd. Ce service tient la liste des JWT ID révoqués et distribue un filtre de Bloom pour cette liste.
Le service fournit une base de données en mémoire pour stocker à court terme les jetons révoqués, jusqu’à l’expiration du jeton lui-même. L’idée générale : le monter le plus vite possible avec un minimum de configuration, offrir une API de gestion pour révoquer des jetons et une API de requête pour vérifier les révocations en cas de faux positifs.
Dans ce principe, le sidecar est étendu pour consommer le filtre de Bloom.
Pour conclure
C’est à peu près tout pour l’instant. J’espère faire avancer ce projet et je publierai les mises à jour sur mon site.