Ça fait un moment que j’utilise Github comme support OAuth2 pour m’authentifier sur des applications. Toutefois, je me suis toujours contenté de suivre une documentation sans réellement chercher à comprendre ce qu’il se passait sous mes yeux chaque fois que je voulais m’authentifier.

De ce fait, je me suis motivé à écrire cet article à propos du SSO. L’objectif est de découvrir les mécanismes disponibles pour gérer une grande quantité d’utilisateurs et leurs accès aux applications de l’infrastructure.

Mais histoire de ne pas perdre les lecteurs qui ne savent pas encore ce qu’est le SSO, je vais vous racontrer une petite histoire pour vous expliquer l’importance de cette méthode d’identification.

Axelle est comptable, elle gère la petite fiscalité de son département et passe par de nombreuses applications pour valider, vérifier ou enregistrer les transactions. Chaque matin, elle s’authentifie sur une application A pour consulter les transactions, puis sur une application B pour enregistrer les achats.
Dès qu’elle consulte un site interne : elle doit s’authentifier, dès qu’elle accède à une nouvelle ressource : un nouveau formulaire à remplir, etc.

En tant qu’administrateur d’une infrastructure, je veux faciliter au maximum l’accès aux applications et aux ressources pour peu que ça ne me pénalise pas au prochain audit de sécurité !

Avoir un LDAP est une possibilité envisageable et simple à mettre en place (et de nombreuses applications sont compatibles) mais on doit toujours passer par cette lourde étape d’authentification. Si j’ai ~10 applications, je dois m’authentifier 10 fois (aka, taper 10 fois mon identifiant, et 10 fois mon mot de passe, certains sites utilisent un username, d’autres un mail…). De plus, le LDAP s’occupe principalement de la partie autorisation et il est complexe de gérer les permissions et droits d’utilisateurs.

Il est également à noter que le LDAP n’est pas une solution all-in-one et que je dois donc ajouter de nombreuses briques applicatives pour permettre aux utilisateurs de :

  • Ajouter un support MFA centralisé,
  • Réinitialiser un mot de passe,
  • Visionner les applications compatibles (un dashboard).

Bref, le LDAP est une solution, qui a ses contraintes et ses avantages.

Or, je cherche quelque chose de simple où je peux modifier les droits d’accès des utilisateurs aux applications et qu’ils puissent s’authentifier une bonne fois pour toutes sur les applications de la boîte.

C’est typiquement ce que fait Google, avec un seul et même compte nous sommes authentifiés sur Youtube, Gmail, Maps, GCP…

Dès lors, ce que nous cherchons, c’est une application de SSO.

Le SSO (Single Sign On)

Le Single Sign On (ou en français “Authentification unique”), est une méthode pour s’authentifier sur une application et faire que sa session soit réutilisable par d’autres produits pour authentifier l’utilisateur.

AvantagesInconvenients
Réduction des risques de PhishingSi dysfonctionnement, de nombreuses applications se retrouvent impactées
Confort de ne plus avoir à taper les mots de passeS’il y a une faille, l’attaquant aura “les clés du chateau”
Facilite l’audit (tout est loggué au même endroit)

En pratique, l’utilisateur s’authentifie sur un site que l’on nomme l’IdP (identity provider / fournisseur d’identité) afin que les autres puissent s’y référer pour identifier un utilisateur.

Le framework le plus courrament utilisé est l’OAuth2/OIDC, nous aurons l’occasion de nous amuser avec dans cet article.

Les mécanismes pour authentifier un utilisateur

OAuth2

OAuth (ou Web Authorization Protocol) version 2 est de nos jours le standard utilisé pour permettre à un utilisateur d’avoir l’accord d’un serveur pour accéder à une ressource. C’est un protocole avant tout d’autorisation et non d’authentification.

Le but de ce dernier est de permettre d’autoriser un site web, un logiciel ou une application (dit « consommateur ») à utiliser l’API sécurisée d’un autre site web (dit « fournisseur ») avec l’accord d’un serveur d’autorisation.

Comme précisé dans le paragraphe précédent : on distingue bien une différence entre l’autorisation et l’identification (ou authentification). La nuance est que l’identification permet de reconnaître un utilisateur précis en fournissant à l’application du détail sur l’auteur de la requête (là où une autorisation peut se délivrer à une entité non nommée).

Si on simplifie, il faut retenir qu’OAuth2 n’est pas un protocole d’authentification, mais de « délégation d’autorisation ». Pour se servir d’OAuth2 comme méthode d’authentification, deux étapes sont nécessaires. La première consiste à récupérer un token d’autorisation, nommé « accessToken », suite à l’authentification de l’utilisateur sur l’application de confiance. La seconde permet, à partir de cet accessToken, d’appeler une API pour confirmer que l’utilisateur possède bien les permissions suffisantes pour accéder à la ressource.

source

Voici les étapes pour autoriser un utilisateur :

  1. L’utilisateur tente d’accéder à une ressource/site.
  2. Le site redirige le client vers le site de confiance.
  3. L’utilisateur s’authentifie.
  4. Le site de confiance redirige l’utilisateur en ajoutant un jeton dans la requête.
  5. Le site récupère le jeton et contacte le site de confiance pour le valider.
  6. L’utilisateur est autorisé à accéder à la ressource .

Une fois que l’utilisateur est autorisé, il peut continuer à utiliser son jeton d’autorisation (access token) jusqu’à expiration de celui-ci. Il devra alors se ré-authentifier ou utiliser un jeton de raffraichissement (refresh token). Le jeton de raffraichissement est ce qui permet à l’utilisateur de réclamer un nouveau couple de jetons à l’IdP. Pour que cette requête aboutisse : le client doit fournir les deux jetons ensemble. L’IdP peut également choisir de ne pas répondre à la requête et demander à l’utilisateur de se ré-authentifier en mettant fin à sa session.

Connaissant cela, la logique est donc de faire des jetons d’autorisations avec une faible longévité (ainsi, si un attaquant le récupère, il n’aura qu’un temps limité pour l’utiliser) et de faire des jetons de raffraichissement ayant une date d’expiration plus reculée.

Maintenant, ajoutons une couche d’identification / authentification à l’OAuth2 avec un framework que l’on nomme : OpenID Connect.

OpenID Connect

OpenID Connect consiste en une simple couche d’identification basée sur OAuth 2.0. Ce protocole permet de vérifier l’identité d’un utilisateur auprès d’un serveur d’autorisation. La différence avec l’OAuth2 est que l’OIDC ajoute de nombreuses informations concerant la personne qui demande l’accès à la ressource, la requête n’est donc plus anonyme. Les opérations sont réalisées via des web services REST et les données sont échangées au format JSON.

Un apport important d’OpenID Connect consiste en la standardisation des données obtenues suite à une authentification. Celles-ci sont formalisées dans un « ID token » au format JWT, qui contient des paramètres obligatoires (ex: identifiant, email…) et non obligatoires (ex: numéro de téléphone, groupes…). Les paramètres ‘obligatoires’ seront toujours nommés de la même façon quel que soit le partenaire de fédération.

Voici certaines données qu’il est possible de transmettre:

  • Email
  • Nom
  • Photo de profil
  • Genre
  • Autres informations personnalisées

Toutes les données ne sont pas forcément transmises à l’application (l’IdP décide de celles à envoyer).

En bref, l’OIDC est la couche qu’il manquait à l’OAuth2 pour gérer les accès de différentes entités nommées et individuelles.

Les étapes pour authentifier un utilisateur sont les mêmes que pour l’OAuth2, sauf que le jeton contient les informations additionnelles nécessaires à l’identification.

Explication OAuth2

SAML

Principalement utilisée pour les applications d’entreprise et gouvernementale, SAML utilise XML pour son format de données d’identité et de simples mécanismes de transport de données HTTP ou SOAP. Le service qui demande et reçoit des données de l’IdP est connu sous le nom de « partie de confiance ». Les données d’identité de l’utilisateur, encapsulées dans un document XML appelé Assertion SAML, se présentent sous la forme d’attributs. Par exemple, l’adresse électronique, le nom, le téléphone, etc.

Dans les faits, le résultat est très similaire à l’OIDC. La popularité de l’OIDC en fait un potentiel remplaçant mais le SAML reste fortement implémenté dans les solutions entreprise.

Le schéma reste identique à l’OAuth2.

GoAuthentik

GoAuthentik est une solution d’authentification unique (SSO) open-source qui permet de gérer l’authentification sur plusieurs applications en utilisant un seul compte. Elle offre la possibilité de donner ou supprimer l’accès à une application à un utilisateur, facilite l’authentification en utilisant des fournisseurs tiers tels que Google ou Github et permet de contrôler les droits des utilisateurs depuis l’application de SSO. De plus, GoAuthentik propose une fonctionnalité pour protéger les applications non compatibles via un middleware Traefik ou un sidecar qui agirait comme un Reverse Proxy.

C’est un concurrent direct à Keycloak réputé pour sa simplicité d’utilisation et sa compatibilité avec de nombreux protocoles / frameworks d’authentification et autorisation.

Par exemple, en tant que provider, il supporte :

  • OAuth2 / OIDC
  • SAML
  • SCIM
  • RADIUS
  • LDAP

Cette liste ne concerne que les protocoles avec lesquels les applications vont se connecter via GoAuthentik (pour la partie LDAP, il expose bien un serveur).

Installation de GoAuthentik

Dans cet article, faisons les choses simples et partons sur une image Docker.

Commençons par télécharger le docker-compose, celui-ci comporte quatre conteneurs. Voici les images présentes, à l’heure où j’écris ces lignes :

  • Le serveur (ghcr.io/goauthentik/server:2024.6.1)
  • Un worker (ghcr.io/goauthentik/server:2024.6.1)
  • Le Redis (docker.io/library/redis:alpine)
  • Une base postgres (docker.io/library/postgres:16-alpine)
wget https://goauthentik.io/docker-compose.yml

On génère ensuite le mot de passe de la base de données et la clé secrète de Authentik (nul besoin de préciser que ces informations sont sensibles).

echo "PG_PASS=$(openssl rand 36 | base64)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand 60 | base64)" >> .env

Vous pouvez maintenant accéder à GoAuthentik par le port 9000 et le chemin /if/flow/initial-setup/ afin de créer le premier compte administrateur. Dans mon cas, je vais l’exposer derrière un Reverse Proxy pour ajouter le TLS (GoAuthentik supporte d’importer des certificats TLS, mais ne supporte pas d’en générer/renouveler).

À noter (pour plus tard dans l’article) que mon GoAuthentik est sur une machine totalement isolée et qui ne possède aucun réseau en commun avec mes autres applications.

Email (optionnel)

Je vous recommande aussi de configurer Authentik pour qu’il puisse envoyer des mails. Cela nous sera utile pour être notifié en tant qu’administrateur mais également aux utilisateurs afin de confirmer leurs mails et réintialiser leurs mots de passe.

Pour cela, voici les variables à mettre dans le .env :

AUTHENTIK_EMAIL__HOST=mon-host-mail
AUTHENTIK_EMAIL__PORT=465
AUTHENTIK_EMAIL__USERNAME=[email protected]
AUTHENTIK_EMAIL__PASSWORD=cafelover
AUTHENTIK_EMAIL__USE_TLS=false
AUTHENTIK_EMAIL__USE_SSL=true
AUTHENTIK_EMAIL__TIMEOUT=10
AUTHENTIK_EMAIL__FROM=[email protected]

Information

Ce n’est pas une erreur, il y a bien deux underscores dans les variables de GoAuthentik !

Je vous invite également à ajouter la variable AUTHENTIK_ERROR_REPORTING__ENABLED=true pour recevoir des mails lorsqu’une erreur intervient dans les traitements.

Après un rédémarrage des conteneurs, notre serveur GoAuthentik est maintenant fonctionnel 😁 !

Avant de continuer, nous nous devons d’expliquer quelques notions pour comprendre le fonctionnement de GoAuthentik.

Fonctionnement de GoAuthentik

Pour que mon article soit en accord avec la documentation, je vais m’efforcer d’utiliser les mots anglais pour les termes liés à GoAuthentik car ce seront ceux présent dans la documentation. Ainsi, ne soyez pas choqués si je parle de “Flow” et non de “Flux” ou de “Stage” et non “d’Étapes”.

GoAuthentik est très flexible et cette liberté a pour prix de devoir comprendre ce que GoAuthentik attend de nous. Par exemple, lorsqu’un utilisateur va s’authentifier, nous pourrons décider des étapes (Stages) qu’il suivra pour que son identité soit confirmée (du formulaire nom et mot de passe, jusqu’à la 2FA et la confirmation par mail si l’utilisateur ne l’a pas validé).

Ainsi, voici les termes à connaitre :

Un Stage est une action attendue de l’utilisateur, ou effectuée par le serveur. Par exemple :

  • Demander un mot de passe à l’utilisateur.
  • Demander à l’utilisateur de s’authentifier par une source précise.
  • Envoyer un mail de confirmation à l’utilisateur pour confirmer son identité.
  • Enregistrer les paramètres de l’utilisateur.

Ce ne sont que des exemples, de nombreuses étapes sont déjà pré-configurées. En voici une pour exemple : default-authentication-mfa-validation.

default-authentication-mfa-validation

Ce Stage permet de valider la MFA (Ex: TOTP ou Yubikey) si l’utilisateur l’a bien configuré. Nous pouvons choisir de lancer cette étape dans un Flow.

Un Flow est une succession de Stage. Ceux-ci commencent toujours par une première dépendance à satisfaire et appartiennent à une catégorie qui définie le contexte attendu (certaines actions en découleront).

La dépendance d’origine concerne toujours le niveau d’authentification de l’utilisateur :

  • S’il n’est pas authentifié.
  • S’il est bien authentifié.
  • S’il est administrateur.

Voici la visualisation d’un flow dans GoAuthentik tel qu’il est représenté dans l’interface administrateur.

Graphique flow

En résumé :

  • Ce flow nécessite que l’utilisateur ne soit pas authentifié.
  • Les Stages default-enrollment-prompt-first et default-enrollment-prompt-second permettent de demander nom d’utilisateur, mot de passe, nom complet et adresse mail.
  • Le Stage default-enrollment-user-write enregistre les informations données par l’utilisateur et crée un compte inactif.
  • Mail confirmation (for enrollement) envoie un mail à l’utilisateur pour passer son compte en actif.
  • Pour finir, default-enrollment-user-login connecte l’utilisateur.

Vous l’aurez compris, ce flow est utilisé pour créer un nouvel utilisateur à partir d’un formulaire.

Chaque étape ou flow peut être liée à des Policies (Policies) pour les valider ou refuser (ex: refuser un mot de passe trop court ou refuser l’accès au formulaire d’inscription si utilisateur n’est pas sur le réseau de l’entreprise).

En espérant que vous n’ayez pas sauté l’explication un peu ennuyante du fonctionnement de GoAuthentik, on peut maintenant passer à la pratique.

GoAuthentik en pratique

Commençons par ajouter notre première application dans la gestion de l’authentification de GoAuthentik. Je vais choisir Proxmox comme première démonstration.

Un applicatif est toujours défini en deux parties :

  • L’application (qui définie les permissions d’accès, qui peut modifier l’entité et comment y accéder),
  • Le provider (permettant de choisir le mécanisme d’authentification).

Pour créer les deux en même temps, je vous encourage à abuser du bouton Create with wizard qui vous permettra de créer l’application et le provider en même temps.

Voici les paramètres de la partie ‘application’ :

  • Nom: Proxmox - Nom de l’application (sera montré à l’utilisateur).
  • Slug: proxmox - Nom utilisé dans les URLs liées à cette application (simple, court, facile à taper).
  • Group: Tech - Les applications du même groupe seront regroupées sur l’interface utilisateur (aucun rapport avec les groupes liés aux utilisateurs).

En “Provider type”, Proxmox est compatible OAuth2/OIDC.

Voici les paramètres pour le provider :

  • Nom: Proxmox provider
  • Authentification flow: default-authentication-flow
  • Authorization flow : default-provider-authorization-explicit-consent si vous voulez que l’utilisateur ait à accepter que l’application accède aux données de l’utilisateur (exemple ci-dessous). Sinon choisir : default-provider-authorization-implicit-consent.
  • La partie Redirect URIs/Origins est optionnelle, mais je vous conseille fortement de la configurer systématiquement. Pour proxmox ce sera https://votre_proxmox:8006 (URL accessible depuis le navigateur de l’utilisateur). Vous pouvez aussi utiliser des REGEX dans ce champ.

Astuce

J’en profite aussi pour vous rappeler que le Proxmox n’a pas besoin d’être exposé dans le même réseau que GoAuthentik. Dans le processus d’authentification OAuth2, c’est le Proxmox qui va contacter GoAuthentik et non l’inverse.

Voici à quoi ressemble mon Provider :

alt text

Dans mon cas, j’utilise https://192.168.1.18[12]:8006 comme URI de redirection puisque ça colle au deux adresses IP de mes noeuds dans mon cluster Proxmox.

Si vous choisissez un Authorization Flow avec un consentement explicite, voici le genre de message que vous aurez (exemple avec l’application Matomo) :

Consent message

Remarque

Un consentement possède une durée et est révocable dans les paramètres de l’utilisateur. Il devra alors de nouveau le valider s’il tente de nouveau d’accéder à l’application.

gestion des approbations

Dans les paramètres avancés du Provider, nous devons aussi configurer comment le sujet est “nommé”. Je vous invite à utiliser le Based on the User’s username dans Proxmox pour définir l’utilisateur.

alt text

Sur les paramètres de notre provider, nous pouvons voir toutes les URLs que notre application (proxmox) pourra utiliser pour communiquer avec notre IDP et pour communiquer avec celui-ci nous avons notre couple de Client ID/Secret à configurer sur l’hyperviseur.

alt text

En cochant “Default”, Proxmox affichera l’authentification par Authentik comme mécanisme par défaut.

alt text

Astuce

La documentation précise aussi que l’on peut configurer Proxmox directement en ligne de commande :

pveum realm add authentik --type openid --issuer-url https://authentik.company/application/o/proxmox/ --client-id xxx --client-key xxx --username-claim username --autocreate 1

Une fois authentifié, vous n’aurez bien évidemment aucune permission sur les machines virtuelles. Le framework OIDC ne permet pas de transférer de permission et de droit (chaque application gère ça individuellement).

Allons maintenant un peu plus loin sur les méthodes d’authentication par GoAuthentik.

Ajouter des utilisateurs

Créer des utilisateurs

La façon la plus simple d’ajouter un utilisateur est bien évidemment de le créer manuellement (dans Directory -> Users).

alt text

Est-il pertinent de dire que je n’ai pas envie de devoir créer manuellement 700 utilisateurs afin que chacun des employés de ma boite puisse avoir son propre compte ?

Voyons alors une nouvelle méthode : le social login. Une pratique permettant à un provider externe de confirmer l’identité d’un utilisateur (Github, Google, LDAP, Okta…).

C’est strictement le même fonctionnement que lorsque vous arrivez sur un site (sans que vous n’ayez créé de compte) et que vous vous connectez avec Google.

Comme c’est également de l’OIDC derrière, la majorité des Identity Provider sont directement compatibles avec GoAuthentik. Voyons ce fonctionnement avec Github…

Social Login avec Github

L’objectif est d’avoir une méthode d’inscription presque “automatique” pour les utilisateurs disposant d’une authentification Github sans que cela ne nécessite une quelconque action manuelle de la part des administrateurs (en dehors de lui attribuer les groupes et les droits liés à son statut).

Information

Dans le cas où vous avez déjà des utilisateurs, vous devrez choisir une stratégie pour lier un utilisateur Github à un utilisateur déjà présent sur le GoAuthentik. Les choix qui s’offriront à vous seront alors les suivants :

  • Lier si un utilisateur Github et GoAuthentik ont le même username ou mail.
  • Refuser de lier automatiquement si l’username/mail correspond à un compte déjà présent.

Pour lier un compte Github, l’utilisateur peut aller dans ses paramètres directement :

connect to github

À condition qu’il n’y ait pas déjà un utilisateur GoAuthentik lié à ce compte Github.

Pour paramétrer l’authentification sur Github, nous devons créer une application OAuth2 depuis Github, cela peut se faire directement dans les paramètres -> Developer settings -> OAuth Apps, ou via ce lien.

dfe7d85e04424a5f1543aab6f4b45fbd.png

L’URL de Callback sera https://goauthentik.us.une-tasse-de.cafe/source/oauth/callback/githubgithub correspondra à notre source OAuth2 sur GoAuthentik (que nous allons créer juste après).

Une fois l’application créée sur GitHub, on obtient un couple ID/Secret à garder précieusement.

Sur l’interface de GoAuthentik, allez dans la section “Federation and Social login” dans Directory afin d’y ajouter un Social Login (et ça tombe bien car un preset GitHub est présent pour faciliter la configuration).

alt text

Les seuls paramètres que je vais changer sont :

  • Name où je dois mettre github pour que ça match avec l’URL de Callback.
  • Consumer key et Consumer secret (obligatoire)
  • Scope où je veux que l’application puisse voir les organisations des utilisateurs qui se connectent (on verra pourquoi un peu plus tard).
  • Slug où je veux que les utilisateurs créés soient au chemin github/, je mets donc %(slug)s dans ce champ.

Astuce

GoAuthentik possède un affichage sous forme de “dossier” pour visionner les utilisateurs. L’objectif n’est pas d’associer des permissions en fonction du chemin, mais plutôt de faciliter la vie aux administrateurs il est toutefois possible de les utiliser pour donner accès à certaines ressources aux membres d’un dossier.

User directory

Mais si je me déconnecte… je n’ai toujours pas la possibilité d’utiliser Github comme méthode d’autentification ☹️.

alt text

La raison : Github n’est pas présent dans le stage utilisé pour l’affichage des méthodes d’authentication. Par défaut, GoAuthentik utilise le flow default-authentication-flow, lui-même appelant le stage default-authentication-identification que je vais modifier pour ajouter Github comme “source” :

alt text

Tadaaaaaa 🥳 ! GitHub est bien ajouté à notre formulaire de connexion. N’importe qui possédant un compte Github peut maintenant créer un compte sur notre GoAuthentik !

51b43b5b769f6f850e91acaaf2fb49f9.png

Attendez… on a dit “n’importe qui” 😵‍💫 ?

Autoriser uniquement une organisation précise

Dans le cadre d’une entreprise, nous ne voulons pas forcément que n’importe qui puisse avoir son compte sur l’IdP (même si aucune permission n’est ajoutée). Pour palier à ce problème, nous pouvons décider d’autoriser l’authentification par Github uniquement si l’utilisateur appartient à une organisation Github spécifique.

C’est d’ailleurs le moment parfait d’utiliser la dernière notion abordée dans le paragraphe : les Politiques (policies en anglais).

Une politique va définir ce qu’un utilisateur a le droit de faire, si le contexte l’autorise à accéder à une ressource, ou à valider des données dans un flow (ex: refuser un mot de passe trop faible).

Voici les types de politiques que l’on peut créer :

alt text

La majeure partie du temps, nous allons utiliser le type Expression Policy qu’est un peu différent des autres. En effet, c’est le plus flexible et celui prenant en compte la plus grande quantité d’information, la raison est que le format des politiques n’est pas un DSL mais est sous forme de script Python.

En bref, un return True sera une autorisation et un return False un refus.

Nous pouvons alors nous baser sur le contexte de la requête (quel utilisateur, ses groupes, son IP, son navigateur, l’heure…) ou faire des requêtes à des services externes (pour peu qu’on utilise les librairies intégrées).

Pour revenir à notre contexte, nous voulons bloquer l’authentification d’un utilisateur s’il n’appartient pas à une organisation. Malheureusement, cette information n’est pas comprise dans les requêtes d’authentification provenant de Github.

Pour obtenir cette information, nous devons contacter l’API de Github à partir du token PAT généré par l’utilisateur durant l’authentification. Voici le code proposé par GoAuthentik dans sa documentation :

if context["source"].provider_type != "github":
    return True

accepted_org = "une-tasse-de-cafe"

connection = context["goauthentik.io/sources/connection"]
access_token = connection.access_token

github_username = context["oauth_userinfo"]Check if user is in Github 

orgs_response = requests.get(
    "https://api.github.com/user/orgs",
    auth=(github_username["login"], access_token),
    headers={
        "accept": "application/vnd.github.v3+json"
    }
)
orgs_response.raise_for_status()
orgs = orgs_response.json()

user_matched = any(org['login'] == accepted_org for org in orgs)
if not user_matched:
    ak_message(f"User is not member of {accepted_org}.")
return user_matched

Créons donc la politique Check if user is in Github Org et assignons la au flow default-source-enrollment. Nous devons aussi modifier l’ordre d’exécution de la politique déjà présente (default-source-enrollment-if-sso) pour qu’elle se lance après la politique que nous venons de créer.

Voici le résultat attendu :

2b7c66370f7e80d60c381f6f883d9ece.png

Puis modifier le Flow pour qu’il ne soit en “success” que lorsque toutes les conditions sont cochées (avant, la réussite d’une seule condition suffisait à ce que l’authentification réussisse).

alt text

En me connectant sur GoAuthentik, celui-ci verra les organisations présentes sur mon compte et la politique sera autorisée.

2550e8427ad5814357d27024994fe4a8.png

Information

Si vous rencontrez des difficultés pour tester la politique, vous pouvez générer un token PAT avec les mêmes permissions que celles demandées par GoAuthentik, puis faire une requête Curl pour afficher les organisations visibles.

TOKEN="pat_monpetittokengénéré"
USER="qjoly"
curl -L \
\-H "Accept: application/vnd.github+json"  
\-H "Authorization: Bearer $TOKEN"  
\-H "X-GitHub-Api-Version: 2022-11-28"  
https://api.github.com/users/${USER}/orgs

La réponse à cette requête sera au même format que celle que nous pouvons lire dans la politique Python.

Inviter les utilisateurs

Une dernière méthode pour créer des utilisateurs est de les laisser s’enregistrer seuls (comme des adultes). Pour cela, nous pouvons générer un lien unique dans la partie Invitations (dans la catégorie ‘Directory’) de GoAuthentik.

Les invitations utilisent toutes les flows du type Enrollment (par exemple, le flow default-enrollment-flow mais je vous conseille de créer un flow dédié à l’invitation des utilisateurs). Une invitation peut être utilisée une ou plusieurs fois et peut avoir une date d’expiration.

Il est également possible de pré-remplir les informations si vous avez un stage du type “Invitation” en remplissant la partie “Custom attributes”.

  • Création du stage Invitation :

alt text

  • Créer l’invitation et définir les “Custom attributes”:

alt text

Dès que l’utilisateur cliquera sur le lien, certains champs seront déjà complétés.

Astuce

Si vous voulez ajouter automatiquement un utilisateur à un groupe, il vous faudra configurer un stage du type User Write qui va finaliser l’inscription de l’utilisateur en ajoutant un groupe spécifique.

Gérer les accès aux applications

Maintenant que nous avons nos utilisateurs, nous voulons sûrement autoriser certaines personnes à accéder à certaines applications spécifiques. Par exemple, permettre aux SRE d’administrer l’hyperviseur mais pas aux comptables.

Pour cela, en allant dans les paramètres d’une application, nous pouvons donner l’accès à celle-ci à :

  • Un utilisateur;
  • Un groupe;
  • Au résultat d’une politique.

En fonction de comment l’application est configurée le résultat d’une règle pourra donner l’accès à un utilisateur (ou à l’inverse, il faudra passer à travers toutes les règles pour avoir l’accès).

Dans 99% des cas je me contente de créer un groupe ayant accès à des sites et j’ajoute ensuite les utilisateurs à ces mêmes groupes. Cela assure une configuration simple et facile à maintenir. Mais parfois, nous souhaitons avoir des politiques un peu plus complexe qu’uniquement vérifier les groupes, en voici quelques exemples :

  • Interdire l’accès à une application pendant un créneau de sauvegarde :
from datetime import datetime

current_time = datetime.now().time()
start_time = current_time.replace(hour=0, minute=0, second=0, microsecond=0)
end_time = current_time.replace(hour=2, minute=0, second=0, microsecond=0)

if start_time <= current_time < end_time :
  ak_message("It's backup time. Go take a coffee and comeback.")
  return False
else:
  return True
  • Refuser l’utilisateur si sa “réputation” (son ratio d’actions réussies/échouées) est faible :

alt text

  • Refuser un utilisateur s’il accède à une application depuis une IP étrangère :
return context["geoip"]["country"] == "FR"
  • Autoriser uniquement l’authentication depuis le réseau de l’entreprise :
return ak_client_ip in ip_network('10.0.0.0/8')
  • Refuser les utilisateurs n’ayant pas configuré le MFA :
return ak_user_has_authenticator(request.user)

À savoir que vous pouvez configurer des politiques pour valider des Stages (et non juste l’accès à une application). Prenons le cas où je souhaite que mes utilisateurs s’enregistrent uniquement avec une adresse mail de l’entreprise. Je peux faire un code Python pour refuser les utilisateurs n’ayant pas le bon format d’adresse mail, mais au lieu d’en arriver là nous pouvons juste empêcher l’utilisateur de s’enregistrer avec cette adresse.

Pour cela, nous devons créer une politique et la mapper directement à un stage précis d’un flow (et pas forcément au flow entier) cela permettra de repartir de la même étape sans invalider les autres stages.

Voici le code que je vais utiliser :

if regex_match(request.context["prompt_data"]["email"], '(.+)@(une-tasse-de.cafe|une-pause-cafe.fr)'):
  return True
else: 
  ak_message("Please enter an email address from the company")
  return False

Je vais mapper ça au stage default-enrollment-prompt-second (celui où l’utilisateur doit entrer son adresse mail) de mon flow d’invitation.

alt text

Ainsi, les utilisateurs n’utilisant pas les domaines de la boîte seront refusés à l’inscription !

Les notifications

Vous l’aurez peut-être remarqué si vous vous êtes amusés avec les politiques en Python : nous recevons un message d’erreur par mail (et sur l’interface des comptes administrateurs) dès qu’une erreur intervient (syntaxe incorrecte, variable non trouvée dans le tableau… vous en aurez beaucoup en debuggant).

alt text

Nous pouvons choisir qui reçoit ces notifications (par défaut, les utilisateurs du groupe authentik Admins) mais surtout quand (et via quel support?) recevoir une notification.

Pour l’instant, les trois notifications paramétrées sont :

  • S’il y a une erreur de configuration dans le serveur GoAuthentik.
  • Si un stage est en erreur.
  • Si une mise à jour est disponible.

Les notifications se déclenchent grâce à des politiques (qui sont souvent du type Event Matcher Policy).

Je souhaite être notifié sur Slack lorsqu’un nouveau utilisateur rejoint mon GoAuthentik (que ça soit par Github ou si une invitation est utilisée).

GoAuthentik peut justement envoyer des notifications par WebHook (méthode compatible avec Slack). Créons notre application Slack :

  • Se rendre sur l’interface développeur de Slack.
  • Créer son application (ne pas passer par un Preset).
  • Sur le dashboard de l’application, aller dans Incoming Webhooks et activer l’option.
  • Ajouter notre application dans un channel Slack (sur la même page).

alt text

On obtient une URL Webhook que GoAuthentik peut requêter pour envoyer des messages. Créons maintenant notre Notification Transports sur GoAuthentik à partir de cette URL.

alt text

Ensuite, nous créons notre Notification Rule qui va notifier le groupe authentik Admins (il est obligatoire d’en mettre un, même si ce n’est pas pertinent ici) avec une alerte du type Notice.

alt text

Maintenant, je vais me connecter à un compte Github fraichement créé pour cet article et je vais m’enregistrer sur mon GoAuthentik :

alt text

Et avec un utilisateur qui passe par les invitations :

alt text

Reverse Proxy

Une des fonctionnalités qui m’a attiré vers GoAuthentik, c’est le fait de pouvoir utiliser un reverse-proxy ajoutant une étape d’authentificiation vers une application ne pouvant pas s’interfacer avec un protocole d’authentification de GoAuthentik (ex: une application statique comme un mkDocs contenant des documentations sensibles). Habituellement, on passe par un Proxy OAuth2 comme ce projet, mais nous n’en aurons pas besoin avec Authentik.

Nous avons deux possibilités :

  • Déployer un agent GoAuthentik qui va agir comme Proxy.
  • Utiliser un Reverse Proxy déjà existant (Traefik, Nginx, Caddy).

Nous allons voir les deux méthodes en commençant par l’agent.

Agent GoAuthentik

À savoir qu’actuellement, mon Authentik est sur une machine isolée et n’est pas présente sur le réseau de l’application que je souhaite protéger.

L’application que je souhaite exposer est accessible via l’URL suivante : http://192.168.1.39:8080 (Authentik ne peut pas accéder à cette adresse IP). Mais grâce au système d’agent Authentik, nous pouvons créer un Reverse-Proxy renvoyant les requêtes vers notre réel IdP.

Agent GoAuthentik

On va commencer par créer notre application sur GoAuthentik. Dans le Provider Type, on sélectionne Transparent Reverse Proxy.

Nous choisissons les flows d’authentification et d’autorisation et nous définissons :

  • L’URL de l’application que nous voulons exposer.
  • L’URL que l’utilisateur va joindre pour accéder à l’application.

alt text

Après cette étape, nous avons notre application et notre Provider. Mais sur la page du Provider, nous avons un message d’erreur Warning: Provider is not used by any Outpost..

Cela signifie qu’aucun agent GoAuthentik n’expose cette application, nous devons donc en déployer un (sur une machine ayant accès à l’application que nous voulons exposer).

Pour cela, nous créons un Outpost du type Proxy et nous sélectionnons l’application que nous venons de créer.

alt text

Sur la droite de notre Outpost, nous avons un bouton View Deployment Info pour voir les variables nécessaires à la création de notre agent authentique, à savoir :

  • AUTHENTIK_HOST - L’URL de notre serveur GoAuthentik que l’agent va joindre.
  • AUTHENTIK_HOST_BROWSER - L’URL de notre serveur GoAuthentik (vers laquelle l’utilisateur sera redirigé pour s’authentifier).
  • AUTHENTIK_TOKEN - Le token du Service Account qui va être utilisé pour s’authentifier sur le GoAuthentik.
  • AUTHENTIK_INSECURE - Si l’agent doit valider le certificat SSL ou non.

Ainsi, je vais déployer une image Docker qui jouera le rôle de Proxy avec les variables d’environnement citées juste au dessus.

service:
  authentik_proxy:
      image: ghcr.io/goauthentik/proxy
      restart: unless-stopped
      ports:
        - 80:9000
        - 443:9443
      environment:
          AUTHENTIK_HOST: "https://goauthentik.us.une-tasse-de.cafe"
          AUTHENTIK_HOST_BROWSER: "https://goauthentik.us.une-tasse-de.cafe"
          AUTHENTIK_INSECURE: "false"
          AUTHENTIK_TOKEN: keep-calm-and-take-a-coffee

Information

Vous devrez configurer le certificat SSL directement sur l’interface d’administration de GoAuthentik. Dans mon cas, je n’ai paramétré aucun certificat ici.

Après avoir lancé l’image Docker, on constate que notre proxy est reconnu par l’instance GoAuthentik (le dernier healthcheck est en succès):

alt text

Nous devons bien sûr configurer notre domaine pour qu’il redirige vers l’adresse IP et Port de l’agent Authentik.

Maintenant, si j’essaie d’accéder à mon application via l’URL http://coffee.home.une-tasse-de.cafe, je suis automatiquement redirigé vers une page d’authentification sur GoAuthentik.

Voici le résultat en image :

Les deux défauts de cette méthode sont dans l’absence de flexibilité (on ne peut pas faire de la haute disponibilité) ou générer les certificats depuis l’agent (actuellement, on doit importer les certificats sur le serveur Authentik pour que l’agent puisse les utiliser).

Maintenant, qu’en est-il du cas où je souhaite passer par un reverse proxy plutôt que par l’agent directement ?

Middleware avec Traefik

Je commence par créer mon application qui me servira à exposer une preview de mon blog (pour voir la mise en page de mes articles), celle-ci est à l’adresse https://staging.a-cup-of.coffee. J’ajoute alors cette application sur Authentik en choisissant le mode Forward Auth (Single Application) concernant le provider. Je configure l’URL interne (utilisée par Traefik) et l’URL que les utilisateurs utiliseront.

Je vais mettre à jour le docker-compose Traefik pour qu’il corresponde à ceci :

Configuration Traefik
services:
  traefik:
    image: "traefik:v3.0.1"
    container_name: "traefik"
    restart: always
    hostname: "traefik"
    networks:
      - traefik-net
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./config:/etc/traefik"

  authentik-proxy:
    image: ghcr.io/goauthentik/proxy
    networks:
      - traefik-net
    environment:
      AUTHENTIK_HOST: https://goauthentik.us.une-tasse-de.cafe
      AUTHENTIK_INSECURE: "false"
      AUTHENTIK_TOKEN: still-dont-want-a-coffee?
      AUTHENTIK_HOST_BROWSER: https://goauthentik.us.une-tasse-de.cafe
    labels:
      traefik.enable: true
      traefik.port: 9000
      traefik.http.routers.authentik.rule: Host(`staging.a-cup-of.coffee`) && PathPrefix(`/outpost.goauthentik.io/`)
      traefik.http.middlewares.authentik.forwardauth.address: http://authentik-proxy:9000/outpost.goauthentik.io/auth/traefik
      traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: true
      traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version
    restart: unless-stopped

networks:
  traefik-net:
    external: true
    driver: overlay
    name: traefik-net

Notez bien que dans les labels du authentik-proxy, j’ai ajouté la redirection vers Authentik pour le domaine staging.a-cup-of.coffee aux chemins /outpost.goauthentik.io/*.

Maintenant, je dois configurer le middleware de Authentik dans le docker-compose.yml de mon blog.

services:
  acupofcoffee-staging:
    build: .
    image: staging-blog-en
    networks:
      - traefik-net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.a-cup-of-coffee-staging.entrypoints=secure"
      - "traefik.http.routers.a-cup-of-coffee-staging.rule=Host(`staging.a-cup-of.coffee`)"
      - "traefik.http.services.a-cup-of-coffee-staging.loadbalancer.server.port=80"
      - "traefik.http.routers.a-cup-of-coffee-staging.tls.certresolver=letsencrypt"
      - "traefik.http.routers.a-cup-of-coffee-staging.middlewares=authentik@docker"  # <--- Just here

networks:
  traefik-net:
    external: true
    driver: overlay
    name: traefik-net

Après avoir relancé mon Traefik et mon conteneur Hugo :

Information

Si nous voulons exposer plusieurs applications dans le Traefik, c’est heureusement possible, nous devons juste :

  • Créer une application sur GoAuthentik.
  • La lier au proxy Outpost depuis GoAuthentik.
  • Modifier les labels de l’agent Authentik pour qu’il match aussi le domaine de la nouvelle application :
  labels:
  # ...
      traefik.http.routers.authentik.rule: Host(`staging.a-cup-of.coffee`, `staging.une-tasse-de.cafe`) && PathPrefix(`/outpost.goauthentik.io/`)
  # ...

C’est bon ! Aucune chance que je ne leak des crédentials/token durant l’écriture de mes articles (c’est jamais arrivé je vous rassure 😇).

Authentification par LDAP

Une plus-value à utiliser Authentik plutôt que Keycloak est qu’il est compatible avec du LDAP pour authentifier nos utilisateurs. Pour que cela fonctionne, nous devons créer un flow dédié à LDAP qui ne demande que le couple utilisateur/mot de passe (en un seul stage et sans MFA).

alt text

  • type: Password stage nom: ldap-authentication-password Backends (laisser par défaut) :

    • User database + standard password
    • User database + app passwords
    • User database + LDAP password
  • type: User Login Stage nom: ldap-authentication-login

  • type: Identification stage nom: ldap-identification-stage User fields:

    • Username
    • Email Password stage: ldap-authentication-password

Maintenant on peut créer notre flow ldap-authentication-flow du type Authentification. On va y bind nos stages créés juste avant dans cet ordre :

  1. Identification stage
  2. ldap-authentication-password
  3. ldap-authentication-login

alt text

Maintenant, on va créer une application LDAP. Dans mon cas, je souhaite pouvoir m’authentifier sur OPNSense grâce à Authentik alors je vais créer l’application dédiée à mon routeur. Je choisirai plus tard un groupe pour les utilisateurs ayant les droits pour faire des requêtes.

alt text

Je laisse le Base DN par défault : DC=ldap,DC=goauthentik,DC=io.

On peut ensuite créer le Outpost qui se chargera d’exposer le serveur LDAP lié à notre Authentik, c’est une étape similaire à lorsque nous exposions un Agent Authentik comme Reverse-Proxy.

alt text

services:
    authentik_ldap:
        image: ghcr.io/goauthentik/ldap
        ports:
            - 389:3389
            - 636:6636
        environment:
            AUTHENTIK_HOST: https://goauthentik.us.une-tasse-de.cafe
            AUTHENTIK_INSECURE: "false"
            AUTHENTIK_TOKEN: still-a-good-time-to-take-a-coffee

Nous avons besoin que l’application (OPNSense dans mon cas) puisse s’authentifier sur le serveur LDAP. Créons alors un Service Account dans la partie Directory/User :

alt text

Après cette étape, on obtient notre utilisateur et un mot de passe. On coche bien le fait de créer en même un groupe au même nom que le Service Account.

On peut maintenant revenir sur la définition de notre provider LDAP pour sélectionner le groupe des utilisateurs pouvant faire des requêtes : ce groupe doit être le même que celui créé durant l’ajout du Service Account (par sécurité, n’ajoutez aucun autre utilisateur dans ce groupe).

alt text

Dès lors, on peut configurer le LDAP sur OPNSense pour l’utiliser comme système d’authentification :

alt text

Permettre à vos utilisateurs de réinitialiser leurs mots de passe

J’ai remarqué que, par défaut, la configuration GoAuthentik ne permettait pas à un utilisateur de réintialiser son mot de passe. Pour autant, nous avons tout le nécessaire pour le mettre en place dans Authentik.

Pour designer notre flow, commençons par lister les stages que l’utilisateur doit passer pour réintialiser son mot de passe :

  • L’utilisateur ne doit pas être authentifié.
  • Demander l’adresse mail de l’utilisateur.
  • Envoyer un mail pour confirmer qu’il est bien l’utilisateur.
  • Afficher le formulaire pour qu’il entre deux fois son nouveau mot de passe.
  • Enregistrer le nouveau mot de passe.

Ce flow appartient à la catégorie Recovery.

alt text

Voici le graphique de notre Flow :

Graphique flow recovery

Pour l’activer sur notre flow d’authentification, je dois aller dans les paramètres de notre Brand (qui représentent les paramètres modifiant l’aspect esthétique de GoAuthentik ou d’activer certains boutons en fonction du domaine).

alt text

On aura un nouveau bouton “mot de passe oublié” sur le stage d’identification :

alt text

Forcer une validation MFA lorsqu’un utilisateur accède à une application

En m’amusant à faire des flows différents, j’ai remarqué qu’il était assez facile de faire un stage forçant l’utilisateur à confirmer sa 2FA lors de l’accès à une application.

Pour cela, je me suis basé sur les Flows Authorization (default-provider-authorization-implicit-consent et default-provider-authorization-explicit-consent) qui servent respectivement à choisir si l’utilisateur va avoir un message ou non pour autoriser l’application cible à accéder aux données de l’utilisateur sur Authentik.

Ainsi, j’ai voulu essayer de créer un flow du même type dans lequel l’utilisateur devait confirmer sa 2FA par Yubikey ou TOTP. L’objectif est d’éviter de compromettre l’accès à l’application si l’utilisateur laisse son ordinateur déverrouillé.

Le stage est le suivant :

802fb147208ddd9e7abf98eab83c307c.png

Et le Flow est le suivant :

Flow Force MFA

Information

Le champ Last validation threshold permet de configurer un temps avant de re-devoir effectuer la validation. Dans mon cas : je laisse hours=0 pour que l’utilisateur le fasse systématiquement (à savoir que cette vérification n’intervient qu’à l’authentification de l’utilisateur et non chaque fois qu’il accède à l’application).

API-server + OIDC = <3

Terminons cet article en beauté avec le fait de s’authentifier sur un cluster Kubernetes via GoAuthentik. En effet, l’API server est nativement compatible avec l’OIDC et pour peu qu’on configure correctement le pod. Nous pouvons alors lui demander de valider l’authentification d’un utilisateur directement avec un issuer (en l’occurence : notre GoAuthentik).

( Et en plus, la documentation de Kubernetes est plutôt complète 😄 )

Pour se faire, on commence par générer une application sur GoAuthentik du type OIDC avec les paramètres suivants (je ne vous apprends plus comment faire à ce stade).

  • Redirects URIs : http://localhost:8000 (si, si, faites moi confiance)
  • Scopes:
    • openid
    • email
    • profile
  • Subject Mode : Based on the User's email.
  • Include claims in id_token : Enabled.

On obtient comme d’habitude notre couple Client ID / Secret.

Mon application se nomme kubernetes-talos-hb-01 sur GoAuthentik, je vais maintenant ajouter les arguments suivants dans la commande appelant l’API-Server :

  • oidc-issuer-url - L’issuer OIDC (ex: https://goauthentik.us.une-tasse-de.cafe/application/o/kubernetes-talos-hb-01/)
  • oidc-client-id - Le client-id pour confirmer
  • oidc-username-claim - L’emplacement de l’username dans le JWT
  • oidc-groups-claim - L’emplacement du groupe dans le JWT

Si vous déployez votre cluster avec Kubespray, vous pouvez directement modifier les variables de l’inventaire à cet endroit.

Dans mon cas, j’utilise Talos (il n’y a pas un article où j’en parle pas), alors voici la configuration que j’ajoute dans mon talconfig.yaml :

controlPlane:
  patches:
    - |-
      - op: add
        path: /cluster/apiServer/extraArgs
        value:
           oidc-issuer-url: "{{ ISSUER_URL }}"
           oidc-client-id: {{ CLIENT_ID}}
           oidc-username-claim: email
           oidc-groups-claim: groups      

Information

Si vous n’utilisez pas talhelper, voici directement le patch à appliquer :

cluster:
  apiServer:
    extraArgs:
      oidc-issuer-url: "{{ ISSUER_URL }}"
      oidc-client-id: {{ CLIENT_ID}}
      oidc-username-claim: email
      oidc-groups-claim: groups

Maintenant que l’API server est prête à authentifier les JWTs fournis par GoAuthentik, on peut configurer notre client kubectl pour qu’il supporte l’authentification OIDC !

C’est également à cette étape qu’on va créer un groupe dédié aux personnes pouvant s’authentifier à notre cluster. Même s’il peut être utilisé pour, le but n’est pas de l’utiliser dans une politique Authentik mais plutôt de fournir une information à l’API-Server pour qu’elle réagisse différemment en fonction des groupes de l’utilisateur.

Je vais créer le groupe kubernetes-api-server et m’assigner à ce groupe.

alt text

Utiliser Kubelogin

Nativement, Kubectl ne peut pas s’authentifier sur un issuer OIDC. Nous avons besoin d’un utilitaire supplémentaire pour cela.

C’est là qu’intervient KubeLogin, un plugin permettant de ajouter cette couche manquante d’OIDC dans kubectl.

kubectl oidc-login setup \
  --oidc-issuer-url="https://goauthentik.us.une-tasse-de.cafe/application/o/kubernetes-talos-hb-01/" \
  --oidc-extra-scope=profile,email \
  --oidc-client-id=${CLIENT_ID} \
  --oidc-client-secret="${CLIENT_SECRET}"

Durant cette étape, Kubelogin va exposer temporairement un serveur web au port 8000 ou 18000 pour récupérer les tokens de GoAuthentik. Une fois l’authentification acceptée et autorisée, nous devons obtenir un JWT comme ceci :

{
  "iss": "https://goauthentik.us.une-tasse-de.cafe/application/o/kubernetes-talos-hb-01/",
  "sub": "[email protected]",
  "aud": "{{ CLIENT_ID}}",
  "exp": 1722259977,
  "iat": 1722259677,
  "auth_time": 1722112885,
  "acr": "goauthentik.io/providers/oauth2/default",
  "nonce": "redacted",
  "email": "[email protected]",
  "email_verified": true,
  "name": "Quentin JOLY",
  "given_name": "Quentin JOLY",
  "preferred_username": "quentin",
  "nickname": "quentin",
  "groups": [
    "authentik Admins",
    "Minio admins",
    "kubernetes-api-server"
  ]
}

La présence de ce JWT confirme que GoAuthentik authentifie bien notre requête à travers kubelogin. Nous pouvons alors créer notre utilisateur dans notre ~/.kube/config, je vais le nommer talos-hb-01 dans mon cas.

kubectl config set-credentials talos-hb-01 \
          --exec-api-version=client.authentication.k8s.io/v1beta1 \
          --exec-command=kubectl \
          --exec-arg=oidc-login \
          --exec-arg=get-token \
          --exec-arg=--oidc-issuer-url=https://goauthentik.us.une-tasse-de.cafe/application/o/kubernetes-talos-hb-01/ \
          --exec-arg=--oidc-client-id=${CLIENT_ID} \
          --exec-arg=--oidc-client-secret="${CLIENT_SECRET}" \
          --exec-arg=--oidc-extra-scope=profile \
          --exec-arg=--oidc-extra-scope=email

Testons une requête vers le cluster :

$ kubectl get nodes --user=talos-hb-01 
Error from server (Forbidden): nodes is forbidden: User "[email protected]" cannot list resource "nodes" in API group "" at the cluster scope

La bonne nouvelle :

  • L’API server reconnaît notre utilisateur (le JWT est alors accepté et fonctionnel).

La mauvaise seconde bonne nouvelle :

  • L’API server ne nous attribue aucune permission.

Nous devons nous-même créer notre ClusterRole (ou en utiliser un existant) et le Bind à notre utilisateur… ou à notre groupe (puisque l’API server peut voir nos groupes sur Authentik).

Générons alors un ClusterRoleBinding pour obtenir les permissions administrateur si l’utilisateur est dans le groupe kubernetes-api-server :

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: oidc-cluster-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: kubernetes-api-server # <---- notre groupe Authentik

Nouvelle tentative :

$ kubectl get nodes --user=talos-hb-01 
NAME   STATUS   ROLES           AGE   VERSION
cp-1   Ready    control-plane   17h   v1.29.1
cp-2   Ready    control-plane   17h   v1.29.1

Tadaaaa 🥳 ! On a bien les accès à notre cluster.

On peut également s’amuser à faire d’autres groupes cluster, en voici quelques exemples :

  • Avec le ClusterRole view (lecture seule) :
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: oidc-app-developper
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: view
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: developper
  • Avec un rôle donnant uniquement les permissions dans le Namespace default :
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: read-write-role-default-ns
  namespace: default
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log", "configmaps", "secrets"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: [""]
    resources: ["services", "endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: ["apps"]
    resources: ["deployments", "daemonsets", "replicasets", "statefulsets"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: ["batch"]
    resources: ["jobs", "cronjobs"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: ["extensions"]
    resources: ["deployments", "daemonsets", "replicasets"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-write-rolebinding
  namespace: default
roleRef:
  kind: Role
  name: read-write-role-default-ns
  apiGroup: rbac.authorization.k8s.io
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: kubernetes-developper

Testons ce dernier :

$ kubectl get pods -n default --user=talos-hb-01-developper
No resources found in default namespace.
$ kubectl get pods -n kube-system --user=talos-hb-01-developper
Error from server (Forbidden): pods is forbidden: User "[email protected]" cannot list resource "pods" in API group "" in the namespace "kube-system"

Nous avons bien les permissions dans le namespace default mais pas les autres !

Conclusion

GoAuthentik est un logiciel très performant et flexible. Le choix du Python comme langage de politique est très intéressant puisque nous n’avons pas les limitations que nous avons habituellement sur d’autres logiciels (tout en gardant une syntaxe simple).

En tant que débutant dans la notion même de SSO, la seule contrainte que j’ai découvert est l’absence de gestion de populations strictement différentes (“realm”), là où keycloak le peut. Authentik étant en développement actif, nous verrons peut-être cette fonctionnalité dans une release.

Mon article n’est qu’une “découverte” de la solution et il reste encore beaucoup de choses à dire. Notamment la partie automatisation que je n’ai pas traitée ici, il existe un provider Terraform ainsi qu’une API REST très complète.

J’espère que cet article aura su vous convaincre. N’hésitez à soutenir mon blog sur Kofi si vous le souhaitez.

Merci de m’avoir lu ☕