Présenter une seule page de connexion pour tous vos services (HOW TO)

Le problème que nous essayons de résoudre

De plus en plus de logiciels utiles aujourd'hui sont distribués sous forme d'applications web. Certains sont livrés avec des images docker, d'autres doivent être installés avec des dépendances. La plupart d'entre eux implémentent une certaine forme de login (authentification) pour y accéder.

Bien qu'une ou deux d'entre elles soient gérables, avec plusieurs d'entre elles installées, cela devient une corvée de se connecter à chaque application et de gérer leur propre date d'expiration. L'interface n'est pas non plus cohérente, ce qui perturbe l'utilisateur (même s'il s'agit d'un utilisateur averti) et le rend vulnérable aux attaques par hameçonnage.

Dans ce tutoriel, je vais essayer d'expliquer et d'implémenter une solution pour que vous ayez une seule page de login pour toutes vos applications, tout en les protégeant des abus et des attaquants indésirables.

Au lieu de ceci: Before

Qu'est-ce que le Single Sign On (SSO) ?

La sphère technologique adore utiliser des glossaires et des acronymes, nous devons donc apprendre leur langage pour utiliser leurs outils. SSO est l'abréviation de Single Sign On. Il s'agit d'un ensemble de techniques permettant de reporter l'authentification à un service unique qui vérifiera si l'utilisateur d'une ressource est autorisé à accéder à cette ressource. Si c'est le cas, le processus est transparent et le service n'intervient pas. Dans le cas contraire, une redirection vers la page de connexion est effectuée et les informations d'identification sont vérifiées.

Cela se passera comme suit: After

Pourquoi est-ce mieux que des pages de connexion multiples ?

Plus vous avez d'applications, plus le risque que l'une d'entre elles soit compromise ou piratée est élevé. Si un login peut être contourné dans une application, la surface d'attaque de l'application complète est disponible pour le hacker. Avec une solution SSO, le login étant redirigé vers une seule application, il n'y a plus qu'un seul code à auditer et à sécuriser. Avec une telle solution, l'utilisateur se verra toujours présenter la même page de connexion quelque soient les applications finales qu'il utilisera. Les attaques de phishing sont donc plus difficiles, puisqu'il est plus difficile de tromper quelqu'un sur un outil qu'il a l'habitude d'utiliser que sur une application peu utilisée.

Comment faisait-on avant que le SSO n'existe ?

Une ancienne solution à ce problème était d'utiliser une pile logicielle avec le même framework (par exemple, un site web en PHP, et d'utiliser session_start pour chaque application dans le processus de connexion). Mais cette solution devient ingérable dès que l'on mélange différentes piles logicielles (PHP, Laravel, Go, npm, etc...).

C'est alors que des solutions différentes sont apparues, avec des acronymes difficiles à deviner et encore plus difficiles à expliquer (comme SAML, OIDC, OAuth2...).

En interne, il se comporte plus ou moins comme ceci: Proxy

Ok, c'est bon, on y va !

Il existe de nombreux tutoriels pour installer Authelia à partir d'un conteneur docker (comme celui-ci one)

Cependant, je ne pense pas que ce soit une bonne idée d'utiliser un conteneur docker ici, cela rend la maintenance plus difficile (on oublie souvent de mettre à jour son conteneur puisqu'il est séparé du système de mise à jour de l'OS) et n'améliore pas vraiment la sécurité (puisqu'il faut toujours ouvrir le même port pour le conteneur docker), réduit les performances (les connexions réseau par proxy des conteneurs vers les conteneurs ont un prix).

Je pense qu'il est préférable d'ajouter Authelia dans le paquetage de votre distribution. Le proxy ne consomme pas beaucoup de ressources, il peut donc être utilisé même sur une petite carte de type fruit rouge.

Ce tutoriel utilise donc Debian stable comme plateforme de base, mais la même chose fonctionnerait pour n'importe quelle distribution supportée.

Je suppose que vous avez déjà un nom de domaine que vous gérez (dans l'exemple que j'utilise, c'est myhome.com), et que vous savez comment ajouter des entrées dans votre DNS. Vous devrez ajouter une entrée CNAME ou une entrée A (ou AAAA) avec le nom auth.myhome.com. qui pointe vers la même adresse que l'IP publique de votre serveur (typiquement, copiez ce qu'il y a dans myapp.myhome.com et personnalisez).

Comme d'habitude, dans les sections de code ci-dessous, si l'invite commence par $, la commande doit être exécutée par votre utilisateur standard. Si elle commence par #, elle doit être exécutée par l'utilisateur root. Ne tapez pas l'invite dans votre terminal, seulement la commande.

Étape 1 : Installation d'Authelia

Ajoutez les clés de signature d'Authelia pour que les paquets puissent être installés sur votre système

$ curl https://apt.authelia.com/organization/signing.asc | sudo apt-key add -

Enregistrez ensuite l'adresse du dépôt et récupérez-la :

$ sudo apt-get install apt-transport-https --yes
$ echo "deb https://apt.authelia.com/stable/debian/debian/ all main" | sudo tee /etc/apt/sources.list.d/authelia-stable-debian.list
$ sudo apt-get update

Enfin, installez-le

$ sudo apt install authelia

Étape 2 : Configurer Authelia

Je vais poster ici un fichier de configuration, et je vais vous expliquer ce qu'il faut personnaliser pour l'adapter à votre installation. Si vous avez plusieurs applications à protéger avec Authelia, vous devrez ajuster la configuration comme décrit dans mes commentaires. Lancez l'éditeur de votre choix pour ouvrir /etc/authelia/configuration.yml. Ensuite, je modifie les clés comme suit :

Clé de configuration Signification Exemple
jwt_secret Une chaîne aléatoire qui ne peut pas être devinée par un attaquant Générez-en une en tapant head /dev/random | tr -dc A-Za-z0-9 | head -c64 > jwt_secret.txt et collez-la ici entre guillemets.
default_redirection_url Si l'utilisateur navigue directement vers auth.myhome.com, Authelia ne peut pas déduire où le rediriger. Il s'agira donc probablement d'une page lister-toutes-les-applications-disponibles https://www.myhome.com
default_2fa_method 2FA est l'abréviation de 2 factor authentication (authentification à deux facteurs). Il s'agit du processus ennuyeux qui vous demande d'entrer le code que vous avez reçu par SMS ou par l'intermédiaire d'une application d'authentification. "totp", au moins c'est gratuit
server/host Attention! Le serveur d'authentification est un serveur qui ne peut pas être utilisé par les utilisateurs de l'Internet. Si vous laissez la valeur par défaut 0.0.0.0 alors Authelia écoutera sur l'interface réseau de votre serveur. Mais ce n'est pas ce que vous voulez une fois que le serveur est en production, puisque nous allons le faire fonctionner derrière un reverse proxy pour HTTPS. Lors de la configuration, laissez 0.0.0.0 mais changez pour 127.0.0.1 une fois que tout est configuré.
log/level Changez pour info une fois que tout est en cours d'exécution info
authentication_backend Activez les sous-clés file et file/path pour faciliter l'utilisation dans un premier temps. Ci-dessous, vous trouverez ce qu'il faut stocker dans ce chemin d'accès Changez path en /etc/authelia/users_db.yml
access_control Cette partie est un peu complexe, donc regardez ci-dessous pour une explication Voir ci-dessous
access_control/rule/.../policy N'importe lequel de deny (rejette la requête), bypass (relai transparent de la connexion), one_factor (demande de login & mots de passe), two_factor (demande de login & mot de passe + preuve de possession d'un appareil) Voir ci-dessous pour un exemple
session/domain Typiquement, définir le domaine de base myhost.com
session/secret Une autre chaîne aléatoire qui ne peut pas être devinée par un attaquant Générez-en une en tapant head /dev/random | tr -dc A-Za-z0-9 | head -c64 > session_secret.txt et collez-la ici entre guillemets.
storage Activez cette option, car elle est nécessaire pour stocker les données de session.
storage/encryption_key Une autre chaîne aléatoire qui ne peut pas être devinée par un attaquant Générez-en une en tapant head /dev/random | tr -dc A-Za-z0-9 | head -c64 > session_secret.txt et collez-la ici entre guillemets
notifier Vous devez définir au moins un notificateur pour que Authelia puisse communiquer avec vous et ses utilisateurs

Règles de contrôle d'accès

Lorsque Authelia intercepte des requêtes, elle vérifie les règles séquentiellement jusqu'à ce qu'elle en trouve une qui corresponde. La règle donne une action à effectuer. Une bonne pratique consiste à écrire les règles à l'envers, en partant de la règle la plus restrictive vers la règle la moins restrictive. Ainsi, lorsque vous lisez une règle dans le fichier de configuration, vous êtes sûr que si elle ne correspond pas, Authelia rejettera la requête.

Un exemple typique est le suivant :

rules:
    - domain:
        - "auth.myhome.com"
      policy: bypass
    - domain:
        - "*.myhome.com"
        - "myhome.com"
        - "app.myhome.com"
      policy: one_factor

La politique par défaut est deny, donc si aucune règle ne passe, l'utilisateur est rejeté.

La première règle pour le domaine auth est écrite de telle sorte que si l'utilisateur demande auth.myhome.com alors il est autorisé à le faire, il peut accéder à la page de login Authelia.

La seconde règle montre une manière différente de faire plus ou moins la même chose : si l'utilisateur accède à myhome.com ou subdomain.myhome.com ou app.myhome.com il lui sera demandé de s'authentifier d'abord.

Si vous avez de nombreuses applications, vous pouvez les lister explicitement ici, ou utiliser un joker *.myhome.com.

Notez que auth.myhome.com est un sous-domaine, mais comme la règle est listée en premier, elle correspond en premier, donc aucune authentification n'est requise pour ce sous-domaine.

Base de données des utilisateurs

Vous devez créer une base de données d'utilisateurs. Cette configuration convient si vous n'avez pas beaucoup d'utilisateurs à gérer. Si ce n'est pas le cas, vous devrez vous plonger dans la documentation Authelia pour utiliser un serveur LDAP ou équivalent. De plus, cette configuration n'est pas faite pour faire tourner plusieurs instances d'Authelia, puisqu'elles se référeront toutes à la même base de données.

La base de données des utilisateurs est un fichier appelé /etc/authelia/users_db.yml qui contient :

users:
  yourname:
    displayname: "Your Name"
    # Generate with: authelia hash-password <your-password>, and copy from the first '$' to the end of the digest.
    password: "<hashed-password>"
    email: <your-email>
    groups:
      - admins

Évidemment, remplacez yourname et tous les autres placeholders par vos informations.

Une fois que vous avez terminé, démarrez Authelia avec :

$ sudo systemctl start authelia

et ouvrez votre navigateur à "http://localhost:9091"

En cas d'erreur, vous pouvez avoir plus d'informations dans le log, via :

$ sudo journalctl -u authelia

Etape 3 : Configurer la partie HTTPS

Comme Authelia affiche une page de connexion/authentification, il doit être exécuté sur un canal de transport crypté pour éviter les attaques de l'homme du milieu (MITM).

Il ne démarrera pas tant que ce canal n'aura pas été correctement configuré.

Comme d'habitude avec HTTPS, pour démarrer une connexion TLS, vous avez besoin d'un certificat. La solution la plus simple est d'utiliser Let's Encrypt ou ZeroSSL avec son outil certbot pour automatiser la récupération et le renouvellement des certificats. Il ne s'agit pas d'un tutoriel sur la mise en place du HTTPS, donc je liste les étapes à réaliser avec un reverse proxy NGINX, n'hésitez pas à personnaliser si vous utilisez un autre reverse proxy pour le HTTPS.

Vous allez créer un fichier dans /etc/nginx/sites-available/auth.myhome.com contenant :

server {
    server_name auth.myhome.com;
    listen 80;
    return 301 https://$server_name$request_uri;
}

server {
    server_name auth.myhome.com;
    listen 443 ssl http2;

    ssl_certificate /etc/letsencrypt/live/myapp.myhome.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/myapp.myhome.com/privkey.pem; # managed by Certbot
    ssl_session_cache shared:SSL:2m;

    location / {
        set $upstream_authelia http://127.0.0.1:9091;
        proxy_pass $upstream_authelia;

        client_body_buffer_size 128k;

        #Timeout if the real server is dead
        proxy_next_upstream error timeout invalid_header http_500 http_502     http_503;

        # Advanced Proxy Config
        send_timeout 5m;
        proxy_read_timeout 360;
        proxy_send_timeout 360;
        proxy_connect_timeout 360;

        # Basic Proxy Config
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-Uri $request_uri;
        proxy_set_header X-Forwarded-Ssl on;
        proxy_redirect  http://  $scheme://;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_cache_bypass $cookie_session;
        proxy_no_cache $cookie_session;
        proxy_buffers 64 256k;

        # If behind reverse proxy, forwards the correct IP
        set_real_ip_from 10.0.0.0/8;
        set_real_ip_from 172.0.0.0/8;
        set_real_ip_from 192.168.0.0/16;
        set_real_ip_from fc00::/7;
        real_ip_header X-Forwarded-For;
        real_ip_recursive on;
    }
}

Remplacez par votre nom de domaine dans le fichier ci-dessus.

La partie qui dit myapp.myhome.com utilise un certificat SSL invalide (celui que vous utilisez déjà pour votre application à myapp.myhome.com). Ne vous inquiétez pas, nous le changerons plus tard, c'est nécessaire pour l'instant pour que NGINX accepte la configuration.

Vous devez ensuite créer un lien pour activer le serveur via:

$ sudo ln -sf /etc/nginx/sites-available/auth.myhome.com /etc/nginx/sites-enabled/auth.myhome.com

Ensuite, vous devez redémarrer NGINX via sudo systemctl restart nginx et générer un bon certificat via sudo certbot run -d auth.myhome.com --nginx

Dirigez votre navigateur vers https://auth.myhome.com et vous devriez voir apparaître la page de connexion de Authelia. Si cela fonctionne, rouvrez le fichier /etc/authelia/configuration.yml et changez la partie host en 127.0.0.1, pour éviter d'exposer le service directement à l'internet.

Etape 4 : Préparer votre application pour le SSO

Ok, maintenant Authelia fonctionne, mais votre application n'est pas encore personnalisée pour fonctionner avec.

L'application doit être adaptée pour traiter avec un serveur d'authentification externe. Dans le diagramme ci-dessus, lorsque Authelia a validé l'utilisateur, il redirige vers votre application. Cette redirection contient des en-têtes spécifiques que votre application doit traiter pour procéder à sa création de session habituelle (typiquement, votre application émettra son Set-Cookie ou une requête de stockage local ou un jeton JWT ou ...). Il s'agit simplement de contourner la page de connexion.

Si vous ne maîtrisez pas votre application, cela peut être difficile à mettre en oeuvre par vous-même. Puisque de nombreux volontaires ont probablement eu affaire à ce problème, la plupart du temps, il s'agit simplement de trouver le bon correctif et/ou les bonnes options de configuration.

Cependant, nous devons nous assurer que ce processus est sécurisé, car vous ne voulez pas qu'un utilisateur distant présentant les en-têtes spécifiques puisse accéder à votre application sans être réellement authentifié.

C'est ce que nous allons faire maintenant.

Typiquement, nous utilisons à nouveau NGINX ici, donc nous allons commencer à construire quelques extraits de configuration qui peuvent être réutilisés dans toutes vos applications.

Le premier extrait est une route interne /authelia qui n'est pas accessible depuis l'internet, qui est utilisée dans vos applications pour affirmer que la session Authelia est valide.

Sauvegardez ce fichier sous le nom /etc/nginx/snippets/authelia.conf

# Virtual endpoint created by nginx to forward auth requests.
location /authelia {
    internal;
    set $upstream_authelia http://127.0.0.1:9091/api/verify;
    proxy_pass_request_body off;
    proxy_pass $upstream_authelia;    
    proxy_set_header Content-Length "";

    # Timeout if the real server is dead
    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;

    # [REQUIRED] Needed by Authelia to check authorizations of the resource.
    # Provide either X-Original-URL and X-Forwarded-Proto or
    # X-Forwarded-Proto, X-Forwarded-Host and X-Forwarded-Uri or both.
    # Those headers will be used by Authelia to deduce the target url of the     user.
    # Basic Proxy Config
    client_body_buffer_size 128k;
    proxy_set_header Host $host;
    proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr; 
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-Uri $request_uri;
    proxy_set_header X-Forwarded-Ssl on;
    proxy_redirect  http://  $scheme://;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_cache_bypass $cookie_session;
    proxy_no_cache $cookie_session;
    proxy_buffers 4 32k;

    # Advanced Proxy Config
    send_timeout 5m;
    proxy_read_timeout 240;
    proxy_send_timeout 240;
    proxy_connect_timeout 240;
}

Vous aurez aussi besoin d'un snippet pour l'authentification (interne) elle-même, donc sauvegardez ce snippet sous /etc/nginx/snippets/auth.conf (remplacez myhome.com par votre domaine) :

Typiquement, ce snippets fait en sorte que NGINX génère une requête vers l'emplacement interne /authelia à chaque fois qu'une requête d'utilisateur est faite vers votre application. Il vérifie le code de résultat HTTP de la requête interne. Si c'est 200, la requête de l'utilisateur est acceptée, sinon il est redirigé vers la page de login Authelia.

# Basic Authelia Config
# Send a subsequent request to Authelia to verify if the user is authenticated
# and has the right permissions to access the resource.
auth_request /authelia;
# Set the `target_url` variable based on the request. It will be used to build the portal
# URL with the correct redirection parameter.
auth_request_set $target_url $scheme://$http_host$request_uri;
# Set the X-Forwarded-User and X-Forwarded-Groups with the headers
# returned by Authelia for the backends which can consume them.
# This is not safe, as the backend must make sure that they come from the
# proxy. In the future, it's gonna be safe to just use OAuth.
auth_request_set $user $upstream_http_remote_user;
auth_request_set $groups $upstream_http_remote_groups;
auth_request_set $name $upstream_http_remote_name;
auth_request_set $email $upstream_http_remote_email;
proxy_set_header Remote-User $user;
proxy_set_header Remote-Groups $groups;
proxy_set_header Remote-Name $name;
proxy_set_header Remote-Email $email;
# If Authelia returns 401, then nginx redirects the user to the login portal.
# If it returns 200, then the request pass through to the backend.
# For other type of errors, nginx will handle them as usual.
error_page 401 =302 https://auth.myhome.com/?rd=$target_url;

Enfin, dans la description de votre site d'application, vous devrez inclure ces extraits. Le authelia.conf va dans le bloc serveur et le auth.conf va dans n'importe quel endroit que vous voulez protéger.

Vous éviterez typiquement de protéger les actifs statiques et les redirections, puisqu'il n'y a pas d'intérêt à vérifier l'authentification pour ceux-ci. Un exemple d'application pourrait ressembler à ceci :

server {
    server_name app.myhome.com;
    listen 80;
    return 301 https://$server_name$request_uri;
}

server {
    server_name app.myhome.com;
    listen 443 ssl http2;
    include snippet/ssl.conf;

    include snippets/authelia.conf; # Authelia auth endpoint

    location / {
        proxy_pass http://proxied:service;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        include snippets/auth.conf; # Protect this endpoint
    }
}

Redémarrez NGINX et essayez de naviguer vers votre application (vous pourriez vouloir vider le cache et les cookies de votre navigateur d'abord pour éviter d'avoir des sessions persistantes). Vous devriez être redirigé vers la page de login Authelia.

Le prochain article montrera un exemple pour Owntone music server

Article suivant