Uubu.org

Systèmes d'information: une approche OpenSource

OpenLDAP - Contrôle d'accès

Implémentation du contrôle d'accès dans OpenLDAP

Le 25 septembre 2014 , Aucun commentaire pour cet article


Le contrôle d'accès d'OpenLDAP est très riche et très complexe. Je vais donner ici quelques conseils afin de réussir au mieux le design.


1. Documenter: Il est absolument impératif de documenter le contrôle d'accès mis en place, et doit être fait en 2 parties, une fonctionnelle, qui est un résumé de ce que l'on a définis, et une partie plus technique.
2. Validation: Une simple modification peut influencer tout le contrôle d'accès. En conséquence, une série de tests doivent être mis en place et jouées de manière régulière afin de s'assurer que les règles définies jouent bien leur rôle.
3. ACL vs ACI: Les ACI sont à première vu plus intéressantes à utiliser. Il faut cependant garder en tête que les ACI sont encore considérés comme expérimentales et sont moins évoluées que les acl, qu'on ne peut pas les utiliser pour le contrôle d'accès à la configuration du serveur, et que peu de backends les supportent. Une autre différence notable: les aci sont lues et traitées ensemble, les acl s'arrêtent au premier match.
4. Gestion de la structure: N'oublions pas que les règles de structures ne sont pas prises en charge par OpenLDAP, et par conséquent doivent être définies par acl.

aci et acl partagent les même syntaxes de permissions, donc attention, la permission "d" ne signifie pas delete! L'approche entre aci et acl est différente. Là ou une acl équivaux à une liste par cible, l'aci traite une liste pas source. On notera qu'OpenLDAP ne gére pas les droits tout à fait conformément au draft acl-model, mais ils sont suffisants pour définir des contrôles d'accès efficaces, et cela les simplifie!

Délégations

La balance entre simplicité et souplesse est difficile à trouver, la catégorisation des éléments est donc importante. Je vais décrire un modèle RBAC de base, et nous allons nous intéresser à cn=config.

Cet objet contient de nombreux attributs liés au serveur. Nous pouvons catégoriser la configuration comme ceci:
- Gestion du cache
- Gestion des connexions concurrentes
- Les requis/restrictions
- Gestion des threads
- Gestion des limites
- Comportement de l'indexation
- Gestion des logs
- L'id de réplication
- Gestion des certificats
- Gestion SASL
- Traitements authz

Plutôt que de créer autant de rôles, nous allons nous limiter à 3:
  - La Sécurité gérera les certificats et tout ce qui est en lien avec l'authentification
  - L'indexation gérera les paramètres d'indexation, et plus généralement l'optimisation des performances.
  - Les administrateurs géreront tous les autres attributs en lien avec le service.

On rajoutera un rôle d'audit, qui autorisera à lire tous les attributs.

Nous pouvons commencer par créer nos objets:

dc=uubu,dc=fr
\_ cn=Delegations
  \_Administration
    \_Admin-Groups
      \_ADM-ldap
  \_Applications
    \_ldap
      \_ldap-cfg-Auditors
      \_ldap-cfg-Security-Mgr
      \_ldap-cfg-Index-Mgr
      \_ldap-cfg-Service-Mgr

Maintenant que nos rôles sont définis, ajoutons nos acl. Notre groupe Admin-Managers ne gère pas directement les rôles, mais délègue la gestion aux membres de ADM-ldap, dont ils peuvent gérer les membres:
olcAccess: {1000}to dn.baseobject="cn=Admin-Groups,cn=Administration,cn=Delegations,dc=uubu,dc=fr" attrs="children" by set="[cn=Admin-Managers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" write by set="[cn=Admin-Approvers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" read by * none
olcAccess: {1050}to dn.onelevel="cn=Admin-Groups,cn=Administration,cn=Delegations,dc=uubu,dc=fr" filter="(objectClass=groupOfMembers)" attrs="entry,objectClass,@groupOfMembers,cn" by set="[cn=
Admin-Managers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" write by set="[cn=Admin-Approvers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" read by * none

On en profite pour inclure notre règle de structure. Ici j'utilise les groupOfMembers, c'est un choix personnel et il est plutôt conseillé d'utiliser groupOfNames pour rester en phase avec les rfc. Ensuite, je gère les droits sur l'application "ldap":
olcAccess: {2000}to dn.baseobject="cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr" filter="(objectClass=groupOfMembers)" attrs="children" by set="[cn=Admin-Managers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" write stop by group/groupOfMembers/member.exact="cn=ADM-ldap,cn=Admin-Groups,cn=Administration,cn=Delegations,dc=uubu,dc=fr" read by * none
olcAccess: {2050}to dn.onelevel="cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr" filter="(objectClass=groupOfMembers)" attrs="entry,objectClass,cn" by set="[cn=Admin-Approvers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" read by set="[cn=Admin-Managers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" write by group/groupOfMembers/member.exact="cn=ADM-ldap,cn=Admin-Groups,cn=Administration,cn=Delegations,dc=uubu,dc=fr" read by * none

Ici ce sont les Delegations-Managers qui sont habilités à modifier les rôles, nous bloquons donc la possibilité à un ADM-ldap de modifier la structure des rôles, même s'il peuvent gérer les membres:
olcAccess: {2100}to dn.onelevel="cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr" filter="(objectClass=groupOfMembers)" attrs="member" value.regex="^cn=ldap-[^,]+,cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr$" by set="[cn=Admin-Approvers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" read by set="[cn=Admin-Managers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" write by group/groupOfMembers/member.exact="cn=ADM-ldap,cn=Admin-Groups,cn=Administration,cn=Delegations,dc=uubu,dc=fr" read by * none
olcAccess: {2150}to dn.onelevel="cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr" filter="(objectClass=groupOfMembers)" attrs="member" value.regex="^cn=[^,]+,ou=Peoples,(.+),dc=uubu,dc=fr$" by set="[cn=Admin-Approvers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" read by set="[cn=Admin-Managers,cn=Administration,cn=Delegations,dc=uubu,dc=fr]/member & user" read by group/groupOfMembers/member.exact="cn=ADM-ldap,cn=Admin-Groups,cn=Administration,cn=Delegations,dc=uubu,dc=fr" write by * none

cn=config

Les accès sur cet objets sont à gérer dans olcDatabase={0}config,cn=config. Un petit aperçus (je ne met pas tous les attributs):

olcAccess: {1000}to dn.base="cn=config" attrs="entry,cn,objectClass" by users =rscd by * none
olcAccess: {1050}to dn.base="cn=config" attrs="olcRootDSE,olcReverseLookup,olcReferral,olcRestrict,olcRequires,olcReadOnly,olcDisallows,olcAttributeOptions,olcAllows" by set="[cn=ldap-cfg-Service-Mgr,cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user" =rscdw by set="[cn=ldap-cfg-Auditors,cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user" =rscd
olcAccess: {1200}to dn.base="cn=config" attrs="olcTLSCACertificateFile,olcTLSCACertificatePath,olcTLSCertificateFile,olcTLSCertificateKeyFile,olcTLSCipherSuite,olcTLSCRLCheck,olcTLSRandFile,olcTLSVerifyClient,olcTLSDHParamFile,olcTLSCRLFile,olcTLSProtocolMin,olcLocalSSF" by set="[cn=ldap-cfg-Security-Mgr,cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user" =rscdw by set="[cn=ldap-cfg-Auditors,cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user" =rscd
olcAccess: {1250}to dn.base="cn=config" attrs="olcIndexIntLen,olcIndexSubstrAnyStep,olcIndexSubstrAnyLen,olcIndexSubstrIfMinLen,olcIndexSubstrIfMaxLen" by set="[cn=ldap-cfg-Index-Mgr,cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user" =rscdw by set="[cn=TR-DSA-Auditors,cn=DSA,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user" =rscd
olcAccess: {1300}to dn.base="cn=config" attrs="entry,@olcGlobal,@olcConfig" by set="[cn=ldap-cfg-Auditors,cn=ldap,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user" =rscd by * none

Voilà, il ne nous reste plus qu'à répéter l'opération sur chaque élément qui constitue la configuration du serveur, frontend, olcDatabases, Monitor et AccessLog si vous utilisez ces bases, Modules, Backends, et Overlays. Ensuite, il ne reste plus qu'à définir des rôles fonctionnels pour une gestion plus souple.

o=Enterprises

À partir de ce nœud, j'utilise les aci. Ceci est à titre d'exemple puisque les aci sont expérimentales, et un certain nombre de choses ne fonctionnent pas ou ne sont pas possibles. Pour déléguer la gestion du contrôle d'accès, il faut le déclarer:

olcAccess: to dn.subtree="o=Corporates,dc=uubu,dc=fr" by dynacl/aci write

Commençons par donner l'accès read aux utilisateurs sur la branche:
1#subtree#grant;r,s;entry,objectClass,description#users#

Note: Il est prévu qu'il soit X-ORDERED à l'avenir.

Ensuite, un rôle Group-Mgr pourra gérer cette branche: il peut lire l'objet et en modifier la description. Il aura également le droit de gérer la structure des entreprises. Un rôle Group-Acl-Mgr sera lui, en charge de la gestion des aci au niveau de l'objet et des entreprises:
2#entry#grant;r,s;[all];w;description#set#[cn=Group-Mgr,cn=Group,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
3#entry#grant;w,r,s;OpenLDAPaci#set#[cn=Group-Acl-Mgr,cn=Group,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
4#entry#grant;w,r,s;children#set#[cn=Group-Mgr,cn=Group,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
5#entry#grant;w,r,s;children#set#[cn=Group-Acl-Mgr,cn=Group,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
6#subtree#grant;w,r,s;entry,children,o,ou,description,objectClass#set#[cn=Group-Mgr,cn=Group,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
7#subtree#grant;w,r,s;entry,OpenLDAPaci#set#[cn=Group-Acl-Mgr,cn=Group,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user

On remarque tout de suite les faiblesses des contraintes possibles des aci. En revanche, le design est quand même plus naturel.

Au niveau des aci des entreprises, on définis également des rôles pour chacune, et on peut, dans un souci d'autonomie, leur donner les même droits dans leur périmètre:

1#entry#grant;r,s;[all];w;description#set#[cn=jouets-fr-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
2#entry#grant;w,r,s;OpenLDAPaci#set#[cn=jouets-fr-Acl-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
3#entry#grant;w,r,s;children#set#[cn=jouets-fr-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
4#entry#grant;w,r,s;children#set#[cn=jouets-fr-Acl-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
5#subtree#grant;w,r,s;entry,ou,description,objectClass#set#[cn=jouets-fr-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
6#subtree#grant;w,r,s;entry,OpenLDAPaci#set#[cn=jouets-fr-Acl-Mgr,cn=Jouets-
fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user

ou=Peoples

Sous ce nœud, nous avons des besoins plus spécifiques:

Les utilisateurs doivent pouvoir s'authentifier:
1#entry#grant;r,s,c,d;children#users#
2#subtree#grant;x;userPassword#public#

Ils peuvent également lire certains attributs de tous les utilisateurs:
3#subtree#grant;r,s;entry,cn,uid,sn#users#

Ils peuvent changer leur propre mot de passe:
4#subtree#grant;w;userPassword#self#

Le rôle PwdReset contient les personnes habilitées à réinitialiser les mots de passe des utilisateurs
5#subtree#grant;w;userPassword#set#[cn=Pwd-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user

Le rôle Users-Adm-Mgr contient les personnes habilitées à gérer les comptes utilisateurs, y compris les créer/supprimer
6#entry#grant;w;children#set#[cn=Users-Adm-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user
7#subtree#grant;w,r,s;[all]#set#[cn=Users-Adm-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user

Le rôle User-Mgr contient les personnes habilitées à gérer des attributs des comptes utilisateurs (mais pas de créer/supprimer de compte):
8#subtree#grant;w,r,s;sn,uid,description#set#[cn=Users-Mgr,cn=Jouets-fr,cn=Applications,cn=Delegations,dc=uubu,dc=fr]/member* & user

Tester les acl:

Point essentiel pour s'assurer que nos acl fonctionnent tel que souhaité, et pour s'assurer qu'une modification n'impacte pas les autres acl. Il existe 2 manières de tester les acls, soit en utilisant slapacl, soit valider en effectuant des opérations directement dans la base. slapacl nous indique les droits mais il ne gère pas les aci.

Utilisation de slapacl: on test si un utilisateur peut gérer les membres (plus précisemment ici s'il peut se rajouter membre d'un groupe:

slapd -T acl -bdc=uubu,dc=fr -F bin/var/slapd.d/ -D"cn=Sylvain,ou=Peoples,o=uubu.org,o=Enterprise,dc=uubu,dc=fr" "member/write" -bcn=Group-Mgr,cn=Group,cn=Applications,cn=Delegations,dc=uubu,dc=fr "member/read:cn=Sylvain,ou=Peoples,o=uubu.org,o=Enterprise,dc=uubu,dc=fr"

on peut rajouter -d aci pour voir ce qui ce passe sous le capot.

La deuxième approche consiste à créer un script qui effectue des actions sur l'annuaire:

Par exemple je veut tester si un utilisateur lambda peut créer une entreprise:
echo -e "dn: o=toto,o=Enterprises,dc=uubu,dc=fr\nobjectClass: organization\no: toto\n" | ldapadd -x -D"cn=Sylvain,ou=Peoples,o=uubu.org,o=Corporates,dc=uubu,dc=fr" -wpassword -e'!noop'
puis on test le résultat:
[ $? -eq 50 ] && echo "OK"
ou alors:
[ $? -eq 14 ] && echo "ERROR!!!!!!"

N'oubliez pas l'extention noop, surtout si vous voulez tester la suppression d'une entreprise par exemple!

 

Conclusion: Les aci ne sont pas encore prêtes pour de la production, mais espérons que cela évoluera rapidement, ce modèle est quand même plus logique et intuitif que les acl. Noter que slapacl ne fonctionne pas avec les aci.

Note importante: Utilisez les outils d'openldap, tel que ldapmodify pour tester vos acl, et évitez d'utiliser ApacheDirectoryStudio. En effet, les acls avec évaluation de valeurs nécessitent un delete explicite d'une entrée, ce que ne fait pas ce dernier (entre autre).

Aucun commentaire pour cet article

Note: Les commentaires sont temporaiment désactivés, merci de contacter l'auteur par mail