Je travaille depuis un certain temps sur la recherche distribuée pour Drupal : j'ai publié une version bêta de l'OpenSearch Aggregator ainsi qu'une version du module OpenSearch feed. L'agrégateur offre une interface conviviale pour configurer un nombre quelconque de sources, et le fil contient l'information sur la pertinence provenant du système de recherche de Drupal. Les résultats sont aussi mis en cache sur l'agrégateur pour des raisons de performance.
Vous trouverez plus d'information sur ces modules dans mes billets de blogue précédents sur OpenSearch.
Le but ultime, toutefois, est de mettre en place une recherche distribuée pour un client de Bryght entre un réseau de sites Drupal sécurisés. Les recherches des utilisateurs connectés doivent inclure le contenu qui leur est visible dans l'ensemble des différents sites Drupal.
OpenID est le choix évident comme mécanisme d'identité pour les utilisateurs, mais il ne nous aide pas directement pour l'authentification. Après quelques recherches, j'ai rédigé un document qui détaille les approches et les solutions possibles. Comme il s'agit ici de technologie de pointe, il m'a semblé préférable de le republier publiquement pour solliciter les commentaires de toute personne intéressée. J'aurais certainement besoin d'autres opinions là-dessus, puisque tout cela est très nouveau pour moi.
Exigences essentielles
L'exigence la plus fondamentale peut se résumer ainsi :
Soit l'agrégateur de recherche (le maître) et un groupe de sites qui renvoient des résultats (les esclaves). Chaque fois qu'une recherche est effectuée par un utilisateur connecté sur le maître, le maître communique avec tous les esclaves et leur transmet l'identité de l'utilisateur qui fait la requête. Cette information doit être univoque et sécurisée. Les esclaves renvoient ensuite au maître des résultats qui respectent les permissions d'accès de l'utilisateur, et le maître agrège les résultats et les met en cache pour la consultation.
Toutes les communications avec les esclaves sont effectuées par le maître. Pour des raisons pratiques et de performance, rien de tout cela ne peut être mis en œuvre du côté du navigateur ou du client (sauf peut-être un simple formulaire ou une redirection pour se connecter une fois).
1. Mise en œuvre naïve
Une mise en œuvre très simple pourrait utiliser l'adresse courriel de chaque utilisateur comme identifiant global d'identité, et l'associer à l'uid Drupal local sur chaque site. La requête pourrait être signée au moyen d'un simple hachage à clé (HMAC) avec une clé secrète partagée, définie sur tous les sites participants (ce qui encode la confiance). Une requête du serveur à un esclave pourrait ressembler à ceci :
GET http://example.com/opensearch/node/keywords?user=name@example.com&hmac=123456789abcdef
Cette approche pose plusieurs problèmes :
- L'utilisateur doit s'inscrire manuellement sur chacun des sites participants avec la même adresse courriel.
- Un secret partagé doit être défini manuellement sur chacun des sites participants.
- Tous les sites participants doivent faire partie d'un réseau fermé entièrement digne de confiance.
2. Améliorations : connexion OpenID
Un premier gain facile consiste à utiliser OpenID, qui est distribué, pour les utilisateurs finaux. Cela évite l'inscription explicite sur chacun des sites participants, puisque le module OpenID de Drupal s'en charge pour nous lors de la première connexion. Cela nous donne aussi un identifiant unique global (l'OpenID de l'utilisateur), vérifié (par DNS), avec lequel faire la correspondance avec les uid numériques locaux.
Cependant, OpenID est distribué par nature et ne nous aide pas directement quand nous voulons que le maître prouve à l'esclave qu'il est autorisé à récupérer du contenu pour un utilisateur donné. La seule entité qui connaît et détient les clés et témoins (cookies) des sites auxquels un utilisateur est connecté est le navigateur de l'utilisateur (User-Agent), tandis que le site d'origine sait seulement quels sites sont autorisés et lesquels ont servi à des connexions dans le passé.
La confiance entre le maître et l'esclave devrait quand même être mise en œuvre par un autre moyen, par exemple, encore une fois, au moyen d'une clé secrète partagée et d'une vérification HMAC :
GET http://example.com/opensearch/node/keywords?user=user.openid.com&hmac=123456789abcdef
Au lieu d'authentifier chaque requête, nous pourrions aussi mettre en œuvre une « porte arrière » authentifiée par laquelle le maître peut forcer des connexions sur les esclaves sans effectuer de véritable authentification OpenID auprès du serveur d'origine. Le résultat serait un témoin de session pour chaque esclave, que le maître pourrait utiliser normalement :
POST http://example.com/trust/?user=user.openid.com&hmac=123456789abcdef
=> Cookie is set
GET http://example.com/opensearch/node/keywords
Cookie: PHPSESSID=123456789abcdef123456789abcdef
Cette connexion par porte arrière serait fournie par un autre module et devrait reposer, par exemple, sur une liste blanche d'hôtes autorisés basée sur le DNS ou les adresses IP, avec SSL en option pour assurer la confidentialité.
Le contrôle d'accès serait respecté par Drupal au moyen du mécanisme de session habituel, et le module client OpenSearch n'aurait pas besoin d'être modifié. Les témoins peuvent être stockés avec le compte d'utilisateur local sur le maître, et les données OpenSearch agrégées seraient mises en cache sur le maître pour chaque utilisateur.
3. Le maître de recherche comme serveur d'origine
Une solution possible consiste à restreindre le serveur d'origine OpenID au maître de recherche.
Cela permettrait au maître de se connecter directement aux esclaves, puisqu'il peut produire tous les jetons cryptographiques nécessaires sans intervention de l'utilisateur. Aucune modification n'est nécessaire aux esclaves. Une fois que le maître s'est connecté une fois à chaque site, il dispose d'un témoin de session valide pour chacun (comme en (2)).
4. Extension de connexion multiple pour OpenID
Une autre solution exigerait une extension à OpenID, à la fois dans le module client pour Drupal et dans le code qui fait tourner le serveur d'origine. Elle permettrait néanmoins au serveur d'origine d'être n'importe quel autre serveur (ou même un ensemble de serveurs), pourvu que le serveur d'origine prenne en charge l'extension personnalisée.
Lorsque l'utilisateur se connecte au maître de recherche, le module saurait qu'il aura besoin d'accéder à chacun des esclaves de recherche. Lorsqu'il envoie une requête au serveur d'origine, il demanderait non seulement une connexion pour lui-même, mais aussi pour chacun des esclaves. L'utilisateur verrait un seul écran de connexion sur le site d'origine (avec une notification adéquate indiquant qu'il s'agit d'une connexion multiple), puis serait renvoyé au maître de recherche.
Le maître de recherche connecte l'utilisateur localement et utilise les jetons cryptographiques de chacun des esclaves pour s'y connecter. Les esclaves peuvent vérifier les jetons de connexion en communiquant directement avec le fournisseur OpenID. Comme en (3), des témoins Drupal normaux sont renvoyés au maître et utilisés pour effectuer les recherches.
5. Autres idées
Parmi les méthodes ci-dessus, seule la (2) est vraiment applicable immédiatement. Utiliser OpenID comme base n'aide pas si l'on a encore besoin d'extensions propriétaires, ou si l'on retire la possibilité de choisir un site d'origine. Et si nous voulons faire la (4) correctement, il faut alors élaborer une véritable spécification qui respecte les principes et la sécurité d'OpenID. Pas une mince tâche.
L'inconvénient de la (2), c'est qu'il n'y a aucune preuve réelle que la connexion a eu lieu, et que nous comptons sur la clé partagée pour assurer la confiance.
Cependant, comme tout le domaine de l'Identité 2.0 est encore en développement, je pense qu'il serait insensé d'essayer de bâtir quelque chose d'élaboré qui mette en œuvre une sorte de modèle utopique de fédération ou de je-ne-sais-quoi. Ce serait une quantité de travail démentielle, et ce ne serait ni pérenne ni même utile aujourd'hui, puisque très peu de services réels le prendraient en charge. Je crois qu'il faut simplement attendre de voir ce qui se développera, une fois qu'OpenID sera plus largement utilisé et que les gens seront plus à l'aise avec ces concepts (ce qui devrait arriver en 2007).
5.1. SAML
SAML a été suggéré comme moyen standardisé de transmettre des assertions de sécurité. Cependant, SAML est en réalité un concurrent d'OpenID et a vu le jour comme moyen d'offrir l'authentification unique entre sites de confiance.
La principale différence, c'est qu'OpenID est lié à un modèle d'échange HTTP particulier, tandis que SAML sépare mieux le message de son mode de transmission. Néanmoins, SAML repose exactement sur les mêmes principes : les assertions doivent donc être générées et signées par le serveur d'origine. Elles restent donc inutiles si nous voulons que le maître prouve de façon sécurisée la connexion d'un OpenID donné. Bien sûr, nous pourrions encapsuler le message du maître à l'esclave dans un message SAML non signé, mais cela irait vraiment à l'encontre de la raison même d'utiliser SAML.
SAML ne gère pas non plus la confiance lui-même. Les esclaves devraient quand même avoir une liste blanche comprenant uniquement le maître, qui serait vérifiée encore une fois par DNS/IP ou par SSL.
5.2. Jeton de preuve de connexion OpenID
La grande lacune fonctionnelle d'OpenID, c'est que le seul qui puisse vérifier les jetons cryptographiques d'une connexion est le destinataire prévu (la partie de confiance). Rien dans les spécifications ne permet de « transférer » l'assertion du serveur d'origine selon laquelle l'utilisateur s'est connecté à la partie de confiance d'une façon qui rende cette information vérifiable par tous.
Une extension à OpenID pour cela serait utile. C'est essentiellement une variante de l'idée de connexion multiple où, au lieu de demander à l'utilisateur de se connecter à l'ensemble des sites, l'utilisateur se connecte seulement au maître, et le maître envoie la preuve de cette connexion aux esclaves :
POST http://example.com/trust/?user=user.openid.com&....crypto-here....
=> Cookie is set
Les esclaves auraient encore une fois une liste blanche basée sur IP/DNS ou SSL. La principale différence avec la (4), c'est qu'ici, la confiance est encore une fois établie par l'administrateur plutôt que donnée explicitement par l'utilisateur au moyen d'OpenID.
Conclusion
La (4) semble être la solution la plus propre, puisqu'elle ne repose pas sur une liste blanche explicite pour établir la confiance. L'utilisateur n'a qu'à vérifier que le maître ne demande pas une connexion à un site externe. Cependant, elle exigerait une extension au processus de connexion OpenID sur le serveur d'origine, y compris à l'interface (puisque les connexions supplémentaires doivent être communiquées d'une façon ou d'une autre), il est donc peu probable qu'elle soit mise en œuvre facilement.
La (5.2) semble meilleure sous cet angle, puisque seuls des changements sous le capot sont nécessaires. Le serveur d'origine enverrait simplement des jetons cryptographiques supplémentaires qui pourraient être transmis à d'autres parties. Selon les enjeux de sécurité, ces jetons pourraient être demandés de façon générale (en quantité) ou pour des destinataires précis (pour chaque esclave).
Les deux solutions exigent un travail sérieux de cryptographie et de spécification pour être bien faites.
(publié aussi sur acko.net)