...
- usage de TTL (expireAt) gérés et propres à MongoDB.
- possibilité de requêter facilement les tickets d'un utilisateur donné, et ce sans rustine (contrairement au ticket registry redis donc)
- par défaut sous debian, le mongodb a un stockage persistant (file storage sous /var/lib/mongodb/ )
...
A l'usage, nous ne ressentons pas d'impact de performances par rapport au fonctionnement avec Redis.
On note par contre que la demande de suppression des sessions CAS d'un utilisateur (cf le script en place et partagé sur ce même espace wiki) est maintenant très rapide (était très lent précédemment).
Nettoyage de tickets via DefaultTicketRegistryCleaner (configuration par défaut)
CAS positionne donc bien un expireAt (index TTL, lié à un expireAfterSeconds) propre à MongoDB dans les enregistrements, notamment les TGT (collection ticketGrantingTicketsCollection).
...
Les expireAt des TGT sont alors positionnés à la date du jour + 8H quand le RememberMe n'est (cas.ticket.tgt.primary.time-to-kill-in-seconds) quand le RememberMe n'est pas actionné, et 14 jours sinonà la date du jour + 14 jours (cas.ticket.tgt.remember-me.time-to-kill-in-seconds) avec le RememberMe de positionné.
Les expireAfterSeconds (TTL) sont positionnés à 14 jours (cas.ticket.tgt.primary.max-time-to-live-in-seconds).
Aussi, les tickets sont supprimés par CAS lui-même via DefaultTicketRegistryCleaner en fonction de ce expireAt.
Et "au pire", ils sont supprimés par le mécanisme interne de MongoDB via le expireAfterSeconds au bout de 14 jours.
Cf la documentation MongoDB à ce sujet.
On graphe dès à présent quelques métriques mongo via munin et https://github.com/comerford/mongo-munin (dont on a converti les scripts en python3 via 2to3).
D'ici quelques jours, on espère pouvoir ajouter à cette page wiki des graphes montrant que tout va bien sur notre CAS avec le ticket registry sous mongo.
A l'usage, nous ne ressentons (pour l'instant) pas d'impact de performances par rapport au fonctionnement avec Redis.
On note par contre que la demande de suppression des sessions CAS d'un utilisateur (cf le script en place et partagé sur ce même espace wiki) est maintenant très rapide (était très lent précédemment).
15 jours après le expireAt ... soit 15 jours et 8 heures après pour les TGT non remember-me et 29 jours après pour les remember-me.
Cf la documentation MongoDB à ce sujet vis-à-vis de expireAfterSeconds.
Coût du néttoyage de tickets via DefaultTicketRegistryCleaner
Aussi, avec cette configuration ("par défaut"), DefaultTicketRegistryCleaner a la charge de supprimer tous les tickets expirés de la base. En soit, on peut penser que ce type d'opérations n'est pas gourmande : on supprime tous les tickets dont le expireAt est plus vieux que la date actuelle ; on peut penser que cela se fait en une seule requête. Malheureusement l'implémentation proposée ici est réalisée dans une logique de NoSQL générique : DefaultTicketRegistryCleaner récupère tous les tickets, les "lit" (et donc les décode si ils sont encodés, ce qui est le cas normalement si vous suivez les recommandations de CAS), sélectionne les tickets qu'il estime expirés pour enfin les supprimer. Cette implémentation générique est très coûteuse, notez qu'en plus par défaut cette procédure de suppression de tickets par le DefaultTicketRegistry est appelé toutes les 2 minutes ( cas.ticket.registry.cleaner.schedule.repeat-interval=PT2M ). Ainsi, lors d'une période creuse sur notre CAS, on note une consommation d'environ 30 secondes de CPU à chaque nettoyage de tickets (toutes les 2 minutes) résultant du déchiffrement de l'ensemble des tickets.
Une "optimisation" élémentaire peut être simplement d'augmenter le cas.ticket.registry.cleaner.schedule.repeat-interval à 1 heure par exemple (PT1H).
A l'autre extrême on peut aussi imaginer proposer une autre implémentation du TicketRegistryCleaner, d'autant que son nom de "DefaultTicketRegistryCleaner" nous y incite.
La méthode en cause ici étant DefaultTicketRegistryCleaner.cleanInternal qui est justement protected.
Le plus logique semble cependant laisser le soin à MongoDB de s'occuper lui-même de supprimer les tickets expirés, d'autant que c'est tout l'intérêt de ce type de base et de la fonctionnalité d'index avec un expireAfterSeconds (ttl) de positionné et que c'est ce qui pose problème à l'implémentation via Redis avec l'introduction de l'entrée CAS_PRINCIPAL:uid correspondant à plusieurs TGT et ne pouvant de fait pas avoir un TTL propre.
De plus, la documentation CAS elle-même indique à propos du Ticket Registry Cleaner :
| Info |
|---|
| The ticket registry cleaner is generally useful in scenarios where the registry implementation is unable to auto-evict expired tokens and entries on its own via a background task. |
Optimisation du nettoyage de tickets via MongoDB
Pour laisser MongoDB s'occuper du nettoyage des tickets, on désactive simplement le nettoyage périodique des tickets par CAS ainsi :
| Bloc de code |
|---|
cas.ticket.registry.cleaner.schedule.enabled=false |
Cf ci-dessus cependant, le comportement ne sera cependant pas tout à fait adapté à ce qu'on recherche car CAS a positionné les expireAfterSeconds des index expireAt non pas à 0 seconde mais à la valeur donnée par cas.ticket.tgt.primary.max-time-to-live-in-seconds (15 jours).
Mettre cas.ticket.tgt.primary.max-time-to-live-in-seconds à 0 est tentant mais à 0 il n'est pas pris en compte. Le mettre à 1 seconde peut être aussi une idée, mais l'authentification dysfonctionne alors et il est rappelé dans les logs que cas.ticket.tgt.primary.max-time-to-live-in-seconds doit être plus grand que cas.ticket.tgt.primary.time-to-kill-in-seconds.
Nous n'avons pas trouvé la parade permettant via les configurations de mettre simplement ce expireAfterSeconds à 0.
Aussi, on les surcharche ainsi depuis mongosh :
| Bloc de code | ||
|---|---|---|
| ||
db.proxyGrantingTicketsCollection.dropIndex('expireAt_1')
db.proxyTicketsCollection.dropIndex('expireAt_1')
db.serviceTicketsCollection.dropIndex('expireAt_1')
db.ticketGrantingTicketsCollection.dropIndex('expireAt_1')
db.proxyGrantingTicketsCollection.createIndex({ expireAt: 1 }, { expireAfterSeconds: 0 } )
db.proxyTicketsCollection.createIndex({ expireAt: 1 }, { expireAfterSeconds: 0 })
db.serviceTicketsCollection.createIndex({ expireAt: 1 }, { expireAfterSeconds: 0 })
db.ticketGrantingTicketsCollection.createIndex({ expireAt: 1 }, { expireAfterSeconds: 0 }) |
Les index ne seront pas écrasés par CAS ici lors du redémarrage car cas.ticket.registry.mongo.drop-indexes est à false et malgrè le cas.ticket.registry.mongo.update-indexes à true par défaut (on le met tout de même à false au passage), celui-ci n'a aucun effet si l'index a le même nom (expireAt ici).
Cependant, une fois ces modifications effectuées, on s'aperçoit que le expireAt (des tickets non remember-me) est maintenant fixé non plus à la valeur de cas.ticket.tgt.primary.time-to-kill-in-seconds mais à celle de cas.ticket.tgt.primary.max-time-to-live-in-seconds.
A ce jour, nous n'avons pas trouvé le code responsable de ce changement de comportement - on imagine que c'est lié à la désactivation du DefaultTicketRegistryCleaner.
On modifie le paramètre cas.ticket.tgt.primary.time-to-kill-in-seconds à 8H.
On a maintenant les paramètres suivants :
| Bloc de code | ||
|---|---|---|
| ||
### Valeur d'expiration des TGTs
# Set to a negative value to never expire tickets
# 8 heures au max -> mongodb supprimera lui-même les tickets au bout de 8 heures (sauf remember-me)
cas.ticket.tgt.primary.max-time-to-live-in-seconds=28800
# DefaultTicketRegistryCleaner supprimera les tickets non remember-me au bout de 8 heures (non utile - désactivé)
cas.ticket.tgt.primary.time-to-kill-in-seconds=28800
### Valeur expiration tickets service/proxy : 10sec par défaut, on augmente car latence sur les sogo ...
cas.ticket.pt.timeToKillInSeconds=60
cas.ticket.st.timeToKillInSeconds=60
# rememberme
cas.ticket.tgt.rememberMe.enabled=true
# mongodb (et pas DefaultTicketRegistryCleaner via notre config) supprimera les tickets non remember-me au bout de 14 jours
cas.ticket.tgt.remember-me.time-to-kill-in-seconds=1209600
# rememberme 2 semaines ok pour le cookie
tgc.remember.me.maxAge=1209600
cas.tgc.rememberMeMaxAge=1209600
# pinToSession = false pour que ça fonctionne même si chgt d'IP par exemple.
cas.tgc.pinToSession=false
### Pas de Ticket Registry Cleaner -> mongo s'en charge
cas.ticket.registry.cleaner.schedule.enabled=false
# index expireAt/expireAfterSeconds et plein texte adapté pour optimisation cpu
cas.ticket.registry.mongo.update-indexes=false
|
Effets de bord d'une telle modification ?
On
Optimisation supplémentaire des index
Cf le retour de Pascal concernant la consommation CPU de MongoDB via CAS, nous avons supprimé les index plein texts text et avons ajouté un index simple sur le numéro de ticket de cette façon :
| Bloc de code | ||
|---|---|---|
| ||
db.proxyGrantingTicketsCollection.dropIndex('json_text_type_text_ticketId_text')
db.proxyTicketsCollection.dropIndex('json_text_type_text_ticketId_text')
db.serviceTicketsCollection.dropIndex('json_text_type_text_ticketId_text')
db.ticketGrantingTicketsCollection.dropIndex('json_text_type_text_ticketId_text')
db.proxyGrantingTicketsCollection.createIndex({ ticketId: 1 }, {name:'json_text_type_text_ticketId_text'})
db.proxyTicketsCollection.createIndex({ ticketId: 1 }, {name:'json_text_type_text_ticketId_text'})
db.serviceTicketsCollection.createIndex({ ticketId: 1 }, {name:'json_text_type_text_ticketId_text'})
db.ticketGrantingTicketsCollection.createIndex({ ticketId: 1 }, {name:'json_text_type_text_ticketId_text'}) |
Notez l'astuce de forcer En forçant le nom des nouveaux index simples par le nom des index plein texte créés normalement par CAS : ainsi au redémarrage de CAS, les index plein texte ne sont pas recréés, CAS n'écrase pas ces index par défaut.
cas.ticket.registry.mongo.update-indexes=false fait également en sorte plus simplement que ces modification sur ces index ne soient pas écrasés.
La bonne solution serait évidemment que la correction soit apportée dans CAS directement.