EoleSSO

Un article de EoleWiki.

Sommaire

Généralités

Le serveur SSO Eole est basé sur le principe de fonctionnement d'un serveur CAS. Il est compatible avec les applications clientes CAS 1 et 2. la gestion des tickets de type proxy (PT) est seulement disponible dans la version disponible sur les modules Eole 2.1.

Le serveur a été codé en utilisant le langage Python et le framework TwistedMatrix (technologies déjà utilisées dans d'autres produits Eole).

Nous avons ajouté un certain nombre de fonctionnalités:

  • Authentification des utilisateurs non locaux sur un serveur sso parent (par exemple, un serveur académique ou national défini comme parent d'un serveur local à un établissement scolaire)
  • Possibilité d'envoyer des données concernant l'utilisateur aux applications clientes (avec possibilité de définir des filtres en fonction de l'application).
  • Possibilité de calculer de nouvelles données à partir de celles disponibles
  • Compatibilité avec les applications OpenId 2.0 (cf. plus bas)
  • accès à certaines fonctions via le protocole xmlrpc.

le serveur répond en https sur le port 8443:

https://ip_serveur:8443/ --> addresse du serveur CAS

https://ip_serveur:8443/oid --> adresse du serveur OpenId

https://ip_serveur:8443/xmlrpc --> adresse du proxy xmlrpc

fonctionnement du sso (mode par défaut : émulation de CAS)

Gestion de sessions

le serveur d'authentification possède deux caches de session :

  • tickets utilisateurs : longue durée, réutilisable
  • tickets d'application : courte durée (5 minutes par défaut), utilisable une seule fois et pour une seule application
Notion de tickets, cf CAS :

Documentation du protocole CAS

L'application ne voit pas l'id de session utilisateur, il est seulement échangé avec le serveur d'authentification. Une fois que l'application a un ticket applicatif, elle gère une session en interne pour ne pas surcharger le serveur.

logging

Image:Sso2.png


Partie Standard du protocole CAS

1. envoi du login ~ mdp sur le serveur sso

2. si login, mdp bon, le serveur sso renvoie un session_id

3. pour se connecter au backend, le frontend se reconnecte au sso en renvoyant son session_id

4. le sso lui renvoie un ticket (valable très peu de temps)

5. le frontend fait une demande de connexion au backend en envoyant le ticket

6. le backend vérifie la validité du ticket auprès du sso

7. le sso répond oui ou non à la question "le ticket est-il valide"

Si la réponse est oui, on gère alors une session interne (exemple de l'ead) :

8. le backend envoie un magic_number au front-end

9. à chaque communication avec le backend, le front-end devra fournir le magic_number que le backend vérifiera à chaque fois.


         le magic_number expire au bout d'un certain temps (temps
         réinitialisé à chaque action exécutée). Une fois que le
         magic_number a expirée, alors le front-end doit recommencer
         la séquence d'authentification.

Exemple de fonctionnement en mode CAS : EAD 2

1. Un utilisateur A se connecte à l'EAD de l'établissement E

2. L'EAD ne voit pas de cookie de session, il redirige donc vers une page de formulaire Login/Mdp (plus ou moins un frontend du serveur d'authentification 'local')

3. A rentre son login/mdp

4. Le frontend web utilise une liste de serveur d'authentifications pour tester le login/mdp sur chacun d'eux. En pratique, la liste consiste en ['ServerAuth Local', 'ServerAuth Distant'] :

  • Si aucun des serveurs d'authentifcation n'a validé les identifiants

de A, il est re-redirigé vers le formulaire Login/Mdp

  • Sinon, il est renvoyé sur la page d'entrée de l'EAD, et une session est

créée. L'identifiant de session, stocké dans le cookie, permet de récupérer la liste des rôles associés à l'utilisateur.

CASification d'une application web existante

Il existe des librairies clientes pour le protocole CAS qui permettent de rendre compatible une application web avec un serveur CAS. Nous utilisons principalement notre librairie PHP phpCAS (il existe aussi des clients java, un mod_cas pour apache, ...)

pour plus d'informations sur ces librairies et leur utilisation, se reporter à la page des Clients CAS sur le site ja-sig.org.

La version disponible sur Eole2.1 implémente la gestion des tickets de type proxy (voir la documentation du protocole CAS). Cela permet en particulier d'utiliser la librairie PAM pam_cas, et de passer l'authentification à des applications non web.

Plus de détails sur la page d'installation et configuration de ce module.

Rqe : Pour pouvoir utiliser la fonctionnalité de proxy, il faut disposer de certificats signés et du certificat de l'autorité de certification. Nous mettrons prochainement en ligne de la documentation pour proposer une démarche à suivre pour créer ces certificats.

Fonctionnement en mode OpenID

Le serveur EoleSSO gère également le protocole OpenID 2.0. Une extension à été ajoutée au protocole pour gérer la remontée des données utilisateur.

Pour s'authentifier via ce mode, il faut entrer l'url suivante comme founisseur d'identité OpenID:

https://ip_serveur_sso:8443/oid/id/<login> où login est le nom de login de l'utilisateur (champ uid dans ldap).

Une fois l'authentification vérifiée, un formulaire permet à l'utilisateur de sélectionner les informations à renvoyer au serveur. Avant d'arriver sur cette page, les données passent par le système de filtre du serveur sso (voir plus loin). Le filtre est appliqué en fonction de l'adresse à laquelle les données de l'utilisateur seront renvoyées.

Compatibilité SAML2 (en développement)

Dans le cadre du dévelopement de solutions de type portail académique, il a été décidé de rendre le serveur eole-sso compatible avec le protocole SAML 2. Le développement est en cours et quelques scénarios sont déjà fonctionnels

Scénarios gérés

Dans un premier temps, nous avons décidé d'implémenter un nombre limité de bindings et de scénarios.

  • Le serveur fonctionne seulement en tant que fournisseur d'identité (Idp)
  • On veut pouvoir fournir à un fournisseur de service (SP) une assertion SAML 2 permettant de lui indiquer que l'utilisateur est authentifié
    • On utilise pour cela un scénario de type idp initiated (le dialogue débute par l'envoi d'une assertion d'authentification par l'Idp vers le SP)
    • L'envoi de cette requête sera faite à travers les bindings HTTP Post ou Redirect.
  • Dans la mesure du possible, le service devra être capable de gérer le Single Logout
    • Le serveur doit accepter une requête de logout provenant d'un fournisseur de service
    • Il doit pouvoir retrouver toutes les sessions associées pour cette session SSO et leur signifier la fin de la session par l'envoi de requete logout
    • Une fois la déconnexion effectuée, le serveur envoie une réponse de logout au fournisseur de service ayant effectué la demande
  • Le serveur Eole SSO gérant la transmission d'attributs utilisateurs avec une notion de filtres (cf paragraphe correspondant plus bas), nous avons in tégré l'envoi d'attributs utilisateur dans la réponse SAML.
    • Le filtrage des attributs peut se faire soit au niveau de l'url du service à atteindre (cas actuel), soit de façon global pour chaque fournisseur d'identité (à définir).

Remarques concernant la partie Single Logout

Le logout des services peut être effectué sous certaines conditions:

  • le fournisseur de service doit gérer le SingleLogout en binding HTTP-Redirect.
  • si un des sp ne renvoie pas sur le serveur sso après fermeture de sa session, le logout global ne pourra pas continuer.
    • concernant ce problème, le serveur sso conserve une trace des logouts initiés,et il est capable de détecter (et invalider) une session dont le logout ne s'est pas terminé (lors du retour de l'utilisateur avec son cookie).
    • on pourrait aussi ajouter un mécanisme de timeout pour expirer la session coté serveur après un temps donné (à voir).
  • Les sessions crées par le protocole cas ne gèrent pas le single logout, la session sso sera terminée, mais les sessions internes aux applications cassifiée peuvent rester valides (à voir pour les applications openid ?).

tests effectués

SimpleSamlPHP

Pour tester l'implémentation, nous avons utilisé le logiciel SimpleSamlPhp (http://rnd.feide.no/simplesamlphp) configuré comme fournisseur de service.

Dans cette configuration, les bindings utilisés par SimpleSamlPhp sont :

  • réception et traitement des assertions d'authentification via le binding HTTP Post. Les requêtes sont signées à l'aide d'un certificat X509.
  • traitement des réponses de logout par le binding HTTP Redirect. Les requêtes sont signées (idem) et compressées.
  • Les demandes de logout sont envoyées au service SSO par HTTP Redirect (idem).

Dans cette configuration, nous avons pu faire fonctionner l'authentification et le logout, en utilisant l'application dokuwiki comme service derrière SimpleSamlPhp (cf http://rnd.feide.no/content/dokuwiki-simplesamlphp-integration).

Pour l'instant certaines données sont en dur dans l'application, et une partie de la configuration doit être faite à la main (url du service à accéder, description des metadata dans SimpleSaml, ...). Le logout global est fonctionnel au niveau SAML. Des requêtes de logout sont envoyées auprès de tous les participants de la session (connectés en mode SAML).

Il est possible d'accéder à quelques pages sur le serveur de test actuel:

Pour être authentifié, il est possible de se connecter à l'adresse suivante (login/mdp : test/test): https://zephir.ac-dijon.fr:8443/
Vous pouvez ensuite retourner sur les urls de test. (https://zephir.ac-dijon.fr:8443/logout pour se déconnecter).
Dans l'état actuel, il faudra ajouter la ligne suivante dans votre fichier hosts : 194.167.18.29  zephir.ac-dijon.fr

Le serveur hébergant SimpleSamlPhp et dokuWiki n'étant pas accessible de l'extérieur, l'envoi de la requête ne fonctionnera pas.

FIM/RSA

Test SSO avec les applications hébergés en académie (ArenB,BaseElève,Sconet, ...)

Nous avons accès à une maquette utilisant FIM et RSA Cleartrust pour tester la compatibilité du produit.Les tests suivants ont été effectués:

  • L'envoi d'une requête (assertion d'authentification) non signée depuis le serveur eole-sso permet de créer un jeton RSA, et donc d'accéder à des ressources protégées.
  • Des tests ont été effectués avec des requêtes signées (en principe c'est le mode opératoire qui sera retenu au final).

Pour la mise en place d'une association FIM/EoleSSO, voir la page : EoleSSOFim en cours de rédaction : logiciel disponible sur les modules candidats eole-2.2

évolutions envisagées

Les évolutions suivantes sont envisagées suivant les besoins:

  • gestion de scénarios ou une demande d'authentification est fournie par le fournisseur de service (AuthnRequest)
  • gérer de façon plus fine les identifiants utilisateur, avec la possibilité de définir un identifiant alternatif (gestion de NameIDMappingRequest)
  • la signature peut être gérée de différente façon : clé incluse dans la réponse/clé échangée au préalable/signature de toute la réponse ou de l'assertion/...
  • ...

Gestion des données utilisateur

rqe : pour prendre en compte des modifications sur les filtres de données, champs calculés, ... le service eole-sso doit être redémarré.

Données disponibles

Le gestionnaire de sessions permet de récupérer des informations sur l'utilisateur connecté. Actuellement, les données disponibles sont (si disponibles):

  • les données ldap de l'utilisateur (récupérées lors de la phase d'authentification).
  • le rne et le libellé de l'établissement hébergeant le serveur d'authentification.
  • les groupes associés à l'utilisateur (ldap)


Definition de données supplémentaires

Une fonctionnalité a été ajoutée pour permettre de calculer des données supplémentaires à renvoyer.

Le principe est de créer un fichier <nom_champ>.py dans le répertoire /usr/share/sso/user_infos/. Ce fichier doit définir une fonction calc_info:

def calc_info(user_info):
    .....
    return liste_val
  • user_info est le dictionnaire des données existantes (cf paragraphe précédent), il est passé automatiquement à la fonction par le serveur sso
  • liste_val est une liste python contenant les valeurs à associer au champ <nom_champ>.

Par défaut, le champ 'secureid' est généré de cette façon (identifiant unique crypté permettant de préserver l'identité de l'utilisateur (md5 de l'uid + rne + mail)):

# -*- coding: utf-8 -*-

def calc_info(user_infos):
    """calcule secureid : identifiant crypté unique pour chaque utilisateur"""
    from md5 import md5
    # calcul d'un identifiant crypté unique
    user_hash = md5()
    for var in ('uid', 'rne', 'mail'):
        if user_infos.get(var,'') != '':
            user_hash.update(''.join(user_infos[var]))
    return [user_hash.hexdigest()]

Pour que ces données soient envoyées aux applications clientes du sso, il faut les mettre dans un filtre de données (cf paragraphes suivants)

Gestion de filtres de données par application

L'idée est d'implémenter un mécanisme permettant de renvoyer des données différentes sur l'utilisateur en fonction de l'application qui émet la requête.

Le principe retenu pour le moment est le suivant:

deux fichier de configuration définissent une liste d'applications reconnues, et associe un nom de filtre à chacune.

/usr/share/sso/app_filters/applications.ini (géré par Eole) /usr/share/sso/app_filters/local_apps.ini (géré par le responsable du serveur)

Dans le même répertoire, on défini les filtres en créant un fichier pour chaque filtre (<nom_filtre>.ini)

Description d'applications

la description d'une application se fait selon le modèle suivant (exemple avec une application fictive):

[editeurs]             # nom de l'application (indicatif)
port=80                # port de l'application (facultatif)
baseurl=/providers     # url de l'application
scheme=both            # type de protocole : http/https/both
addr=^appserv..*.fr$   # adresse des serveurs autorisés (cf ci dessous)
typeaddr=regexp        # type d'adresse (idem)
filter=providers       # nom du filtre à appliquer

si vous spécifiez un port, le port doit apparaître dans l'url du service désirant s'authentifier. Pour que la définition fonctionne quel que soit le port (ou si le port n'est pas dans l'url), enlevez la ligne concernant le port, ou mettez 'port=' sans numéro.

il y a 2 types de vérification de l'adresse:

  • type ip : l'adresse donnée peut être une adresse ip ou un couple adresse/netmask


Les formats suivant sont possibles

192.168.230.1

192.168.230.0/255.255.255.0

192.168.230.0/24

  • type regexp : l'adresse est donnée comme une expression régulière à comparer à l'adresse dns du client

dans l'exemple : ^appserv..*.fr$ -> correspond à toutes les adresse du type par appser.<qqe_chose>.fr

ces données seront comparée avec l'url associée à la session dans le serveur sso (pour cas, cette url correspond au champ service donné lors de l'obtention d'un ticket d'application).

Rqe : pour vérifier le fonctionnement d'une regexp, lancer un shell python:

>>>import re
>>>regexp = '<votre regexp>'
>>>url = '<une url à comparer avec la regexp>
>>>print re.match(regexp, url) is not None

La dernière ligne doit renvoyer True si l'url correspond à l'expression régulière.

Baseurl correspond au chemin de l'application. Pour vérifier quelle url est reçue, vous pouvez regarder dans /var/log/eole-sso.log. L'url est présente dans les lignes du type : adding session for service  : https://....

Dans l'exemple ci dessus, une url du type http://appserv_test.fr:80/providers sera reconnue, mais pas si elle finit par /providers/toto ou /titi/providers.

Dans les dernières versions, une url du type http://appserv_test.fr:80/providers?mavar=1 sera reconnue, car la partie requête de l'url n'est pas prise en compte.

Spécifique à la version Eole 2.1 : le champ baseurl est comparé au début de l'url (correspond si url commence par baseurl)


Fichiers de filtre

le filtres sont définis sous la forme suivante:

[section1]
libelle=variable
libelle2=variable2
....

[section2]
....
  • section sert à la mise en forme de la réponse (pour cas, un noeud dans le xml retourné lors de la validation d'un ticket)
  • variable correspond à l'identifiant de la donnée utilisateur à récupérer
  • libelle est le nom qui sera utilisé pour présenter cette donnée dans la réponse du serveur

les variables disponibles actuellement sont les champs ldap de l'utilisateur + quelques variables supplémentaires si elles sont disponibles (groupes de l'utilisateur, rne, libelle etablissement)

exemple de filtre pour l'application ci desus (le fichier doit s'apeller providers.ini):

[utilisateur]
user=uid
codeUtil=uidNumber
nom=sn
prenom=givenName
niveau=Meflcf
initialisation=gecos
mail=mail
typeUtil=objectClass
classe=Divcod
groupe=user_groups
discipline=typeadmin

[etablissement]
codeRNE=rne
nomEtab=nom_etab
typeEtab=typeEtab
exemple de données récupérées en mode cas 2
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
  <cas:authenticationSuccess>
    <cas:utilisateur>
      <cas:groupe>eleves</cas:groupe>
      <cas:groupe>4e1</cas:groupe>
      <cas:groupe>4eme</cas:groupe>
      <cas:groupe>domainusers</cas:groupe>
      <cas:nom>NOM</cas:nom>
      <cas:typeutil>posixAccount</cas:typeutil>
      <cas:typeutil>sambaSamAccount</cas:typeutil>
      <cas:typeutil>inetOrgPerson</cas:typeutil>
      <cas:typeutil>Eleves</cas:typeutil>
      <cas:prenom>PRENOM</cas:prenom>
      <cas:niveau>4eme</cas:niveau>
      <cas:classe>4e1</cas:classe>
      <cas:initialisation>0</cas:initialisation>
      <cas:user>PRENOM.NOM</cas:user>
      <cas:mail>PRENOM.NOM@i-etab_test.ac-dijon.fr</cas:mail>
      <cas:codeutil>12021</cas:codeutil>
    </cas:utilisateur>
    <cas:etablissement>
      <cas:coderne>0123456F</cas:coderne>
      <cas:nometab>etab_test</cas:nometab>
    </cas:etablissement>
  </cas:authenticationSuccess>
</cas:serviceResponse>

Redirection depuis Amon

Pour rendre accessible le sso de Scribe depuis Amon, il faut ajouter un reverse proxy.

Par exemple, il faut installer le logiciel libre nginx :

# apt-get install nginx

Puis remplacer le fichier /etc/nginx/sites-available/default par les lignes suivantes (en modifiant le nom de domaine dans les lignes "server_name" et "proxy_pass" avec le nom dns du serveur scribe):

server {
        listen 8443;
        ssl    on;
        ssl_certificate    /etc/ssl/certs/eole.crt;
        ssl_certificate_key     /etc/ssl/certs/eole.key;
        server_name scribe.etab_test.fr;
        access_log  /var/log/nginx/localhost.access-sso.log;
        location / {
                proxy_pass              https://scribe.etab_test.fr:8443/;
                proxy_set_header        Host $host;
                proxy_set_header        X-Real-IP $remote_addr;
                proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header        Destination   $dest;
                set                     $dest  $http_destination;
        }
}

Pour que cela fonctionne, il faut que les différentes applications (sso/applis web) soient configurées avec des noms dns et non des adresses ip.

Dans cet exemple, on redirige tout ce qui arrive sur le port 8443 d'Amon sur le port 8443 de scribe (service eole-sso).

Points à définir / Développements restants

Certains aspects sensibles restent à décider pour la mise en oeuvre:

  • quelles données renvoyer par défaut (applications sans filtre défini) ?
  • que faire des données nominatives ?

Sur un serveur Eole 2.1(2.2), le fichier de configuration (/usr/share/sso/config.py) prend en compte des paramètres définis dans la configuration creole du serveur. Ces variables sont:

  • port_sso : Port utilisé par le service sso
  • dap_sso : Adresse du serveur ldap utilisé par le sso
  • port_ldap_sso : Port du serveur ldap utilisé par le sso
  • base_ldap_sso : Chemin de recherche dans l'annuaire
  • cas_version : Version du protocole cas émulé (1 ou 2)
  • adresse_sso_parent : Adresse du serveur sso parent
  • port_sso_parent : Port du serveur sso parent
  • sso_cert : Chemin du certificat ssl du serveur sso
  • sso_session_timeout : Durée de vie (en secondes) d'une session sur le serveur sso
  • sso_css : css utilisée par défaut pour les formulaires du service sso