NGINX avec une authentification Kerberos

Salut à tous, ça vous dirait d’apprendre à configurer NGINX avec une authentification Kerberos sous CentOS 7 ? C’est le sujet d’aujourd’hui, vu que j’ai eu besoin de le faire au boulot récemment. Il s’avère que c’est un poil plus suant qu’avec Apache. Bref on y va.

Kerberos ?

Pour ceux qui ne savent pas ce que c’est, kerberos est un protocole d’authentification. Sa particularité est de reposer sur des clés secrètes partagées entre le client et un serveur tiers (le KDC). Vous trouverez pas mal de documentations, plus ou moins exactes, sur le net (c’est un protocole qui date un peu et qui n’est pas forcément super intuitif non plus). On peut vulgariser son utilisation ainsi :

  • A l’ouverture de votre session, le KDC vous délivre un TGT (Ticket Granting Ticket) protégé par un secret partagé entre vous et le KDC.
  • Lorsque vous souhaitez accéder à un service authentifié en kerberos (genre un site web), vous demandez au KDC un TGS (Ticket Granting Service) pour ce service, à l’aide de votre TGT. Ce TGS est protégé (en partie) par la clé secrète du service (que le service partage de son côté avec le KDC)
  • Il vous suffit alors d’envoyer ce TGS au service pour que ce dernier puisse vérifier qu’il a bien été émis par le KDC (avec lequel il partage son secret). Si oui, il peut vous laisser accéder.

Bon c’est fortement simplifié là et ça mériterai que je vous fasse un article dédié dessus (je le met dans ma TODO…). Mais en attendant le schéma de Wikipédia ci-dessous suffira. Il vous faut les concepts de KDC, TGT, TGS et Keytab.

NGINX avec une authentification Kerberos
merci qui? merci Wikipédia !

NGINX et Kerberos

Alors NGINX ne supporte pas nativement kerberos… Mais, il existe un module open-source pour avoir NGINX avec une authentification Kerberos : spnego-http-auth-nginx-module. Comme d’habitude, à vous de voir si vous faites confiance à ce développement tiers, néanmoins ce module reste quand même utilisé et est même indiqué comme la référence par NGINX (pour la version opensource, voir § Do NGINX Open Source and NGINX Plus support single sign‑on?). Donc je pense qu’on peut y aller.

NGINX avec une authentification Kerberos… sous CentOS 7

Prérequis

Donc il vous faudra, les sources de :

Vu qu’il y a déjà des articles dédiés sur le site, je ne vais pas évoquer HTTPS et TLS1.3, qui sont requis avec tout service d’authentification. Sans une configuration HTTPS sérieuse vous vous exposez à des attaques (genre pass-the-ticket).

En dépendance sur CentOS 7 vous aurez besoin d’installer :

yum groupinstall 'Development Tools'
yum install perl-core (#pour openSSL et TLS1.3)
yum install krb5-workstation krb5-devel

NTP

Je vais faire une toute petite digression sur NTP. La synchronisation des horloges du KDC et des clients est indispensable pour Kerberos. La raison est que les TGS et TGT dont je parlais juste avant sont « timestampé » et le serveur vérifie que le ticket est valide avant de donner accès.

Vous devrez donc configurer le client NTP sur votre serveur web et ses clients. En gros ça devrait tenir dans les commandes suivante.

systemctl enable ntpd
vim /etc/ntp.conf # ajouter/vérifier qu'il y a au moins une ligne "server timesrv.domain.tld"
service ntpd start
ntpq -p  # vérifier que la synchro fonctionne.

Compiler NGINX avec le module spnego

Sans transition une fois que vous aurez téléchargée les sources :

mv ./spnego-http-auth-nginx-module-master.zip /opt
cd /opt
unzip ./spnego-http-auth-nginx-module-master.zip
rm ./spnego-http-auth-nginx-module-master.zip 

Même combat pour les sources de ngins

cd /opt
wget http://nginx.org/download/nginx-1.6.0.tar.gz
tar -xvf nginx-1.6.0.tar.gz 
rm nginx-1.6.0.tar.gz 
cd nginx-1.6.0.tar.gz 

# ci-dessous je reprend "bêtement" les options de la compilation du paquet système précédemment installé, en ajoutant le module spnego (idem tuto TLS1.3). Un coup de "nginx -V" devrait vous indiquer cette liste
./configure --prefix=/etc/nginx [...] --add-module=/opt/spnego-http-auth-nginx-module-master 

make
make install

nginx -V # Vérifier la présence des options de compilation pour spnego
nginx -t # Vérifier que votre configuration est compatible avec cette version.
service nginx restart # On redémarre,
service nginx status # et on vérifie que ça tourne..

Générer une Keytab dans un environnement AD (Active Directory)

Bon jusqu’ici on a préparé notre serveur pour qu’il supporte Kerberos, il faut maintenant faire l’échange de secret entre le KDC et le serveur pour ce dernier puisse vérifier les tickets des clients. Techniquement c’est ça une keytab, c’est le secret partagé entre votre (K)DC et le client.

Créer un compte de service

Pour cela, connectez-vous sur votre contrôleur de domaine AD (ou un serveur avec les outils d’administration AD RSAT) et créer un nouvel utilisateur (que j’appellerai SCV_USR_SPNEGO dans la suite de cet article, à vous de modifier) avec les caractéristiques suivantes :

User cannot change password
Password never expires

C’est deux là sont « nécessaires » car à chaque changement de mot de passe du compte, il faudra renouveler le fichier de keytab associé et le redéployer sur votre serveur web. Donc mettez un bon gros mot de passe sur ce compte (genre 32 à 256 char) et stocker-le dans un keepass par exemple.

Vous pouvez aussi sélectionner :

This account support Kerberos AES 256 bit encryption  

Ce serait dommage de se priver d’AES 256 vu qu’on fait du chiffrement symétrique pour une fois, non ?

Générer une keytab pour ce compte

Toujours sur votre serveur avec vos outils d’administration, ouvrez une console PowerShell (ou un cmd.exe pour les anciens, on s’en fou). Et saisissez les 2 commandes suivantes :

ktpass -princ HOST/<ServerNAME.domain.TLD>@<AD_DOMAIN.TLD> -mapuser    <AD_DOMAIN>\<SCV_USR_SPNEGO> -pass '<SCV_USR_SPNEGO-PASSWORD>' -crypto all -mapop set -ptype KRB5_NT_PRINCIPAL -out <ServerNAME>_host.keytab 

ktpass -princ HTTP/<ServerNAME.domain.TLD>@<AD_DOMAIN.TLD> -mapuser    <AD_DOMAIN>\<SCV_USR_SPNEGO> -pass '<SCV_USR_SPNEGO-PASSWORD>' -crypto all -mapop add -ptype KRB5_NT_PRINCIPAL -out <ServerNAME>_http.keytab  

Les trucs importants à noter au dessus : j’utilise l’option « -crypto all » pour autoriser tous les chiffrements supportés. Vous avez peut-être envie d’en enlever un ou deux… Les noms de domaine doivent toujours être en majuscule pour des raisons compatibilité avec l’implémentation de Kerberos sous Linux (Donc AD_DOMAIN.TLD et pas ad_domain.tld). Si vous ne comprenez pas le reste, je vous laisse lire le man de ktpass.exe.

Bref, ces commandes vont vous générer 2 fichiers « .keytab » dans le dossier courant. Il faut les transférer sur votre serveur web linux, par exemple avec pscp (PuTTY).

Bonus: vous pouvez vérifier que le service est déclaré (et trouvable) dans l’AD pour les clients. La commande suivante vous permet de requêter l’AD (le KDC donc) pour savoir si un nom de service existe (de façon identique à ce que le client fera en demandant un TGS pour le service plus loin). Vous verrez dans le paragraphe sur le Debuging que ça peut être utile.

setspn -q HTTP/<ServerNAME.domain.TLD>@<AD_DOMAIN.TLD>

Keytab sur le serveur CentOS

Un fois vos keytab transférées sur votre serveur. Il reste à configurer NGINX pour les utiliser.

Le premier truc à faire est de fusionner les 2 fichiers en un, pour cela on peut utiliser la commande ktutil :

# ktutil
ktutil: rkt  <ServerNAME>_host.keytab 
ktutil: rkt  <ServerNAME>_http.keytab  
ktutil: l
slot KVNO Principal
---- ---- --------------------------------------------------------
1    5     HOST/<ServerNAME.domain.TLD>@<AD_DOMAIN.TLD>
2    6     HTTP/<ServerNAME.domain.TLD>@<AD_DOMAIN.TLD>
ktutil: wkt <ServerNAME>_full.keytab
ktutil:  quit
# rm <ServerNAME>_host.keytab   
# rm <ServerNAME>_http.keytab  

Vous pouvez contrôler le contenu de la nouvelle keytab avec la commande :

klist -kte  <ServerNAME>_full.keytab  

A ce stade on doit configurer Kerberos sur le serveur et vérifier que la keytab nous permet bien d’authentifier l’utilisateur. Pour cela éditez : /etc/krb5.conf

 [libdefaults]
 dns_lookup_realm = true
 dns_lookup_kdc = true 
 ticket_lifetime = 24h 
 renew_lifetime = 7j
 forwardable = true 
 rdns = false
 default_realm =  <AD_DOMAIN.TLD>

 [realms]
 <AD_DOMAIN.TLD> = {
 kdc = <ANY_DC_AD_DOMAIN.TLD> 
 admin_server = <ANY_DC_AD_DOMAIN.TLD> 
 default_domain = <AD_DOMAIN.TLD> 
 }
 [domain_realm]
 .hidden.com = <AD_DOMAIN.TLD> 
 hidden.com = <AD_DOMAIN.TLD> 

C’est au passage le moment de vérifier que le port 88 (UDP et TCP), c’est port standard pour Kerberos, de vos contrôleurs de domaine est accessible depuis votre serveur (et depuis vos clients qui devront demander le TGS, hein…).

Et vous devriez pouvoir tester l’authentification avec la commande suivante :

kinit -5 -V -k - t <ServerNAME>_full.keytab   HTTP/<ServerNAME.domain.TLD>@<AD_DOMAIN.TLD>
# Lister les ticket obtenus :
klist
# les supprimer, tous
kdestroy -A

Vu que cette Keytab ne va servir qu’à NGINX je vais la placer dans le dossier de configuration de NGINX, mais rien de vous empêche de la placer ailleurs. Néanmoins je vous rappelle qu’une keytab : c’est pas chiffré (et donc équivalent à un mot de passe en clair stocké dans un fichier). Il faut donc être vigilant avec les permissions sur celles-ci :

mkdir /etc/nginx/keytabs
mv  <ServerNAME>_full.keytab /etc/nginx/keytabs
cd  /etc/nginx/ 
chown root:nginx  keytabs/<ServerNAME>_full.keytab  
chmod 640 root:nginx keytabs/<ServerNAME>_full.keytab   

Si tout ça fonctionne il est temps de passer à la configuration d’NGINX

Configurer NGINX avec une authentification Kerberos

Je vais supposer que votre site web et déjà configuré dans NGINX (et si vous ne savez pas comment, il y a un exemple par ici) et que vous avez un fichier .conf associé à votre site. Du coup dans ce fichier de configuration, il suffit de rajouter les lignes suivantes (en gras) dans la « location » que vous souhaitez authentifier en kerberos. Exemple pour authentifier tout le site :

server {
  listen 443 ssl http2;
  server_name  <ServerNAME.domain.TLD>;
  root /usr/share/nginx/<website_dir>;
  
  #Configuration SSL;
  #  [...]
  location /{
    auth_gss on;
    auth_gss_realm <AD_DOMAIN.TLD>;
    auth_gss_keytab /etc/nginx/keytabs/<ServerNAME>_full.keytab;
    auth_gss_service_name HTTP/<ServerNAME.domain.TLD>@<AD_DOMAIN.TLD>;
    auth_gss_allow_basic_fallback on;
  }
  #  [...] 
}

On test la configuration avec un coup de nginx -t, et si tout va bien :

service nginx restart

Et on essaye de s’authentifier. Si vous n’avez rien raté, votre utilisateur devrait s’authentifier sans saisir de login ou de mot de passe.

Configuration du navigateur client

Sauf que, ce serait trop simple si ça fonctionnait directement. En effet, la plupart du temps il faut configurer les navigateurs clients pour autoriser SPNEGO. Je vous met ce lien pour la configuration (chez IBM). Sachez que les 3 principaux navigateurs actuels supportent tous spnego et qu’il sont tous configurables par GPO (stratégie de groupe) dans les environnements d’entreprise windows.

Debuging

Et on fait quoi si ça ne fonctionne pas ? Etape 1, passer les logs de NGINX en debug, pour ça éditez le fichier /etc/nginx/nginx.conf pour modifier la ligne de log d’erreur :

error_log /var/log/nginx/error.log debug;

Recharger la conf du serveur et tenter à nouveau une authentification en surveillant le fichier de log cette fois. Je vous met quelques unes des principales erreurs qu’on peut rencontrer.

Client sent too long header line

Classique dans les environnements d’entreprise un peu conséquents (i.e où votre compte utilisateur appartient à plein de groupes). Il suffit de configurer votre serveur web pour accepter ses en-têtes un peu costauds.

gss_accept_sec_context() failed: Unknown error

Dans ce cas là, le plus souvent, votre serveur web est correctement configuré mais votre client n’envoie pas un un TGS kerberos valable (et fallback du NTLM). C’est bien décrit dans cette issue sur github (à laquelle j’ai contribué du coup). Il peut y avoir plusieurs causes à ce comportement erroné du client : DNS pas raccord avec le service Kerberos, Service Kerberos mal nommé dans la Keytab, KDC inaccessible pour le client, service Kerberos introuvable, mauvais service demandé… Brefr, pour la résoudre, il faut comprendre le mécanisme par lequel le navigateur récupère et transmet le TGS au serveur (doc à lire ici et ).

Si besoin, faites une capture réseau de la demande sur votre client, ça devrait vous orienter sur le TGS demandé. Autre sources d’infos, les logs de votre KDC et les demandes de TGS en échec et commande la setspn notamment.

gss_acquire_cred() failed: Permission denied

Il y a de très forte chances que votre keytab ne soit pas accessible par NGINX. A vérifier avec la commande :

sudo -u nginx cat /etc/nginx/keytabs/<ServerNAME>_full.keytab

Donc vérifier vos permissions sur, le fichier et les dossiers du chemin (il faut le droit « x » sur un dossier sous linux pour pouvoir le traverser).

Bonus : limiter l’accès à certains utilisateurs

Le module spnego pour NGINX permet de spécifier les utilisateurs ayant accès en ajoutant la ligne de configuration :

auth_gss_authorized_principal <LOGIN>;

Selon le nombre d’utilisateurs vous pouvez soit les rajouter en dessous de votre configuration kerberos dans le fichiers de conf du site. Soit utiliser un include comme ça.

echo 'auth_gss_authorized_principal <LOGIN>;' >> /etc/nginx/conf.d/authorized_principals.list

Et ajouter dans votre fichier de conf la ligne :

include  /etc/nginx/conf.d/authorized_principals.list 

Kerberos ne permet pas de gérer facilement les « groupes » AD mais LDAP oui, il suffira donc de rajouter une identification LDAP de vos utilisateurs authentifiés et de vérifier leur appartenance à un groupe. Je vous le laisse en devoirs à la maison. 😉

Et avec Apache ?

J’ajoute que si le module NGINX est fonctionnel (et qu’on a même du support plutôt cool sur le github). Dans un environnement d’entreprise, je serai tenté de recommander l’utilisation Apache au lieu de NGINX pour ce type de demandes. En effet, le module mod_auth_gssapi (cf. commentaire de Cédric plus bas) pour apache est en général disponible dans les repository des distributions et supporté nativement, du coup pas besoin de recompiler, donc beaucoup plus simple à déployer dans des contextes un peu contraints. Corolaire sympa, comme c’est plus facile, il y a plus d’utilisateurs, donc plus d’erreurs référencées et résolues sur internet. Ce qui me dispense au passage de vous expliquer comment on fait avec apache :

https://lmgtfy.com/?q=apache+kerberos+sso

Conclusion

Et voilà, on a un NGINX avec une authentification Kerberos. Alors, que retenir ?

  1. C’est pas aussi compliqué que ça en à l’air… ;
  2. mais Kerberos s’appuie sur beaucoup de briques et concepts externes (AD, DNS, réseau, crypto, https, etc.) qui peuvent tous mettre un peu la grouille dans votre config (voir la sécurité) si vous ne les maitrisez pas ; du coup
  3. si vous avez des bases du fonctionnement de Kerberos, ça se passe quand même beaucoup mieux. Sinon, comme souvent en sécu, faites appel à quelqu’un dont c’est le métier !

Voilà, j’espère que ça en aidera un ou deux à configurer leur NGINX avec une authentification Kerberos sur CentOS (ou une autre distribution, on est pas raciste ici). Moi au moins, ça me fera ma doc perso dans un coin !

5 commentaires on “NGINX avec une authentification Kerberos

  1. Bonjour,

    J’ai plusieurs spn (dans plusieurs sous-domaine) sur le même user.
    La génération et fusion des keytab se passent correctement.
    Le kinit fonctionne.
    Par contre après dans la conf nginx comment je stipule plusieurs spn dans auth_gss_service (@sous-domaine.domaine.local) ?

    J’ai essayé avec Any, ça ne fonctionne pas, j’ai essayé en les espaçant dans la directive, ça ne fonctionne pas non plus…

    • Très bonne question, j’en ai pas la moindre idée… à chaque fois que l’ai fait c’était dans un seul domaine et avec une keytab dédié par site.
      D’ailleurs la doc de spnego spécifie :

        auth_gss_service_name: service principal name to use when acquiring credentials.

      avec service principal nameau singulier, je me demande si c’est supporté tout simplement… on est pas vraiment sur module « production-ready » ici.
      Je pense ça vaut le coup que tu ouvres un ticket sur leur GitHub.

      • Bonjour Etienne,

        Merci d’avoir pris le temps de répondre… je galère un peu avec ça.
        On essaie également en spécifiant le spn uniquement sur le domaine parent, mais pas encore fonctionnel…
        Ok pour le message sur leur github

  2. Pour apache, mieux vaut utiliser mod_auth_gssapi, plus récent et fonctionnel que mod_auth_kerb, et aussi dispo en package (du moins sur debian).
    cf. le wiki interne 😉

    • Merci ! J’ai modifié dans l’article !
      C’est compliqué le wiki-interne de dehors aussi… tu me le repostes ici ? :-p
      Byz

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.