Une alternative au CAS Jasig: CASIno

Après quelques années d’exploitation d’un CAS Jasig/Aperero, et lors de l’étude d’une nouvelle architecture d’authentification, j’ai remarqué que cette solution n’est pas idéale pour une autre raison qu’historique, ce pour les raisons suivantes:

  • nécessité d’utiliser la solution lourde et ultra-propriétaire Java + Tomcat: ~ entre 2 et 3 go de ram nécessaire / machine,
  • impossible à intégrer dans une solution de déploiement automatisé,
  • aucune visibilité sur le code,
  • configuration par XML, très complexe,
  • aucune possibilité de personnalisation du code.

Mes critères de recherche d’une nouvelle application (dans l’ordre de priorité)

  • Stabilité,
  • Implémentation standard + Single Sign-out,
  • HA + répartition de charge,
  • Possibilité d’utiliser plusieurs contextes d’authentification,
  • Performance,
  • HTML 5 / Standards web,
  • Facilité de déploiement / intégration,
  • Ouverture du code.

Après quelques temps et peu de résultats, je suis tombé sur la solution CASino qui utilise du Ruby On Rails.

login

Reprenons maintenant chacun des critères.

Stabilité: Après lecture rapide du code, il est propre (« clean base » comme ils e précisent. Pas de plantage en backend observé en 3 mois d’utilisation, j’ai juste trouvé un problème très spécifique à la propagation de la connexion lorsqu’on a plusieurs pages de connexion ouvertes en même temps. Autrement dit RAS. 9/10

Standard CAS: Support (confirmé) de CAS 1.0 and CAS 2.0 as well as CAS 3.1 Single Sign Out. ». Le Single Sign-ut est directement opérationnel, sur le CAS Jasig, ce n’est clairement pas le cas. 10/10

HA + RR: Déportée au backend SQL (dans mon cas un cluster Maria DB). our l’application rails, elle est load-balancée n amont par un HAProxy qui fait également du SSL offloading. Tout étant en base, il n’y a pas besoin de faire du RR avec sticky cookie. Sur le CAS Jasig, chaque cas stockait ses sessions propres (leur synchronisation étaient du « plus »)  10/10 aussi.

Multi-contexte: Non prévu dans l’application. Il s’avère que c’est un cas d’utilisation bien plus spécifique que je le pensais. Difficile donc de mettre une note.

Performance: l’applicatif tourne sur une machine avec 500 mo de RAM / 1 vCPU. Pas de fuites mémoire. 10/10, je pense pas qu’on puisse faire mieux dans des langages interprétés.

HTML 5 / Standard web: L’application est responsive, mobile friendly est très agréable. Utilisation de templates ERB/SASS pour personnalisation. 9/10

Déploiement / Intégration: Pour les non connaisseurs de rails, la premier déploiement peut être (très) rebutant. Pour les « débutants », un projets rails fonctionnel (sans avoir à gérer les gems de RoR) est fourni. La documentation paraît complète, mais omet certaines commandes de bases de rails. Concrètement, j’ai pu faire de l’intégration/déploiement continue, avec des sources hébergés sur un dépôt Git avec une branche dédiée au processus. 7/10

Open Source: code sur Github, Licence MIT (on peut ~ tout faire). Que dire de plus ? Ah.. c’est un petit projet avec peu de suivi donc il faut savoir faire avec. Ce qui vaut un 7/10.

Les autres « plus »

  • Possibilité d’utiliser l’authentification à deux facteurs (GAuthenticator)
  • Possibilité de voir et déconnecter ses autres session

Modification apportées et fork.

Je vous présente ici les modification que j’ai apportées sur le code pour intégré ce CAS dans notre environnement.

  • Pour des raisons de compatibilité, l’implémentation d’un contexte de connexion (dans mon cas l’établissement de l’utilisateur), chacun correspondant à un filtre particulier sur le LDAP. L’identifiant retourné par le CAS est donc également contextualisé en fonction du service. Le CAS ainsi modifié devient alors une fédération d’identité centralisée, au contraire d’une architecture shibboleth où chaque Identity Provider est idépendant.
  • Pour simplifier les usages, j’ai implémenté un filtre de groupe ou de contexte par service. Certains services sont dédiés à un établissement spécifique (pas de préfixe, ex d’identifiant fourni: john.doe), d’autres sont dans le contexte « global », et sont préfixés par l’établissement. john.doe devient alors etab1.john.doe. Enfin un service peut-être accessible uniquement à un groupe d’utilisateur défini dans l’annuaire LDAP. Le changement notable est que ces vérifications sont faites côté serveur, qui délivre alors des Service Ticket de manière conditionnelle ! On peut donc filtrer très simplement sans avoir à modifier le client CAS parfois très trivial (le module apache notamment).

Ces modifications m’ont pris environ une semaine de travail, pour produire une solution native et propre (et testée). L’essentiel du temps passé a été d me familiarisé avec Ruby que je découvrais, et CASino. Au final, la modification porte sur moins de 100 lignes, le fork est très proche de l’original et je pourrai envisager de merger facilement les nouvelles versions.

Je souhaite publier le fork produit, mais il me faut encore éliminer le code spécifique à l’entreprise avant de le rendre public. N’hésitez pas à me contacter si vous souhaitez le tester dans votre environnement.

Exemple d’architecture

CASino_infra

Cette infrastructure est totalement redondante. Aucun élément ne constitue un SPOF (Sigle Point Of Failure).

  • Corosync/Pacemaker sur les deux load-balancers (lb1 & lb2) fournissent une IP Virtuelle (vIP) avec bascule automatique
  • HAProxy sur les deux load-balancers répartit la charge et redirige le trafic lorsqu’un serveur CASino tombe (erreur réseau mais aussi au niveau applicatif – erreur 500). Il faut de même pour la partie SQL, il reçoit les demandes de connexion au « pool », qu’il équilibre sur les deux serveurs actifs (+ failover);
  • Le cluster MariaDB est en mode multi-master, chaque serveur peut écrire des données, ce qui accélère les traitements, et améliore la HA. Le cluster fonctionne normalement lorsqu’un élément tombe grâce au quorum.
  • Les serveurs CASino ne stockent pas de données, ils sont donc totalement interchangeables
  • Les LDAP sont en mode master/slave². Deux peux tomber sans interrompre le serveur (car il n’y pas de données temps-réel). L’applicatif CAsino bascule sur le suivant lorsqu’un ne réponds plus.

Mais…

Chose non prévue avant la mise en production. Le cluster MariaDB rencontre des soucis de synchronisation qui provoque des « deadlock » et donc des erreurs 500 (< 1/200 logins). C’est dû au nombre très important de modifications par rapport au nombre de lectures.

Un cluster actif/actif n’est peut-être pas forcément adapté pour des sites qui ont beaucoup d’iops en écritures, mais aujourd’hui je n’ai pas trouvé de meilleure solution….

Références

 

[Apache-PHP] Quant les CSS wordpress ne veulent pas charger

Plusieurs causes sont possibles, mais je parlerai en particulier des erreurs dues à un mauvais type annoncé par le serveur (le type « mime »).

Sur un navigateur web, l’erreur caractéristique sera :

resource interpreted as stylesheet but transferred with mime type text/html wordpress

Qu’est-ce qui se passe ?

Cette erreur est due à une mauvaise configuration du serveur. Le serveur donne l’ordre au navigateur d’interpréter la ressource en tant que page html. Et lorsque le type n’est pas explicitement spécifié dans le code html qui charge le css, ça ne fonctionne pas.

Comment y remédier ?

Activer l’extension de apache « mod_headers ».

a2enmod headers

Il faut maintenant spécifier les nouveaux types. Dans un fichier de configuration global chargé par apache (ex: /etc/apache2/conf.d/headers), ajouter les lignes suivantes :

AddType text/css .css
AddType application/javascript .js


Si ça ne marche toujours pas, il est possible, en fonction de votre configuration, que ce soit php qui force le type de contenu. Si pour x raison, les CSS/JS sont interprétés (ce qui était le cas sur mon installation), PHP a tendance à définir le type par défaut à "text/html".
Pour changer ce comportement, éditer le php.ini (/etc/php5/[fpm|apache2]/php.ini) et remplacer la ligne

default_mimetype = "text/html"

Par (bien laisser la valeur vide, sinon la valeur par défaut sera utilisée)

default_mimetype =