Makina Blog

Le blog Makina-corpus

SSO Keycloak : Ajouter un contrôle d'accès au niveau des flux d'authentification


TLDR : Le Serveur de SSO (Single Sign On) Keycloak ne fournit pas par défaut de moyens pour bloquer l'accès à une application client si l'utilisateur n'est pas associé à un rôle lié (déléguant ce contrôle d'accès à l'application).
 

Si vous voulez bloquer la génération de tokens OIDC (OpenId Connect) depuis le SSO quand un rôle utilisateur n'est pas présent vous devrez modifier le flux d'authentification au niveau de deux sections. La section relative aux formulaires de login, pour les nouvelles sessions,  et la section relative aux cookies, pour les utilisateurs déjà connectés à d'autres applications.

Cette modification du flux d'authentification est assez complexe, demande des tests, et nous montrons ici un workflow fonctionnel et une démo fonctionnelle basée sur une stack docker-compose qui va vous permettre de tester la chose.

Le problème: génération de tokens OIDC y compris sans roles sur le client

Keycloak est un serveur d'authentification SSO qui fait du SAML ou de l'OIDC (OpenId Connect).

Il y a de fortes chances que vous connaissiez déjà un certain nombre de choses sur Keycloak si vous entamez la lecture de cet article, et donc je passerai sur les premiers niveaux de détails sur OIDC ou certaines terminologies propres à Keycloak comme les 'clients'.
Ici nous allons parler de la génération des tokens OIDC dans le flux standard, pour les utilisateurs qui ne disposent pas de rôles associés à une application.

Disons que vous possédez plusieurs clients dans votre configuration Keycloak (donc des applications réelles qui sont branchées sur le SSO). Le type de client n'est pas un problème (confidential ou public, mettons de côté les bearer-only car la génération de tokens se fait sur l'un des deux autres types avant d'être utilisés sur ces APIs).

Vous avez peut-être des rôles globaux ou des rôles définis au niveau de chaque client (ou un mix des deux). Notez que définir au minimum un rôle par client, nommé "Access quelquechose" est, je pense, une bonne pratique, y compris si vous gérez vos rôles au niveau global. Ce rôle est pratique pour effectuer les correspondances dans les 'scopes' et définir quels utilisateurs ont accès à quelles applications.

Le problème dont nous allons parler arrive quand vos utilisateurs n'ont pas tous accès à l'ensemble des applications clientes. Et nous allons manipuler une démo qui recréé ce cas.

Ce que vous devriez observez est que par défaut rien n'empêche vos utilisateurs d'accéder à un client définit dans Keycloak. Les utilisateurs obtiendrons un token OIDC valide pour l'application en question. Le token contiendra sans doute une liste de rôles, et l'application pourrait vérifier cette liste et voir que l'utilisateur n'a pas de rôle pour l'application en cours, mais rien ne bloque la création de tokens OIDC par Keycloak pour cet utilisateur et ce client.

Vous avez en fait demandé au SSO "Qui est cet utilisateur" et il vous a répondu. Cet utilisateur n'a peut être pas d'accès autorisé à votre application mais il semble bien que par défaut le rôle du serveur de SSO s'arrête à cette définition de 'qui est l'utilisateur' et 'quels sont ses rôles'. Le reste est à gérer côté applicatif, en se basant sur les informations contenues dans le token. C'est valide, mais c'est aussi risqué, une erreur côté applicatif et vous avez possiblement des informations affichées à tort. Il est en fait assez courant de voir des applications (front par exemple) qui assument que si l'utilisateur dispose d'un jeton OIDC alors il a accès à l'application.

Démo: faites l'expérience du problème

Clonez le dépôt git de la démo


Un petit projet git existe contenant l'ensemble des éléments permettant d'expérimenter le problème et ses solutions.

Vous pouvez le cloner à partir de Github.

Le README contient des détails pour l'installation, en résumé cela donne:

git clone git@github.com:regilero/keycloak-exp.git
cd keycloak-exp
vim README.md
docker-compose build
docker-compose up -d
# Load 1st configuration
docker-compose stop keycloak
docker-compose \
  run --rm \
   --entrypoint "/bin/bash -c" \
  keycloak \
  " \
   /opt/keycloak/bin/kc.sh \
     --auto-build \
     import \
     --dir /config/ \
     --override true \
  "
 docker-compose up -d

N'hésitez pas à lire le README du projet, il y a par exemple des messages d'erreurs lors de l'import de la configuratio qui ne sont pas graves.

Ajoutez des entrées DNS locales avec votre fichier hosts

 

Editez le fichier hosts de votre machine '/etc/hosts sur Linux) et ajoutez-y

127.0.0.1 keycloak.local
127.0.0.1 client1.local
127.0.0.1 client2.local

vous pourriez essayer d'interroger les 3 applications sur localhost ou 127.0.0.1 suivit du numéro de port (8080, 9091 et 9092) mais vous obtiendriez des conflits de cookies, plus spécifiquement avec les deux applications de debug oidc qui partagent leur configurations dans des cookies. et, oui, les cookies se fichent du numéro de port, seul le nom de l'hôte compte, et donc en utilisant localhost ou 127.0.0.1 les cookies seraient partagés.

Utiliser des noms comme client1.local est toujours une bonne idée.

La démo

Après avoir démarré la stack (et après chargement de la configuration Keycloak) les applications sont disponibles sur:

  • keycloak : http://keycloak.local:8080/auth attention: http://keycloak.local:8080/ sans la partie /auth` ne réponds pas. J'ai ajouté le préfixe /auth/ pour correspondre à toutes les versios précédentes de Keycloak (avant la version 17) qui utilisaient toutes ce préfixe, qui n'est plus actif par défaut, mais tout dans le monde keycloak s'attends à la présence de ce préfixe donc j'ai préféré le remettre. La console d'administration de keycloak (donc le realm master) est disponible avec le compte kadmin dont le mot de passe est kpasswd.
  • client1 : http://client1.local:9091 : une application de debug OIDC
  • client2 : http://client2.local:9092 : la même application de debug OIDC, mais avec une autre config client

Connectez vous sur le client1 et le client2, la première fois ces applications demandent de charger une configuration, donc faites le, chacune dispose d'une configuration propre à charger (config1 et config2 respectivement).

Les deux applications sont des clients OIDC, branchées sur le realm test.

vous disposez de trois utilisateurs de test dans ce realm test:

  • test1 : mot de passe test : membre du groupe Test1
  • test2 : mot de passe test : membre du groupe Test2
  • test3 : mot de passe test : membre des groupes Test1 et Test2

Notre objectif final est de nous assurer que seuls les membres du groupe Test1 ont accès à l'application client1.local et que seuls les membres du groupe Test2 ont accès à l'application client2.local. Donc seul le troisième utilisateur à accès aux deux applications, les autres devraient n'avoir accès qu'à une seule des deux applications.

Détails techniques

Dans la stack docker-compose de cette démo nous avons:

  • keycloak version 18
  • une base de données PostgreSQL
  • Deux instances d'un client OIDC confidential, qui provient en fait d'un joli debugger OIDC (projet idp-oidc-tester) où vous pouvez visualiser les différentes tokens OIDC (access token, refresh token, id token) et le logut (avec pour le moment un fix pour le support du logout qui a été modifié en version 18.

La configuration Keycloak est déjà faite pour le realm 1, dedans nous avons:

  • 2 clients (test-client-id-1 et test-client-id-2) pour les deux instances applicatives
  • chaque client à un rôle associé: Access client1 et Access client2
  • le full scope est désactivé sur ces clients
  • un client-scope Test est créé et automatiquement ajouté aux scopes (onglet client-scope des clients). Ce client-scope est associé aux deux rôles d'accès. Cela pourrait dans le futur être utilisé pour limiter la démo à ces deux clients, tout en ajoutant d'autres clients dans le realm. C'est juste une bonne pratique, préférable au mode 'full scope'.
  • les groupes sont associés aux rôles d'accès respectifs (Groupe test1 pour accès au rôle Access Client 1 par exemple). Utiliser les groupes pour affecter des rôles aux utilisateurs est aussi une meilleure pratique que de le faire au niveau de chaque utilisateur, on gère mieux les montées en charge et combinaisons.

Testez des connections

Pour commencer nous nous rendons sur le client client1.local, sur le port 9091. Si vous activez bien la config 'Client1' vous obtenez cet écran, depuis lequel cous allez cliquer sur le bouton 'Login'.

 

Keycloak Demo Client1 Login

Vous êtes alors redirigé vers le formulaire de login du SSO. Depusi cet écran vous allez pouvoir ouvrir une session pour le premier utilisateur, test1.

Keycloak Login Form
Formulaire de login du SSO: Entrez l'identifiant test1 mot de passe test

De retour sur l'application client1.local sélectionnez l'onglet Access Token. vous devriez obtenir quelque chose approchant ceci:

Keycloak login user1 access token

 

On voit clairement que ce token OIDC contient la liste des rôles associés avec cet utilisateur. Liste de rôles que l'application pourrait contrôler. Pour les habitués on remarquera aussi l'absence d'audience dans ce access token, nous en reparlerons.

Nous allons maintenant, pendant que la session de SSO de l'utilisateur test1 est toujours active, nous connecter sur le deuxième client, sur client2.local sur le port 9092.

Keycloak Demo client2 config

 

Si la configuration n'est pas encore active vous obtenez l'acran ci-dessus, choisissez bien 'client2' puis le bouton 'apply'. On obtient le même état 'vide' pour le client2 et nous allons cliquer sur le bouton Login:

Keycloak Demo Client2 login

A la différence de la première fois avec le client1 nous n'obtenons pas l'écran de login dans Keycloak (car nous avons une session de SSO active, des cookies).

Nous sommes directement de retour sur le client2, et nous pouvons aller sur l'onglet 'Access Token'

Keycloak Demo Client1 User1 Access token

 

Ici on observe une audience (clef aud), qui cible le client1 (et non le client2). cela signifie que votre application (client2) pourrait réutiliser ce token pour faire des requêtes vers client1 (avec l'identité du user courant). le genre de choses que font les clients publics (type front js, quand ils tapent sur des APIs).  Avec les versions précédentes de Keycloak nous aurions obtenus cette même audience en nous connectant sur client1.local. Mais les nouvelles versions de keycloak retirent le client courant (donc par exemple ici le client2 et quand nous étions sur le client1 le client1). C'est un peu dommage, le client courant ayant été retiré des access token de façon systématique nous ne pouvons plus nous servir de l'audience pour vérifier les droits d'accès de l'utilisateur.

Mais je m'éloigne du sujet, vérifier l'audience c'est comme vérifier les rôles, il s'agit d'une vérification à faire par l'application cliente OIDC, nous pouvons déjà constater que par défaut nous avons bien obtenu un accès au client 2, tous les tokens sont là, alors que notre utilisateur 'test1' n'a pas le rôle d'accès à cette application.

Vous pouvez vous déconnecter avec le bouton Logout, puis rester le fonctionnement avec le user 'test2' et le user 'test3'. Vous observerez des différences dans le contenu de la clef 'resource access' et de la clef 'aud' des access tokens, mais tous les utilisateurs obtiennent néanmoins des tokens. C'est à l'application finale de décider ou non d'accepter l'utilisateur à partir des informations portées par le token.

Ci-dessous voici par exemple les tokens pour le user test3, qui a accès aux deux applications (aux deux rôles).

Keycloak demo accesstoken user3 client1

 

 

keycloak demo accesstoken user3 client2

 

Et donc, le problème ?

Et donc, oui, le problème  c'est que ce contrôle d'accès doit être appliqué en écrivant du code applicatif qui examine les contenu des access token et qui prends une décision en fonction des rôles qu'il y trouve.

C'est une chose qu'il est possible de faire, mais cela impacte toutes les applications clients (front et back), et en cas d'oubli on obtient une application client qui affiche potentiellement des contenus qui n'étaient pas destinés à l'utilisateur.

Ce qu'il faudrait c'est pouvoir bloquer ces accès dès l'étape de contrôle de la session par le SSO, et ne pas revenir sur les applications avec des tokens si l'utilisateur n'a aucun accès autorisé sur ces applications.

Et bien cette solution existe, mais elle n'est pas facile à trouver dans Keycloak. C'est ici que nous allons devoir jouer avec les flux d'authentification.

Les flux d'authentification

Dans keycloak les comportements sont définis dans des flux (flows), et le plus important de ces flux est nommé browser. Il correspond à ceci:

Keycloak Flux Browser

 

Pour ajouter ce blocage d'accès au niveau de keycloak nous allons devoir cloner ce flux, puis le modifier pour obtenir des flux de ce type (cliquez pour voir l'image en grand):

Keycloak flux Alternatif final
Flux modifié et permettant un contrôle d'accès par rôle au niveau du SSO

 

 

Attention: il est assez facile d'oublier de modifier aussi la partie 'Cookie' dans ce flux, qui gère les sessions utilisateurs déjà ouvertes (quand on ne passe pas par les formulaires de login). Le flux peut générer des tokens à partir de deux chemins autorisés par défaut, le chemin nouvel-utilisateur-passant-par-les-formulaires-de-login et le chemin utilisateur-déjà-connecté-qui-possède-un-cookie-de-session (les deux autres chemins, Kerberos et Identity Provider Redirector sont désactivés par défaut). Et il est aussi facile de casser complètement l'un ou l'autre des chemins en faisant une petite erreur dans le flux, et donc des tests intensifs sont requis.

Notre but sera d'obtenir le flux tel qu'affiché ci-dessus, et plusieurs piège y sont cachés, nous ferons donc un pas à pas ci-dessous.

Pour l'utilisateur final nous obtiendrons alors ces écrans de blocage gérés par keycloak:

 

Keycloak Access Denied

 

 

Les deux pièges dans l'édition de flux

En éditant ce flux nous allons trouver deux pièges

  • Le premier, et j'en ai déjà parlé au dessus, c'est le fait que l'ouverture de session peut se faire à partir de deux chemins de connexion (nouvelle session, session existante) et il faut donc bien tester chacun de ces chemins
  • Le deuxième est lié au fait que vous devrez cloner plusieurs fois le flux. Pour chaque application client qui fonctionne en mode filtré vous devrez cloner votre nouveau flux et l'associer au(x) client(s). Ici il existe un problème dans le type de copie utilisée lors du clonage de flux, avec certains blocs qui sont effectivement des copies (des clones) et certains blocs qui ne sont en fait pas des copies et qui sont en fait partagés entre les flux. Vous éditez ces blocs pour les adapter à votre client, par exemple pour modifier le rôle ciblé et là... paf, vous venez aussi de modifier le filtrage dans un autre flux. En fait ici il y a sans doute de quoi faire un rapport de bug UX. C'est un peu comme si lorsque vous faites une copie d'un dossier avec ses fichiers sur votre OS, certains des fichiers seraient des copies et d'autres des liens symboliques vers le vrai fichier. Donc faites très attention avec les blocs qui ont besoin d'être modifiés dans chaque clone, vérifiez bien que ce sont des nouveaux blocs, sinon supprimez les et créez un nouveau bloc.

Édition pas à pas du flux

En pas à pas l'opération semble longue, mais notez que cette action n'est à faire que la première fois, ensuite vous ferez des clones de votre nouveau flot. Pour la démo vous n'êtes pas obligé de suivre ce pas à pas, la configuration 'fixée' existe dans la démo et vous pouvez la charger directement (rendez-vous plus bas à la partie dédiée au chargement de la conf fixée).

1- Clonez le flux 'browser'

Clonez le flux "browser". Nommez le nouveau avec un nom qui correspond au filtrage de rôle que vous appliquez, par exemple "Browser with filter on Client1".

Keycloak pas à pas 1

 

Dans ce nouveau flux supprimez la ligne Cookie, nous allons la recréer plus tard mais à l'intérieur d'une section.

keycloak pas à pas 2

 

Flux Cookie

Nous allons créer un premier flux, pour les accès Cookie que nous venons de supprimer.

Utilisez le bouton 'Add flow' tout en haut. Nommez cette section "Filtered Cookie Access".

Keycloak pas à pas step 3

Nous allons ensuite ajouter l'executor "Cookie" à l'intérieur de ce flow. Pour celui il ne faut pas utiliser le bouton "Add execution" mais plutôt le bouton d'action à droite en bout de ligne, dans les actions il y aura Add executor.

keycloak pas à pas step 4

Vérifiez que cet executor est bien en mode REQUIRED.

Nous ajoutons ensuite un nouveau flow à l'intérieur du flux 'Filtered Cookie Access'. Cliquez Add flow an bout de ligne 'Filtered Cookie Access'.

Keycloak Pas à Pas step 5

Nommez ce flow Cookie - Require Role Filtering.

Keycloak pas à pas step6

Vérifiez que le flow est en mode CONDITIONAL.

Keycloak pas à pas step 7

On ajoute ensuite un executor, fils du flux que l'on vient de créer, de type Condition - User role.

Keycloak pas à pas step 8

 

Keycloak pas à pas step 8b

Une fois créé on le passe de DISABLED à REQUIRED.

Puis on édite cette condition avec l'action Config en bout de ligne.

Keycloak pas à pas step 9

 

En alias saisissez Access Role Filtering Client 1 -negate-, cochez l"option negate Output et choisissez dans la liste déroulante le rôle d'accès qui sera requis pour accéder à ce client. Notez qu'il est important de bien mettre quelque chose dans ce nom qui indique que vous avez une condition sur un rôle nommé, et non par exemple un nom comme 'role filtering'. Car quand vous ferez plus tard une copie de ce flux pour une autre application vous identifierez mieux les blocs à supprimer et recrééer (comme celui-ci) car il ne correspondront plus.

Keycloak pas à pas step 11
Keycloak pas à pas step 12

 

On retourne sur le flux, et on ajoute un deuxième executor dans le flux Cookie - Require Role Filtering (attention, pas un fils de "Filtered Cookie Access" mais bien un fils de son fils "Cookie - require role Filtering").

Keycloak step by step 10

Ici on choisit un type Deny Access.

Keycloak step by step 13

Et on s'assure qu'il passe à REQUIRED.

Keycloak step 14 required

Nous avons finit un premier groupe (le filtrage d'accès en mode Cookie), On utilise les flèches pour le faire remonter en tête

Keycloak step 15, cookies en tête

Et si comme moi vous avez ce flux en mode disabled, repassez le en mode 'ALTERNATIVE'.

Flux Formulaires

Nous allons effectuer des opérations très semblables pour le deuxième chemin. La différence ici est qu'il n'est pas besoin de supprimer le flux existant (Browser With filter on Client1 Forms), nous allons plutôt simplement lui ajouter un groupe CONDITIONNAL à la fin, après celui qui existe déjà et qui s'appelle 2FA - Conditional OTP (qui gère le fait que les utilisateurs peuvent actuellement choisir d'activer l'authentification 2FA).

Je ne vous mets plus les copies d'écran, vous devriez maîtriser dorénavant cette UX... particulière.

  • On ajoute un Flot fils de Browser With filter on Client1 Required Forms que l'on nomme Forms - Required Role Filtering.
  • Ne pas oublier de le passer de DISABLED à CONDITIONAL (et non REQUIRED).

Ensuite c'est comme pour le Cookie - Require Role Filtering que nous avions effectué à l'étape précédente, on va ajouter deux executors à ce nouveau flot, le premier de type Condition - User Role (avec sa négation) et le deuxième de type *Deny Access. Comme la fois précédente vous devrez faire Config sur le filtreur de groupe pour aller choisir le bon groupe à filtrer.

Pensez bien à passer ces deux executor en mode REQUIRED.

Revérifiez le schéma global une dernière fois, faites surtout attentions aux statuts REQUIRED/DISABLED/ALTERNATIVE/CONDITIONAL.

Keycloak flux Alternatif final

2- Connectez le flux à une application Client

Pour pouvoir tester ce flux il faut le connecter à au moins une application client (mais si plusieurs applications partagent la même politique de filtrage par rôle vous pourrez bien sur affecter ce flux à l'ensemble des clients concernés).

Dans notre cas d'exemple il faut associer ce flux au client 'client1', depuis la fenêtre d'édition du client.

Keycloak Add client flow

 

A partir de ce point vous pouvez déjà tester les accès sur http://client1.local:9091  sont possibles pour le premier et le troisième utilisateur de test, mais le deuxième utilisateur de test ne devrait plus y avoir accès. Et ceci devrait être testé autant en mode 'création de session SSO' qu'en mode 'à partir d'une session SSO existante (Un des moyens alternatif de tester ce deuxième mode --session existante-- est d'utiliser le bouton d'impersonnation depuis l'administration Keycloak, qui va ouvrir une popup sur l'application account avec une session utilisateur, et dans cette session vous avez un onglet 'Applications' avec des liens direct vers les applications).

3- Clonez votre nouveau flux pour la deuxième application

Pour le client 'test-client-id-2' il va nous falloir un flux approchant mais légèrement différent. En effet il faut le même flux mais filtré sur le rôle 'Access client 2' au lieu de 'Access Client 1'.

Pas la peine de toute reprendre depuis le début, nous allons cloner le flux "Browser With Filter on Client 1" et le nommer "Browser With Filter on Client 2".

Keycloak Copie pour deuxième client

Une fois le flux créé vous verrez que les noms des composants créés sont un peu bizarres, avec du 'Copy of' un peu partout, vous pouvez faire quelques modifications de noms. Mais surout nous avons deux composants qu'il faut reprendre, ceux qui actuellement filtrent sur le rôle 'Client1'.

Et là, attention, il ne faut pas juste les modifier, il faut les supprimer et les récréer, car lors de la copie ils n'ont pas été clonés, il s'agit des mêmes briques que celles du premier flux 'Browser With Filter on Client 1'. Vous pouvez le vérifier en cliquant sur modifier, il y a un uuid qui s'affiche, et c'est le même dans les deux flux.

Keycloak condition uuid

Donc pour ces composants de filtrage par rôle:

  • ajoutez un nouveau composant de filtrage par role au même niveau
  • configurez ce composant pour filtrer sur le bon rôle (toujours en mode 'negate', attention)
  • remontez-le en haut
  • vérifier son statut REQUIRED
  • supprimez l'ancien
Keycloak 2nd version of alternative workflow

Puis connectez ce flux sur le client 2.

Vérifiez le fix avec la démo, chargez la nouvelle configuration

Pour charger la démo avec ces deux flux déjà écrits et branchés sur les clients utilisez ces commandes:

docker-compose stop keycloak
docker-compose \
  run --rm \
  --entrypoint "/bin/bash -c" \
  keycloak \
  " \
    /opt/keycloak/bin/kc.sh \
    --auto-build \
    import \
    --dir /config-v2/ \
    --override true \
"
# ignorez les faux warnings sur --auto-build
# Puis relancez la stack complète
docker-compose up -d

Testez les accès client

Vous pouvez maintenant retester l'ensemble de la démo, il faut normalement bien vérifier que toutes les combinaisons fonctionnent comme attendu:

Vous pourriez aussi ajouter un quatrième utilisateur n'ayant aucun des rôles et bloqué partout. Et peut-être aussi tester un utilisateur ayant activé le 2FA sur les formulaires de login.

Les choses à toujours vérifier

  • ne pas oublier d'associer les clients Keycloak à ces flux alternatifs.
  • attentions aux composants de flux partagés entre plusieurs flux
  • toujours tester l'ensemble des chemins possibles pour les utilisateurs

Autre chose ?

Si vous connaissez d'autres moyens de faire n'hésitez pas à commenter, par exemple via les issues du projet de démo.

Si vous appréciez le travail autour de l'édition de flux sachez qu'il y a pleins d'autres fonctionnalités très intéressantes qui s'y cachent. A partir de Keycloak 17 il y a par exemple les limites sur l'usage en parallèle d'un même compte utilisateur (pour contrôler l'usage des comptes partagés), et les mêmes règles de vérification des chemins alternatifs dans le flux s'appliquent.

Actualités en lien

Image
Chatons association
29/06/2022 - 10:30

CHATONS, un hébergeur près de chez vous

CHATONS est le Collectif des Hébergeurs Alternatifs, Transparents, Ouverts, Neutres et Solidaires. Il a été créé en 2016 par Framasoft et fait suite à la campagne Dégooglisons Internet.

Voir l'article
Image
Encart adhésion associations du libre
28/06/2022 - 16:10

Découvrez les outils libres utilisés par Makina Corpus

Nous souhaitons vous faire découvrir les outils libres et bonnes pratiques que nous utilisons dans ce répertoire. Peut-être aurez vous aussi envie de les utiliser ?

Voir l'article
Image
Django Python Keycloak
18/11/2021 - 17:08

Administrer des comptes Keycloak depuis une application Python/Django

Dans cet article, nous allons créer une application Python/Django qui agira en tant que maître sur Keycloak afin de pouvoir ajouter facilement des comportements personnalisés à Keycloak.

Voir l'article

Inscription à la newsletter

Nous vous avons convaincus