Activation de SASL pour OpenLDAP


Retour à la page principale d'accueil SASL


L’objectif de cette page web est de présenter très succinctement ce qu’il faut faire pour pouvoir mettre en œuvre une authentification de type SASL avec un annuaire LDAP (OpenLDAP).

Pour tout commentaire, louange, insulte ou demande d'information complémentaire : Pour m'écrire, cliquez ici.

Cette page a été modifiée le 4 décembre 2006.


Les tests ont été réalisés avec :



Les clients qui ont été utilisés sont :



Autrement dit, je n'ai pas testé avec d'autres outils, je n'ai pas mis en production, je ne cherche pas à mettre en production, mon seul et unique objectif est de donner quelques pointeurs. Pour le reste, c'est à vous de bosser. Bon courage et bonne chance !



Contrôle de l'authentification SASL

L'activation de SASL est contrôlée grâce au fichier de configuration slapd.conf, côté serveur, et par le fichier ldaprc (ou ldap.conf), côté client.

Seule la configuration du côté serveur semble nécessaire, ou du moins intéressante. Celle du côté client semble moins fondamentale. Si je m'aperçois ultérieurement qu'il faut se pencher sur le problème, je rédigerai à ce moment quelque chose là-dessus. Pour l'instant, avec les tests que j'ai effectués, je n'ai pas eu besoin de trop regarder la partie cliente.

Cette page contient les points principaux suivants :




Configuration du serveur (donc, de slapd.conf)

Plusieurs choses sont évidemment possibles ; il suffit de lire la documentation pour s'en apercevoir. Toutefois, les deux éléments de configuration les plus « importants » sont traitées ci-dessous.

Un point important qui est indépendant du mécanisme SASL utilisé est la conversion des identités fournies par le sous-système SASL en identités habituellement manipulées par le serveur LDAP. Cette conversion est optionnelle bien sûr, mais vous vous apercevrez très vite que dans certains cas, c'est pratiquement obligatoire.

Conversion identité SASL -> identité LDAP

La seule clause utile est celle qui permet de transformer une identité SASL en un identité LDAP. Autrement dit, c'est la clause authz-regexp qui convient de regarder.

Voici la description (en anglais ; je n'ai pas envie de traduire.) de la clause auth-regexp, tirée de la doc en ligne de OpenLDAP 2.3.xx :

       authz-regexp <match> <replace>
              Used  by  the  authentication  framework  to convert simple user
              names, such as provided by SASL subsystem, to an  LDAP  DN  used
              for authorization purposes.  Note that the resultant DN need not
              refer to an existing entry to  be  considered  valid.   When  an
              authorization  request  is received from the SASL subsystem, the
              SASL USERNAME, REALM, and MECHANISM are taken,  when  available,
              and combined into a name of the form

                     UID=<username>[[,CN=<realm>],CN=<mechanism>],CN=auth

              This   name   is   then   compared   against   the  match  POSIX
              (''extended'')  regular  expression,  and  if   the   match   is
              successful,  the  name  is replaced with the replace string.  If
              there are wildcard strings in the match regular expression  that
              are enclosed in parenthesis, e.g.

                     UID=([^,]*),CN=.*

              then  the  portion of the name that matched the wildcard will be
              stored in the numbered placeholder variable  $1.  If  there  are
              other wildcard strings in parenthesis, the matching strings will
              be in $2, $3, etc. up to $9. The placeholders can then  be  used
              in the replace string, e.g.

                     UID=$1,OU=Accounts,DC=example,DC=com

              The  replaced name can be either a DN, i.e. a string prefixed by
              "dn:", or an LDAP URI.  If the latter, the server will  use  the
              URI  to  search  its  own database(s) and, if the search returns
              exactly one entry, the name is replaced by the DN of that entry.
              The  LDAP  URI  must  have  no  hostport,  attrs,  or extensions
              components, but the filter is mandatory, e.g.

                     ldap:///OU=Accounts,DC=example,DC=com??one?(UID=$1)

              The protocol portion of the URI must be strictly ldap.

              Multiple authz-regexp options can be given in the  configuration
              file  to  allow  for multiple matching and replacement patterns.
              The matching patterns are checked in the order  they  appear  in
              the file, stopping at the first successful match.

On voit donc qu'un premier travail est fait automatiquement par le sous-système SASL. Si par exemple un utilisateur « toto » désire s'authentifier via SASL, le DN qui lui sera associé sera de la forme suivante :

uid=toto,cn=realm,cn=mech,cn=auth

Ce qui donne le plus souvent, dans le cas où le serveur se trouve dans le même domaine que l'utilisateur et en supposant que le mécanisme choisi soit GSSAPI (Kerberos) :

uid=toto,cn=gssapi,cn=auth



Si le mécanisme d'authentification choisi avait été DIGEST-MD5, le DN généré aurait été de la forme :

uid=toto,cn=digest-md5,cn=auth



Il reste maintenant à convertir les DN « SASL » en DN « LDAP ». Voici un extrait de ce que j'ai écrit pour un de mes serveurs :

authz-regexp
        uid=ldapadmin,cn=gssapi,cn=auth
        cn=Manager,o=INT,c=FR

authz-regexp
        uid=(.*),cn=gssapi,cn=auth
        ldap:///o=INT,c=FR??sub?(&(uid=$1)(objectClass=inetOrgPerson))



La première clause explique que l'utilisateur SASL « ldapadmin » est transformé en l'utilisateur LDAP « cn=Manager,o=INT,c=FR »



La deuxième clause est plus complexe. Elle consiste à transformer une identité de la forme uid=*,cn=gssapi,cn=auth en un DN obtenu à partir de la base de données LDAP. Ce DN est le résultat d'une recherche d'une entrée ayant la classe inetOrgPerson et l'attribut uid égal à la valeur de l'uid dans l'identité SASL.

Si aucune entrée ne répond au critère, l'identité SASL ne sera pas convertie et on conservera comme DN d'authentification celui obtenu automatiquement ; c'est-à-dire de la forme uid=xyz,cn=realm,cn=mech,cn=auth

Pour une sécurité plus grande, il conviendra ensuite d'écrire correctement les listes de contrôle d'accès (ACL) en tenant compte des remarques ci-dessus.



Contrôle des mécanismes de proxy

Veuillez directement regarder au chapitre correspondant et plus spécifiquement à cet endroit.


Essai avec GSSAPI

On suppose qu'un serveur Kerberos tourne (voir démarrage d'un serveur Kerberos).

On suppose également qu'il existe au moins une identité principale. Appelons-la neuman@ROYAUME

On suppose que l'on a obtenu un ticket pour cette identité principale (voir ici pour un exemple d'obtention de ticket)



Côté serveur

Il est apparemment nécessaire de lancer le serveur en étant root. Lorsque j'ai malencontreusement lancé mon serveur LDAP en étant un utilisateur lambda, le serveur s'est franchement bien planté dès la tentative de contrôle des identités Kerberos (enfin, je suppose que c'est aux environs de ce moment-là). Le plantage s'est produit alors que le serveur LDAP et le serveur Kerberos tournaient tous les deux sur la même machine sous Solaris 8. J'ai pas testé sous Linux. Faudra que je le fasse. Plus tard.



Côté client

Rien de particulier à noter. Les tests ont été faits avec les commandes ldapsearch, ldapadd, etc.

Pour tester avec d'autres clients, je vous souhaite bon amusement et bon courage.



Soit la commande :

ldapsearch -b "" -s base -H ldaps/machine/ -Y GSSAPI +

Cette commande permet l'accès à l'entrée RootDSE du serveur LDAP tournant sur "machine", via une connexion TLS, et en activant le mécanisme GSSAPI pour l'identité principale dont on possède un ticket.

La commande suivante fonctionne également :

ldapsearch -b "" -s base -H ldaps/machine/ +

Il n'est pas toujours nécessaire en effet de spécifier le mécanisme SASL à activer. Le client ldapsearch et le serveur LDAP se mettent d'accord auparavant pour sélectionner ce mécanisme. Le client ldapsearch (ldapadd, ldapmodifiy....) demande au serveur quels sont les mécanismes SASL qu'il est capable de gérer. Par défaut, si GSSAPI est présent, et qu'il est le premier dans la liste des mécanismes proposés, c'est ce mécanisme qui est sélectionné.



Note :

Vous n'êtes pas contraint de vous connecter au serveur LDAP en utilisant une connexion TLS. Une connexion normale fonctionne également. La connexion sera chiffrée par SASL si la connexion n'utilise pas TLS. Je ne sais pas s'il y a double chiffrement (SASL + TLS). Si quelqu'un peut me le dire, ce sera un plaisir.




Essai avec DIGEST-MD5

Voici un extrait (toujours en anglais ; non traduit pour des raisons de fatigue) de la documentation d'OpenLDAP :

This section describes the use of the SASL DIGEST-MD5 mechanism using secrets stored either in the directory itself or in Cyrus SASL's own database. DIGEST-MD5 relies on the client and the server sharing a "secret", usually a password. The server generates a challenge and the client a response proving that it knows the shared secret. This is much more secure than simply sending the secret over the wire.
Cyrus SASL supports several shared-secret mechanisms. To do this, it needs access to the plaintext password (unlike mechanisms which pass plaintext passwords over the wire, where the server can store a hashed version of the password).
Secret passwords are normally stored in Cyrus SASL's own sasldb database, but if OpenLDAP Software has been compiled with Cyrus SASL 2.1 it is possible to store the secrets in the LDAP database itself. With Cyrus SASL 1.5, secrets may only be stored in the sasldb. In either case it is very important to apply file access controls and LDAP access controls to prevent exposure of the passwords.



On voit que cette méthode nécessite l'utilisation de mots de passe qui peuvent être enregistrés indifféremment :

  1. dans la base LDAP ou bien,

  2. dans la base SASL (/etc/sasldb2). Cette base doit être « visible » par le serveur LDAP. Cela veut dire qu'elle doit résider sur la même machine où tourne le serveur.

Si les mots de passe sont conservés dans la base LDAP, il est nécessaire que ceux-ci soit enregistrés selon le format « plaintext ». Par conséquent, il est nécessaire de spécifier dans le fichier de configuration slapd.conf la clause suivante :

password-hash     {CLEARTEXT}



Prenons maintenant un exemple.

Soit l'utilisateur Alfred E. Neuman qui possède une entrée dans la base LDAP dont une partie du descriptif LDIF est le suivant :

dn: cn=Alfred E. Neuman,ou=mad,o=magazine,c=US
objectClass: inetOrgPerson
cn: Alfred E. Neuman
sn: Neuman
uid: alf
userPassword: neuneu

Pour s'authentifier auprès du serveur LDAP fonctionnant sur "machine", il lui suffira de taper la commande suivante :

ldapsearch -Y DIGEST-MD5 -U alf -H ldaps://machine .......

Il lui sera demandé son mot de passe (qui est, rappelons-le « neuneu ») ; l'authentification sera effectuée et la requête sera satisfaite.



Si par contre Alfred est également enregistré dans la base /etc/sasldb2, c'est le mot de passe qui est dans cette base qui sera utilisé pour générer le « challenge » proposé par le serveur.

On peut mélanger les deux méthodes : certains utilisateurs seront enregistrés dans le base SASL (/etc/sasldb2) et d'autres dans la base LDAP. Le serveur commencera toujours par regarder la base SASL. Ce qui signifie que si un utilisateur a la mauvaise idée de sauvegarder un mot de passe dans la base /etc/sasldb2 ET un mot de passe dans l'annuaire, seul le mot de passe stocké dans la base SASL sera utilisé. Le mot de passe de la base LDAP ne sera jamais utilisé.

Ne pas oublier de noter que les règles authz-regexp deviennent par exemple :

authz-regexp
        uid=ldapadmin,cn=digest-md5,cn=auth
        cn=Manager,o=INT,c=FR

Note:

La connexion LDAP est également chiffrée après la mise en œuvre de l'authentification DIGEST-MD5. La couche TLS n'est donc pas absolument nécessaire.


Essai avec CRAM-MD5

La différence fondamentale entre DIGEST et CRAM semble être que seule la base /etc/sasldb2 est consultée. Les mots de passe qui sont stockés dans la base LDAP n'interviennent pas. La documentation que j'ai pu trouver semble le confirmer. De toutes façons, il est maintenant fortement déconseillé d'utiliser ce mécanisme.




Essai avec EXTERNAL

Cette méthode très particulière utilise en réalité le protocole TLS/SSL. Il faut donc pouvoir mettre en œuvre une connexion de ce type (regardez ici pour mettre en œuvre une connexion TLS/SSL).

Par contre, il est absolument nécessaire que le client possède un certificat. Et il est obligatoire que le serveur demande un certificat au client.

Et dans le cas de ldapsearch, ldapmodify, etc., il faut donc explicitement utiliser un fichier de configuration (ldaprc ou ldap.conf).

En effet, c'est le DN du certificat qui va être utilisé pour former l'identité SASL. Ensuite, cette identité SASL sera transformée (si possible) en une identité LDAP. Voir ici pour la transformation d'identité.

Par exemple, voici une règle de transformation possible :

authz-regexp
        "emailAddress=Michel.gardie@int-evry.fr,cn=Michel Gardie,ou=LOR, o=INT, l=Evry, st=Essonne, c=FR"
        "cn=Michel Gardie,ou=LOR,o=INT,c=FR"



Voici ce que donne la commande suivante :

ldapsearch -H ldaps://bonemine.int-evry.fr/ -b "" -s base +

en supposant que le client ait émis un certificat valide :

dn:
structuralObjectClass: OpenLDAProotDSE
namingContexts: o=INT,c=FR
monitorContext: cn=Monitor
supportedControl: 2.16.840.1.113730.3.4.18
supportedControl: 2.16.840.1.113730.3.4.2
supportedControl: 1.3.6.1.4.1.4203.1.10.1
supportedControl: 1.2.840.113556.1.4.1413
supportedControl: 1.2.840.113556.1.4.1339
supportedControl: 1.2.840.113556.1.4.319
supportedControl: 1.2.826.0.1.334810.2.3
supportedExtension: 1.3.6.1.4.1.1466.20037
supportedExtension: 1.3.6.1.4.1.4203.1.11.1
supportedExtension: 1.3.6.1.4.1.4203.1.11.3
supportedFeatures: 1.3.6.1.4.1.4203.1.5.1
supportedFeatures: 1.3.6.1.4.1.4203.1.5.2
supportedFeatures: 1.3.6.1.4.1.4203.1.5.3
supportedFeatures: 1.3.6.1.4.1.4203.1.5.4
supportedFeatures: 1.3.6.1.4.1.4203.1.5.5
supportedLDAPVersion: 2
supportedLDAPVersion: 3
supportedSASLMechanisms: LOGIN
supportedSASLMechanisms: PLAIN
supportedSASLMechanisms: GSSAPI
supportedSASLMechanisms: SRP
supportedSASLMechanisms: OTP
supportedSASLMechanisms: DIGEST-MD5
supportedSASLMechanisms: CRAM-MD5
supportedSASLMechanisms: EXTERNAL
vendorName: Main server. OpenLDAP 2.2.27 under Solaris 8.
subschemaSubentry: cn=Subschema



Test avec JXplorer

Un autre outil a été utilisé pour tester le mécanisme SASL EXTERNAL: JXplorer.

Cet outil a besoin de connaître un certain nombre de certificats pour mettre en œuvre le mécanisme d'authentification EXTERNAL.

Il lui faut connaître les certificats des différentes autorités de certification nécessaires pour valider le certificat serveur. Les certificats des autorités peuvent être indifféremment au format PEM ou au format DER.

Il lui faut également connaître le certificat utilisateur qui sera utilisé, et il lui faut pouvoir accéder à la clé privée associée à ce certificat. Le certificat peut être au format PEM ou DER.

Par contre, la clé privée doit être au format pkcs8 non chiffré !!!! Je sais, c'est glauque, mais c'est comme ça !!!!

Cela a donc nécessité l'opération suivante sur la clé privée :

openssl pkcs8 -topk8 -in cle_privee.pem -inform PEM -out cle_privee.pkcs8 -nocrypt

Lisez la documentation de l'outil pour le reste des opérations. En cas de problèmes, « drop me a mail »!!!






Autres mécanismes

Je n'ai pas encore vraiment testé. J'ai regardé PLAIN et LOGIN. Je n'ai fait que regarder. Les méthodes PLAIN et LOGIN nécessitent la mise en œuvre de TLS. Ces mécanismes sont considérés comme obsolètes. Je n'ai pas regardé le mécanisme SASL ANONYMOUS.

En outre, le RFC 2829 (Authentication for LDAP) considère que les mécanismes SASL ANONYMOUS et PLAIN ne doivent pas être utilisés avec LDAP.



Les méthodes SRP et OTP n'ont pas été testées non plus. La seule chose que je peux dire est qu'elles semblent ne pas nécessiter TLS pour fonctionner.






Proxy SASL

J'ai essayé avec un succès relatif (mais non sans mal) à mettre en œuvre une authentification SASL de type proxy en utilisant l'outil d'OpenLDAP.

Principe (en anglais)

(Tirée de la documentation d'OpenLDAP)

The SASL offers a feature known as proxy authorization, which allows an authenticated user to request that they act on the behalf of another user. This step occurs after the user has obtained an authentication DN, and involves sending an authorization identity to the server. The server will then make a decision on whether or not to allow the authorization to occur. If it is allowed, the user's LDAP connection is switched to have a binding DN derived from the authorization identity, and the LDAP session proceeds with the access of the new authorization DN.
The decision to allow an authorization to proceed depends on the rules and policies of the site where LDAP is running, and thus cannot be made by SASL alone. The SASL library leaves it up to the server to make the decision. The LDAP administrator sets the guidelines of who can authorize to what identity by adding information into the LDAP database entries. By default, the authorization features are disabled, and must be explicitly configured by the LDAP administrator before use.
This sort of service is useful when one entity needs to act on the behalf of many other users. For example, users may be directed to a web page to make changes to their personal information in their LDAP entry. The users authenticate to the web server to establish their identity, but the web server CGI cannot authenticate to the LDAP server as that user to make changes for them. Instead, the web server authenticates itself to the LDAP server as a service identity, say,
cn=WebUpdate,dc=example,dc=com
and then it will SASL authorize to the DN of the user. Once so authorized, the CGI makes changes to the LDAP entry of the user, and as far as the slapd server can tell for its ACLs, it is the user themself on the other end of the connection. The user could have connected to the LDAP server directly and authenticated as themself, but that would require the user to have more knowledge of LDAP clients, knowledge which the web page provides in an easier format.
Proxy authorization can also be used to limit access to an account that has greater access to the database. Such an account, perhaps even the root DN specified in slapd.conf(5), can have a strict list of people who can authorize to that DN. Changes to the LDAP database could then be only allowed by that DN, and in order to become that DN, users must first authenticate as one of the persons on the list. This allows for better auditing of who made changes to the LDAP database. If people were allowed to authenticate directly to the priviliged account, possibly through the rootpw slapd.conf(5) directive or through a userPassword attribute, then auditing becomes more difficult.
Note that after a successful proxy authorization, the original authentication DN of the LDAP connection is overwritten by the new DN from the authorization request. If a service program is able to authenticate itself as its own authentication DN and then authorize to other DN's, and it is planning on switching to several different identities during one LDAP session, it will need to authenticate itself each time before authorizing to another DN (or use a different proxy authorization mechanism). The slapd server does not keep record of the service program's ability to switch to other DN's. On authentication mechanisms like Kerberos this will not require multiple connections being made to the Kerberos server, since the user's TGT and "ldap" session key are valid for multiple uses for the several hours of the ticket lifetime.



J'ai essayé de rejouer le scénario proposé dans la même documentation. Un utilisateur (par ex. : neuman) va s'authentifier via un client (par ex. : clientweb). Toutefois, si le client clientweb possède un mot de passe (ou un ticket Kerberos, ou un certificat) valide, il est tout à fait envisageable que l'utilisateur neuman ne possède ni ticket ni mot de passe. En réalité, c'est le mot de passe (ou le ticket) du client clientweb qui valide le premier niveau d'authentification. Ensuite, le serveur LDAP accordera (ou non) l'autorisation à l'utilisateur neuman.

Problème des identités

Dans le cas d'une autorisation de type proxy, deux identités vont être utilisées et générées :

  1. L'identité d'authentification

    C'est avec cette identité que l'on commence. Le DN généré va dépendre du mécanisme utilisé. Il est généralement de la forme : uid=nom,cn=mécanisme,cn=auth si le mécanisme n'est pas « external ». Sinon, le DN est celui du sujet (subject) du certificat client dans le cas du mécanisme externe;

    Ce DN est ensuite transformé par des règles authz-regex. Si aucune règle ne s'applique, le DN n'est évidemment pas transformé.

  2. L'identité d'autorisation

    C'est cette identité qui prend la place (si possible) de l'identité d'authentification. Le DN généré est sous une des formes suivantes :

    Le mécanisme est celui utilisé pour l'identité d'authentification (gssapi, digest-md5, external, cram-md5....). Comme dans le cas de l'identité d'authentification, on fait passer l'identité d'autorisation à la moulinette des authz-regexp.



Une fois les deux identités générées et transformées, le serveur va tenter d'appliquer des règles d'autorisation (authzFrom, authzTo). Le point suivant explique plus ou moins bien tout cela.



Mise en œuvre

Afin d'autoriser l'utilisateur neuman à pouvoir prendre la place du client initial, il convient de rajouter un attribut spécial dans l'entrée de neuman. L'attribut que j'ai choisi est authzFrom. Voir la documentation pour plus d'informations sur les attributs authzTo et authzFrom.



J'ai donc créé quelque chose comme cela dans l'entrée dn: cn=Alfred E. Neuman,o=INT,c=FR :

authzFrom: dn: cn=ClientWeb,ou=LOR,o=INT,c=FR



Ceci étant fait, j'ai modifié le fichier de configuration slapd.conf et j'ai rajouté la clause suivante :

authz-policy from



Cette clause permet au serveur de lire les règles définies dans les attributs authzFrom des entrées de la base LDAP.

Tests

J'ai commencé par créer une entrée particulière dans ma nase LDAP. Je l'ai appelée cn=ClientWeb,ou=LOR,o=INT,c=FR. Elle est décrite de la manière suivante :

dn:  cn=ClientWeb,ou=LOR,o=INT,c=FR
objectClass: ApplicationProcess
cn:  ClientWeb
description: Client web fictif pour tester la fonctionnalité proxy de SASL
ou: LOR
l: Evry



Ensuite, j'ai testé le tout grâce au mécanisme GSSAPI parce que c'était plus rapide.

J'ai donc obtenu un ticket pour le client clientweb (qui est associé au DN cn=ClientWeb,ou=LOR,o=INT,c=FR) :

kinit clientweb

Puis, j'ai tapé la commande ldapsearch suivante (abrégée) :

ldapsearch -b "" -s base -X u:neuman

Cette commande possède l'argument -X u:neuman pour indiquer au serveur LDAP que c'est « neuman » qui devra remplacer « clientweb ». L'utilisateur « clientweb » est authentifié grâce à son ticket Kerberos.



Si la méthode utilisée avait été DIGEST-MD5, j'aurai tapé :

ldapsearch -b "" -s base -X u:neuman -U clientweb -Y DIGEST-MD5

Où l'on voit bien la différence entre l'utilisateur neuman et l'utilisateur clientweb.

Le mot de passe tapé dans ce dernier cas est bien évidemment celui de « clientweb ».