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 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 là).
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 ?
- C’est pas aussi compliqué que ça en à l’air… ;
- 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
- 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 !
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
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