Unicité dans un contexte de fédération d'identité

Une fédération d'identité constitue un environnement distribué, avec des participants multiples, au sein duquel il est nécessaire d'assurer l'unicité de certaines valeurs. Dans le cas de SAML, le protocole actuellement utilisé par la communauté Éducation/Recherche, ceci concerne deux éléments:

  • l'identifiant d'entité, le fameux entityID
  • les valeurs de certains attributs spécifiques, comme par exemple eduPersonPrincipalName

Ces deux éléments étant souvent mal compris, et en conséquence mal utilisés, voici quelques éléments d'explication sur leur fonction, leur sémantique, et les moyens utilisés pour garantir leur unicité. En commencant d'abord par exposer d'autres cas similaires à titre de comparaison.

Problématique générale

Il est relativement complexe d'assurer l'unicité d'une valeur dans un contexte distribué, impliquant différents participants.

Le plus simple est d'avoir une autorité centrale qui attribue ces valeurs aux participants. Le MITRE, par exemple, l'a longtemps fait pour les identifiants de vulnérabilité, les fameux CVE. Néanmoins, ce mode de fonctionnement devient rapidement intenable dès que le nombre de participants augmente.

Pour passer à l'échelle, il est généralement mis en place un fonctionnement mixte:

  • une autorité centrale distribue un premier élément unique à chaque participant
  • chaque participant utilise ensuite cet élément pour générer des valeurs uniques

Parmi les exemples connus de ce mode de fonctionnement, on peut citer l'adressage IP, pour lequel des registres régionaux, comme le RIPE en Europe, attribuent des préfixes IP, ou le système DNS, pour lequel des bureaux d'enregistrements attribuents des noms de domaine, qui sont ensuite utilisés comme suffixes pour assurer l'unicité des noms d'hôtes.

Enfin, il existe des modèles sans autorité centrale, comme par exemple les protocoles Zeroconf, qui permettent l'auto-assignation d'adresse IP ou de noms. Ces protocoles consistent généralement à générer dynamiquement une valeur, puis l'annoncer à la cantonade pour vérifier que personne ne l'utilise déjà, quitte à générer une nouvelle valeur si c'est déjà le cas.

Cas d'une fédération d'identité

Les fonctions exactes assurées part l'opérateur d'une fédération d'identité varient, notamment en fonction de la technologie sous-jacente, et du modèle de fonctionnement adopté. Dans le cas d'un univers SAML, le choix a été fait d'utiliser un fonctionnement mixte, comme exposé précédemment, mais en s'appuyant sur un élément externe pré-existant, les enregistrements DNS, pour assurer le rôle de racine d'unicité.

Identifiant d'entité (EntityID)

L'identifiant d'entité, ou EntityID dans le jargon technique, d'après le nom de l'élément correspondant dans les métadonnées, constitue l'identifiant unique d'une entité SAML (fournisseur de service ou fournisseur d'identité) au sein d'une fédération. C'est cet identifiant qui permet au fournisseur d'identité d'identifier le service pour lequel un utilisateur lui demande une authentification, et à un fournisseur de service d'identifier le fournisseur d'identité qui atteste que son visiteur est bien la personne qu'il prétend être. Et c'est cet identifiant qui va permettre à cette entité d'aller chercher au sein des métadonnées qu'il a préalablement téléchargées un certificat lui permettant de vérifier l'identité de son correspondant, l'utilisateur n'étant qu'un intermédiaire entre les deux.

Techniquement, cet identifiant n'est donc qu'une chaine de caractères arbitraires, et toto ferait parfaitement l'affaire. Sauf que dans une fédération rassemblant aujourd'hui plus d'un millier d'entités, il y aurait statistiquement au moins 372 toto différents. Or il ne doit en rester qu'un, et même remplacer toto par Connor McLeod ne suffirait pas à corriger le problème.

Le choix a donc été fait d'utiliser des URIs à la place. Et plus précisément, des URIs de type URL, l'usage d'URIs de type URN, comme par exemple urn:mace:cru.fr:federation:sac n'étant autorisé que pour les entités historiques, afin d'assurer la compatibilité avec les débuts de la fédération. L'identifiant d'une entité est donc une chaine utilisant la syntaxe d'une URL, pour les protocoles HTTP ou HTTPS, mais il s'agit juste d'un identifiant, il n'est absolument pas nécessaire que cette URL corresponde à une requête fonctionnelle, et qu'il y aie un serveur en place pour pouvoir y répondre…

Cette convention de syntaxe n'est cependant pas suffisante à elle seule pour assurer l'unicité: http://toto.tutu/titi est une URI parfaitement valide, mais ne garantit toujours pas l'unicité, il faut donc ajouter une contrainte forte: le domaine DNS utilisé doit être public ET contrôlé par l'organisme contrôlant l'entité SAML. Autrement dit, chaque organisme peut utiliser l'ensemble des domaines qu'il possède comme autant d'espaces de nommage pour ses différentes entités, avec la certitude que personne ne viendra marcher sur ses plate-bandes par inadvertance.

Il n'y a aucune obligation technique de mettre en place les enregistrements DNS correspondant au nom de domaine utilisé: il est tout a fait possible d'utiliser comme identifiant SAML http://toto.renater.fr pour une entité, sans qu'il n'y aie aucun enregistrement DNS toto.tenater.fr, et donc que cet identifiant soit résolvable, puisque ce n'est pas sa fonction. Néanmoins, comme il faudra bien pouvoir adresser des requêtes à cette entité, d'une manière ou d'une autre elle aura forcément au moins un enregistrement DNS associé, par exemple service.renater.fr. A moins d'être masochiste, et vouloir maintenir une cartographie de l'association entre des identifiants SAML arbitraires d'une part, et des enregistrements DNS effectifs d'autre part, le bon sens recommande d'utiliser comme identifiant SAML d'une entité son URL d'accès, qui se résume en général aujourd'hui à un simple nom de domaine, donc https://services.renater.fr dans notre exemple.

La sémantique reste néanmoins celle d'un identifiant, et la comparaison entre ces identifiants est une simple comparaison de chaines, caractère par caractère: https://services.renater.fr ne désigne pas la même entité que https://services.renater.fr/ par exemple, alors que ces URLs sont pourtant fonctionnellement équivalentes.

Et la règle d'usage de cohérence entre identifiant et URL d'accès n'est pas non plus une règle absolue. En cas de migration, typiquement le passage d'un IdP Shibboleth en version 2.x à un IdP shibboleth en version 3.x, il peut être préférable de conserver l'identifiant d'origine, même si l'URL d'accès change. En effet, s'il existe des relations bilatérales directes avec d'autres entités (en dehors d'une fédération d'identité, donc), ou si d'autres entités ont mis en place des politiques de filtrages spécifiques (par exemple, pour autoriser la diffusion de plus d'attributs que ceux autorisés par la politique de filtrage par défaut), toutes ces configurations distantes devront être mises à jour si l'identifiant change. Et il faut aussi mentionner que l'identifiant SAML d'un fournisseur d'identité intervient également dans la génération des valeurs de certains attributs, comme par exemple certains identifiants opaques comme eduPersonTargetedId, constitué d'un triplet (identifiant saml du fournisseur d'identité, identifiant saml du fournisseur de service, valeur de hachage). Même si le stockage de ces identifiants garantit la persistance du dernier élément, le changement du premier fait changer l'ensemble, et risque d'avoir un impact difficile à prédire sur le service cible.

Attributs uniques (scoped)

Ce besoin d'unicité global existe également pour certains attributs, notamment ceux correspondant à des identifiants d'utilisateurs. En utilisant un suffixe propre à chaque organisme, et en le concaténant à une valeur unique au sein de l'organisme, on peut garantir l'unicité au sein de la fédération. Typiquement, on génère un identifiant global, comme l'attribut eduPersonPrincipalName, en combinant un identifiant interne comme l'attribut uid, avec un nom de domaine propre à l'établissement.

Comme dans le cas précédent, c'est un nom de domaine DNS qui est utilisé comme suffixe. Ici aussi, il est utilisé uniquement afin de définir un espace de nommage, et il n'y a aucune nécessité de mettre en place le moindre enregistrement DNS. Et le caractère @ est typiquement utilisé comme séparateur entre la valeur proprement dite et le suffixe, comme par exemple jean.dupont@domaine.fr.

Ce que cette syntaxe rend difficile à comprendre, c'est que les attributs ainsi formés, dit qualifiés (scoped, en anglais), ne sont pas des chaines arbitraires, comme par exemple une adresse email, mais qu'ils sont explicitement typés. L'examen des assertions SAML, telles qu'elles sont transmise au format XML dans la version 1.0 du protocole, montre clairement cette différence:

<Assertion>
  <AttributeStatement>
    <Attribute AttributeName="urn:mace:dir:attribute-def:mail" AttributeNamespace="urn:mace:shibboleth:1.0:attributeNamespace:uri">
      <AttributeValue>jean.dupont@domain.fr</AttributeValue>
    </Attribute>
    ...
    <Attribute AttributeName="urn:mace:dir:attribute-def:eduPersonPrincipalName" AttributeNamespace="urn:mace:shibboleth:1.0:attributeNamespace:uri">
      <AttributeValue Scope="domain.fr">jean.dupont</AttributeValue>
    </Attribute>
  </AttributeStatement>
</Assertion>

Curieusement, cette différence disparait avec SAML 2.0:

<Assertion>
  <AttributeStatement>
    <Attribute FriendlyName="mail" Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
	<AttributeValue>jean.dupont@domaine.fr</AttributeValue>
    </Attribute>
    ...
    <Attribute FriendlyName="eduPersonPrincipalName" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
	<AttributeValue>jean.dupont@domaine.fr</AttributeValue>
    </Attribute>
  </AttributeStatement>
</Assertion>

Dans un cas comme dans l'autre, le fournisseur de service qui reçoit ces assertions est capable d'extraire le suffixe utilisé, et de le comparer avec les valeurs connues pour le fournisseur d'identité qui les génère, telles qu'elles sont déclarées pour celui-ci dans les métadonnées. Et en cas d'incohérence entre les deux, il va rejeter la valeur comme incorrecte.

Par exemple, en imaginant que les métadonnées du fournisseur d'identité générant ces assertions ressemble à l'extrait ci-dessous, il y a bien incohérence entre les valeurs attendues et celles reçues:

<Extensions>
  <Scope regexp="false">domaine.com</Scope>
  <Scope regexp="false">autre-domaine.fr</Scope>
</Extensions>

Le fournisseur de service va donc rejeter l'attribut eduPersonPrincipalName, dont la valeur est invalide, mais accepter l'attribut mail, qui est exempté de ce contrôle, puisqu'il ne s'agit pas d'un attribut qualifié.

Il faut noter cependant que ce comportement n'est pas codé en dur dans le fournisseur de service Shibboleth, il s'agit juste de la politique de filtrage par défaut, définie dans le fichier /etc/shibboleth/attribute-policy.xml, et qu'elle peut donc être configurée.

Dernier détail, la traduction de scoped attribute par attribut qualifié ici est un choix tout à fait arbitraire, par similitude avec la notion de nom de domaine (pleinement) qualifié. Sur le guichet, c'est ce terme qui est également utilisé pour désigner les attributs, et celui de domaine de qualification des attributs pour désigner le suffixe proprement dit. D'autres traductions utilisent le terme de périmètre pour désigner cette même notion. Toute suggestion alternative est la bienvenue :)

Identifiant de fédération

De manière beaucoup plus anecdotique, les fédérations d'identités utilisent également des URIs comme identifiants. Ces identifiants sont par exemples utilisés pour définir des critères d'applications d'une politique de filtrage d'attribut dans la configuration d'un fournisseur d'identité:

<AttributeFilterPolicy id="releaseToAllRenaterSps">
 
  <PolicyRequirementRule
    xsi:type="saml:AttributeRequesterInEntityGroup"
    groupID="https://federation.renater.fr/test/"/> # identifiant de la fédération de test
    ...

Cet identifiant figure dans les métadonnées de la fédération en question:

<EntitiesDescriptor Name="https://federation.renater.fr/test/">
...
</EntitiesDescriptor>

Encore une fois, il s'agit juste d'un identifiant, qui n'est jamais utilisé pour générer une requête HTTP.