Liferay et CAS : quand le SSL s'en mêle

LiferaySi vous travaillez sur la réalisation de sites web, vous avez surement déjà dû mettre en place une couche SSL, pour chiffrer certaines données sensibles, comme le mot de passe des internautes. Au delà du fait que la génération et l’utilisation des certificats non signés (pour le dev et les tests) reste relativement douloureuse et peu intuitive, il est possible de rencontrer une multitude d’autres problèmes. L’idée de ce post vient de ma volonté de partager une situation où un problème lié au SSL est apparu. J’y explique le problème, sa cause, ainsi que les différentes étapes de sa résolution. J’espère qu’il vous sera utile, et qu’il vous permettra d’éviter certains pièges vicieux dont j’ai été la victime.

Introduction : Contexte technique du projet

Le contexte technique du projet est relativement banal : un portail (Liferay) avec plusieurs portlet externes (webapps), un sytème de SSO – Single Sign On – (CAS), et un frontal faisant office de firewall et load-balancer (par exemple un apache tomcat).
Pour des raisons fonctionnelles, nous avons du intégrer le SSO (CAS) dans une iFrame se trouvant sur le portail. Ainsi, l’internaute reste en permanence sur le portail, est n’est pas redirigé vers une page tierce du SSO. Bien entendu, il reste possible de brancher d’autres applications sur ce SSO, et ce composant reste donc tout à fait justifié.

Le problème

Bien que tout fonctionnait correctement, nous avons décidé de mettre en place une couche SSL sur le portail, pour toutes les pages d’authentification, et les pages contenant des données utilisateur (création de compte, mise à jour du profil, etc). Après quelques tests, nous avons remarqué que l’on perdait parfois la session utilisateur en passant de HTTP à HTTPS. Concrètement, après s’être connecté via une page en HTTPS, on pouvait naviguer tranquillement sur le site, sur toutes les adresses en HTTP. Par contre, dès que l’on passait en HTTPS, l’utilisateur était déconnecté. En repassant sur des pages en HTTP, on retrouvait notre compte, sans aucune redirection vers le CAS. Nous avions donc ici 2 informations interessantes :

  • Le problème est aléatoire (reproduit sur 3 plateformes sur 5)
  • Le problème se situe au niveau du sessionId envoyé au serveur (tomcat), car le compte reste actif en HTTP, sans redirection vers CAS pour nouvelle confirmation de connexion

De plus, ce problème se produisait seulement sur notre plateforme de staging (avec firewall et load-balancer), et non sur nos environnements d’intégration (pas de load-balancer). Le problème semblait donc lié au load-balancing. Après désactivation de l’une des 2 machines du cluster… le problème persitait ! Nous étions donc dans un cas où un problème aléatoire se produit seulement sur une plateforme load-balancée, même avec désactivation du balancing. Incompréhensible…

Résolution du problème

Après plusieurs manipulations infructueuses, nous sommes arrivés à la conclusion que le jsessionId envoyé en HTTP et HTTPS n’était pas le même. Cela semble logique, mais nous avions déjà essayé, sans succès, plusieures configurations différentes du portail, pour par exemple forcer l’utilisation de la même session en HTTP et HTTPS, ou bien répliquer les paramètres d’une session à l’autre. Lorsque que l’on regardait les cookies du navigateur, nous avions 2 jsessionId :

NomDomainePathSécurisé
JSESSIONIDmy.com/Non
JSESSIONIDsub.my.com/Oui
Bizzare. Le site est accessibles par l’url sub.my.com, mais le portail réécrit les cookies pour les mettre sur my.com, afin que tous les sous-domaines utilisent la même session. Si le second cookie venait du portail, il devrait aussi être changé de domaine. Ce n’est évidement pas le cas. Au niveau du serveur, les autres webapps que nous avons sont :
  • Des portlets personnalisées -> Pas de raison de créer un nouveau jsessionId
  • Le CAS -> Ses cookies devraient avoir l’attribut “path” à “/cas-server” (d’ailleurs plusieurs l’ont)

En fait, le problème vient d’un paramètre du connecteur tomcat. En effet, il existe un attribut “emptySessionPath”, mis à “true” par Liferay. Cela permet de forcer le “path” des cookies à “/”. C’est très utile pour les portlet, car elles partagent le même jsessionId, mais… ce n’est pas ce que l’on veut pour le CAS. Comme le portail fonctionne majoritairement en HTTP, la session HTTPS est créée par le CAS, et non par le portail. A cause de la configuration du portail, le cookie du cas est configuré avec un “path” à “/” et non à “/cas-server”. Du coup, lorsque le portail passe en en HTTPS, il utilisae la session de la webapp CAS, et non celle du portail. L’utilisateur perd donc sa session. Quand on revient en HTTP, il la retrouve, car on utilise de nouveau le bon jsessionId. Nous avons donc mis le CAS sur un autre serveur tomcat, pour qu’il ait ses propres cookies, non modifiés. On dirait que ça fonctionne.

CQFD ? Par vraiment, malheureusement. Alors que nous pensions avoir résolu le problème, il est réapparu après quelques tests ! Pourtant, l’explication précédant se tient parfaitement. Il se trouve que nous avions oublié un paramètre important : le load-blancing. Nous l’avions mis de coté car le problème se produisait même avec une seule machine active. En fait, le calcul d’affinité sur le load-balancer se fait par le jsessionId. Ainsi, si vous fournissez toujours le même jsessionId, vous tomberez toujours sur la même machine ? Toujours ? Eh non, justement ! Suivant que la requête soit sécurisée ou non, un même identifiant ne renverra pas sur la machine. C’est évidement un problème de configuration du load-balancer. Après quelques tests, tout fonctionne de nouveau.

Conclusion

Bien que les explications du problème soient relativement simples, il n’a vraiment pas été facile à identifier, car il s’agissait en fait de deux problèmes imbriqués :

  • Configuration du serveur qui “mélangeait” les cookies des différentes webapps
  • Mauvaise configuration du load-balancer qui pouvait changer l’utilisateur de machine, avec un même identifiant, suivant que la requête était sécurisée ou non

La correction de l’un ne faisait pas disparaitre le problème (ce qui poussait à penser que cette correction était fausse), mais était obligatoire. Dans un cas de test normal, on essaie de modifier le moins possible la configuration orginale, pour ne pas inclure d’autres facteurs à risque et de faux comportements dans la résolution du problème. Pour résoudre celui-là, il ne fallait donc pas procéder comme cela, contre toute attente.

Voilà donc quelques conseils pour débugger ce type de cas :

  • Vérifiez les cookies de session
  • Soyez sûrs de bien comprendre la configuration de votre serveur (dans notre cas, nous avions utilisé un bundle tout fait, donc nous n’avions pas ajouté nous-même l’attribut “emptySessionPath” qui posait problème)
  • Dans la cas d’une architecture avec load-balancer, testez toujours avec une seule machine, derrière le load-balancer. Cela permet de dissocier les problèmes inhérents à une config de serveur, et ceux liés à une config du load-balancer. Il peut arriver (comme ici) que le problème vienne de 2 endroits.

Si vous avez des problèmes similaires, n’hésitez pas à les partager ici !