Gestion des droits d'accès à l'annuaire
Introduction
Cette page a pour objectif d'exposer des bonnes pratiques concernant la mise en œuvre des règles de contrôle d'accès aux requêtes LDAP, via les mécanismes de type “Access Control List” (ACL) offerts par le serveur LDAP ; et plus particulièrement en s'appuyant sur la nomenclature proposée pour Les groupes d'applications.
Principes de sécurité généraux
Ces recommandations s'inscrivent dans un contexte plus large de gestion de la sécurité et de la confidentialité des données, que nous ne détaillerons pas ici mais dont nous rappelons quelques grandes lignes ci-dessous.
Réglementation sur la protection des données sensibles
Principe de moindre exposition par défaut
N'ouvrir par défaut que les accès qui sont nécessaires (tout ce qui n'est pas explicitement autorisé est interdit). Ce principe est applicable à tous les niveaux : flux réseaux, ACLs LDAP, etc .
Contrôle des flux réseaux par pare-feu
Le pare-feu constitue le premier niveau de protection d'un annuaire LDAP. Selon le cas, il pourra être fait appel à un pare-feu central, ou des règles de filtrage IP locales à l'hôte hébergeant le service LDAP. En fonction de l'usage envisagé du serveur LDAP, son accès sera par exemple restreint uniquement aux sous-réseaux hébergeant des serveurs d'applications, ou aux postes de travail internes à l'établissement. Une ouverture du protocole LDAP hors du réseau d'établissement est rarement nécessaire et devra être examinée au cas par cas, en s'assurant que les autres mécanismes de sécurisation sont bien opérationnels, notamment l'authentification, le chiffrement et les ACLs.
Instances dédiées
L'établissement peut faire le choix de mettre en œuvre plusieurs instances de serveurs LDAP, dédiée chacune à un usage ou une audience particulière : par exemple une instance pour les requêtes provenant de serveurs d'applications, et une autre accessible des postes de travail des utilisateurs, pour les usages de type “carnet d'adresses” des clients de messagerie. Chaque instance pourra avoir sa propre stratégie de sécurité (pare-feu, limites, ACLs, authentification…), et son propre contenu, par le biais de réplications partielles : par exemple, l'instance “postes de travail” ne contiendra pas de données privées.
Chiffrement
Le chiffrement garantit la confidentialité et l'intégrité des échanges entre le client et le serveur, et sécurise l'authentification. Lorsque le protocole LDAP est exposé sur un lien non maîtrisé, il est nécessaire de s'assurer que le chiffrement est bien rendu obligatoire, par exemple en n'autorisant que le chiffrement TLS implicite (protocole LDAPS sur le port 636), ou en utilisant des ACLs imposant un “Security Strength Factor” (SSF) minimum : access by ssf=…
. Il convient également de s'assurer que les protocoles et algorithmes de chiffrement utilisés sont suffisamment sûrs au regard de l'état de l'art, en désactivant les algorithmes et protocoles obsolètes : utilisation de la directive OpenLDAP TLSCipherSuite
, désactivation des anciennes versions de SSL/TLS, suppression des “enctypes” obsolètes dans les keytab Kerberos, etc.
Authentification
L'authentification constitue le niveau le plus fin du contrôle d'accès, sur lequel s'appuient notamment les limites et les ACLs décrites ci-après. Elle peut être considérée comme nécessaire dès lors que le serveur LDAP héberge des données privées (voir : Visibilité des attributs) et que des distinctions de droits d'accès doivent être faites en fonction du client. Selon le cas, on pourra faire appel à un mécanisme d'authentification interne au serveur LDAP (mot de passe), externe (Kerberos ou certificat TLS par exemple), ou se contenter d'un contrôle d'adresse IP si adapté au contexte. A noter que des mécanismes comme Kerberos ou TLS, en plus d'offrir une couche de chiffrement du flux de données, permettent l'authentification mutuelle : le serveur peut authentifier son client, et le client son serveur.
Délégation d'authentification et de contrôle d'accès
Dans un certain nombre de cas, des données issues du LDAP peuvent être rendues accessibles à un tiers avec un contrôle d'accès relativement laxiste, charge à ce tiers d'assurer un contrôle plus strict avant de présenter les données au client final. C'est le cas par exemple d'une API se connectant à l'annuaire LDAP avec une identité unique, et présentant des données filtrées à des applications clientes via un autre protocole (web service REST ou SOAP, SAML, etc). Dans ce cas la confiance envers ce tiers fait partie intégrante du modèle de sécurité, et doit être auditée avec rigueur. On parle communément d'identité “de confiance” ou “trusted”.
Les limites OpenLDAP
Objectfs des limites
Imposer des limites d'accès sur les requêtes LDAP permet :
- de limiter la collecte massive de données de l'annuaire (malveillance), en complément des autres mesure de protection (pare-feu, authentification) ;
- de se protéger contre une rupture ou une dégradation de service par épuisement des ressources du serveur, qu'elle soit malveillante (déni de service) ou accidentelle (requêtes mal formulées, par exemple non indexées ou trop lourdes au regard de leur finalité, qui engendrent une charge exagérée sur le serveur).
Le principe de base est d'appliquer des limites basses aux connexions anonymes (si celles-ci sont autorisées), ainsi qu'aux applications ou utilisateurs n'ayant pas déclaré de besoin particuliers.
Ensuite, proportionner les limites aux usages :
- limites plus ou moins élevées correspondant aux besoins pour les applications sous la responsabilité d'un tiers ;
- absence de limites pour les comptes les plus privilégiés, parfaitement maîtrisés, s'ils en ont besoin (connecteurs d'alimentation de l'annuaire par exemple).
Types de limites
OpenLDAP propose plusieurs catégories de limites :
- limites sur la taille des requête (size);
- limites sur la durée des requêtes (time);
- limites sur la “complexité” des requêtes (size unchecked) : correspond à la taille de la requête interne qu'OpenLDAP doit exécuter pour répondre à la requête client, quelle que soit la taille de la réponse effective. Ce type de limite offre une protection efficace contre les requêtes mal formulées, notamment les recherches non indexées.
Point important : toutes les requêtes exécutées de façon répétitives et qui attendent une réponse immédiate (temps réel) doivent être indexées : voir Types d'indexation des attributs.
limits
affecte les deux variantes simultanément.
Par défaut OpenLDAP fixe des limites arbitraires : durée (timelimit) de 1h, taille (sizelimit) de 500 entrées.
Ordre de déclaration des limites
La première directive limits
dont le sujet (<who>) correspond au client l'emporte. Il convient donc de déclarer les limites par ordre décroissant, les plus larges en premier.
Exemple de mise en œuvre de limites
L'exemple ci-dessous peut constituer une base de définition des limites d'un annuaire établissement typique, à ajuster en fonction des besoins.
Ce fichier fait appel à la nomenclature préconisée par SupAnn pour les groupes de contrôles d'accès des applications: Les groupes d'applications.
Note: ce fichier est un extrait de configuration au format statique (.conf), qui peut être référencé dans le fichier slapd.conf via une directive include
au sein de la section database
souhaitée. Si vous utilisez la configuration dynamique (cn=config), il peut être converti au format LDIF en lançant slapd avec les options -f et -F, comme spécifié dans man slapd
.
- slapd-limits.conf
# -- aucune limite pour les applications de confiance # effectuant des requêtes "batch" sur la totalité de l'annuaire limits group="cn=acl.limits.unlimited,ou=applicationGroups,dc=univ-exemple,dc=fr" size=unlimited size.unchecked=unlimited time=unlimited # -- limites élevées, pour des applications amenées à faire de grosses requêtes limits group="cn=acl.limits.large,ou=applicationGroups,dc=univ-exemple,dc=fr" size=10000 size.unchecked=100000 time=60 # -- limites moyennes, convenant à une majorité d'applications limits group="cn=acl.limits.medium,ou=applicationGroups,dc=univ-exemple,dc=fr" size=1000 size.unchecked=50000 time=20 # -- limites basses, pour des applications n'effectuant que des requêtes limitées limits group="cn=acl.limits.small,ou=applicationGroups,dc=univ-exemple,dc=fr" size=100 size.unchecked=50000 time=10 # -- limites de compatibilité basse, pour les applications ne gérant pas correctement # l'erreur "administrative limits exceeded" en cas de résultats partiels: # pas de "size.unchecked" mais report sur un "time" très réduit. limits group="cn=acl.limits.noload.small,ou=applicationGroups,dc=univ-exemple,dc=fr" size=10 size.unchecked=unlimited time=2 # -- limites par défaut pour les connexions authentifiées: # autorise les recherches approximatives simples, avec taille plafonnée limits users size=10 size.unchecked=10000 time=5 # -- limites par défaut pour les connexions anonymes: # n'autorise que des recherches unitaires indexées limits anonymous size=1 size.unchecked=1000 time=2
Les ACLs OpenLDAP
Objectifs des ACLs
Les ACLs permettent de mettre en œuvre le principe de moindre exposition par défaut, en graduant l'étendue des accès en fonction de la confiance accordée au client, et d'assurer la confidentialité des données considérées comme privées ou secrètes, si l'annuaire en contient.
Mode de fonctionnement des ACLs
OpenLDAP implémente un contrôle d'accès via des règles statiques, définies au niveau de la configuration du serveur. Ces règles peuvent s'appuyer sur des mécanismes relativement complexes, définis de manière exhaustive dans la page de manuel man slapd.access
.
Par défaut, en absence d'ACLs, toute l'arborescence est accessible en lecture seule quel que soit l'identité du client.
Il est recommandé de toujours inclure un minimum de directives de droits d'accès dans sa configuration, ne serait-ce que pour s'assurer que le droit par défaut soit cohérent avec son objectif, et protéger les mots de passe servant à l'authentification des clients.
Bien qu'OpenLDAP permette de gérer des droits d'accès discrétionnaires très fins, SupAnn recommande de se limiter aux 5 niveaux macroscopiques imbriqués qui sont écriture (write), lecture (read), recherche (search), authentification (auth) ou rien (none). Ces niveaux sont ordonnés du plus fort au plus faible, chaque niveau impliquant les privilèges des niveaux inférieurs.
Ordre de déclaration des ACLs
OpenLDAP applique une logique séquentielle de traitement des ACLs : la première rencontrée dont l'objet (champ <what> dans man slapd.access
) correspond à la requête en cours s'applique et arrête le traitement pour cet objet, de façon similaire aux ACLs de pare-feu réseau. Les privilèges accordés aux sujets énumérés (champ <who>) sont alors appliqués à cet objet, aucun accès n'étant accordé si aucun sujet ne correspond ; un échappatoire est néanmoins possible avec l'action break
, qui permet de continuer la lecture des ACLs suivantes pour le même objet.
Pour chaque niveau de privilège (écriture, lecture), les règles définissant des exceptions sur un périmètre plus restreint que la règle générale (en terme d'objets ciblés ou de sujets autorisés) doivent donc être placées avant celle-ci.
Dans ce contexte SupAnn préconise l'ordonnancement suivant des ACLs :
- droit d'administration global
- droits d'administration discrétionnaires sur des branches
- masquage sélectif d'entrées sur la base d'attributs matérialisant leur état (ex: supannRessourceEtat), avec gestion d'exceptions
- droits d'écriture discrétionnaires sur attributs ou groupes d'attributs
- droit d'accès anonyme nécessaire pour l'authentification
- droit de lecture des branches publiques
- droit de parcours des branches à visibilité restreinte
- droits de lecture discrétionnaires sur des branches
- droits de lecture discrétionnaires sur des attributs ou groupes d'attributs
- droits par défaut de recherche ou de lecture sur les attributs
- rejet de toutes les requêtes restantes
Exemple de mise en œuvre d'ACLs
Le fichier de configuration suivant reprend l'ensemble de ces concepts, et donne un modèle pouvant être adapté et enrichi en fonction des besoins. Il fait appel à la syntaxe recommandée par SupAnn pour attribuer les privilèges via des groupes : voir Les groupes d'applications.
Note: ce fichier est un extrait de configuration au format statique (.conf), qui peut être référencé dans le fichier slapd.conf
via une directive include
au sein de la section database
souhaitée. Si vous utilisez la configuration dynamique (cn=config), il peut être converti au format LDIF en lançant slapd avec les options -f et -F, comme spécifié dans man slapd
.
- slapd-access.conf
# ====== AUTORISATIONS D'ECRITURE GLOBALES ====== # --- administrateurs LDAP: accès total access to * by group=cn=acl.dit.admin,ou=applicationGroups,dc=univ-exemple,dc=fr write by * break # --- Accès global à ou=groups # cet exemple permet de déléguer la gestion des groupes à une application spécifique access to dn.sub="ou=groups,dc=univ-exemple,dc=fr" by group=cn=acl.dit.groups.admin,ou=applicationGroups,dc=univ-exemple,dc=fr write by * break # ====== INTERDICTIONS ====== # --- masquage des comptes inactifs # cet exemple masque les entrées inactives (et en bloque l'authentification), # sauf pour les applications dûment autorisées. access to filter="(supannRessourceEtat={COMPTE}I*)" by group=cn=acl.filter.inactive.access,ou=applicationGroups,dc=univ-exemple,dc=fr break by * none # ====== AUTORISATIONS D'ECRITURE DISCRETIONNAIRES SUR LES ATTRIBUTS ====== # --- écriture supannFCPerson # cet exemple délègue à une application la gestion de l'identité France Connect access to attrs=@supannFCPerson,objectClass by group=cn=acl.class.supannFCPerson.write,ou=applicationGroups,dc=univ-exemple,dc=fr write by * break # ====== AUTHENTIFICATION ====== # cet exemple autorise l'authentification par mot de passe, sauf pour les entrées à l'état "suspendu" access to filter="(!(supannRessourceEtat={COMPTE}S*))" attrs=userPassword by anonymous auth by self write by * none # ====== AUTORISATIONS PAR DEFAUT SUR LES BRANCHES ===== # --- branches publiques --- # schéma access to dn.sub=cn=Subschema by * read # -- base access to dn.base=dc=univ-exemple,dc=fr by * read # -- premier niveau d'arborescence access to dn.one=dc=univ-exemple,dc=fr by * read # -- entités SupAnn access to dn.children=ou=structures,dc=univ-exemple,dc=fr by * read # --- Branches énumérables publiquement (avec attributs filtrés) --- # -- personnes access to dn.children=ou=people,dc=univ-exemple,dc=fr attrs=entry by * read # -- groupes access to dn.children=ou=groups,dc=univ-exemple,dc=fr attrs=entry by * read # ====== AUTORISATIONS DE LECTURE DISCRETIONNAIRES SUR LES BRANCHES ====== # -- lecture de ou=people # cet exemple donne "carte blanche" sur la branche ou=people, # sauf pour les mots de passe bloqués à la section "AUTHENTIFICATION". access to dn.children=ou=people,dc=univ-exemple,dc=fr by group=cn=acl.dit.people.read,ou=applicationGroups,dc=univ-exemple,dc=fr read by * break # -- lecture de ou=groups access to dn.children=ou=groups,dc=univ-exemple,dc=fr by group=cn=acl.dit.groups.read,ou=applicationGroups,dc=univ-exemple,dc=fr read by * break # ====== AUTORISATIONS DE LECTURE DISCRETIONNAIRES SUR LES ATTRIBUTS ====== # -- lecture du scope "identifiants" access to attrs=eduPersonPrincipalName,eduPersonPrincipalNamePrior,eduPersonTargetedID,eduPersonUniqueId,supannAliasLogin,supannEmpId,supannEtuId,supannFCSub,supannRefId,uid by group=cn=acl.scope.id.read,ou=applicationGroups,dc=univ-exemple,dc=fr read by * break # -- lecture du scope "identité numérique" access to attrs=cn,displayName,eduPersonNickname,sn,givenName,supannCivilite,supannCodeINSEEPaysDeNaissance,supannCodeINSEEVilleDeNaissance by group=cn=acl.scope.identity.read,ou=applicationGroups,dc=univ-exemple,dc=fr read by * break access to attrs=,supannNomDeNaissance,supannOIDCDateDeNaissance,supannOIDCGenre,supannPrenomsEtatCivil by group=cn=acl.scope.identity.read,ou=applicationGroups,dc=univ-exemple,dc=fr read by * break # -- lecture du scope "gestionnaires d'un compte" access to attrs=supannMailContact,owner,manager,supannGroupeAdminDN,supannParrainDN by group=cn=acl.scope.manager.read,ou=applicationGroups,dc=univ-exemple,dc=fr read by * break # -- lecture du scope "données personnelles" access to attrs=homePostalAddress,homePhone,mailForwardingAddress,preferredLanguage,supannAdressePostalePrivee by group=cn=acl.scope.manager.read,ou=applicationGroups,dc=univ-exemple,dc=fr read by * break access to attrs=supannConsentement,supannListeRouge,supannMailPerso,supannMailPrive,supannTelephonePrive by group=cn=acl.scope.private.read,ou=applicationGroups,dc=univ-exemple,dc=fr read by * break # -- lecture de la carte multi-services access to attrs=@supannCMS by group=cn=acl.class.supannCMS.read,ou=applicationGroups,dc=univ-exemple,dc=fr read by * break # -- lecture des timestamps # cet exemple permet à des applications de s'alimenter sur l'annuaire en mode "delta" (différentiel) access to attrs=contextCSN,entryCSN,modifyTimestamp,createTimestamp by groupe=cn=acl.scope.timestamps.read,ou=applicationGroups,dc=univ-exemple,dc=fr read by * break # -- lecture des membres des groupes # cet exemple autorise des applications à lire les membres de tous les groupes # et par symétrie, donne accès aux memberOf des membres. access to attrs=member,memberOf by groupe=cn=acl.attr.member.read,ou=applicationGroups,dc=univ-exemple,dc=fr read by * break # ====== AUTORISATIONS PAR DEFAUT SUR LES ATTRIBUTS ===== # -- visibilité sélective des propriétés des groupes # cet exemple ne montre les membres et autres propriétés des groupes # qu'en fonction des l'appartenance ou des privilèges du sujet access to filter="(objectClass=groupOfNames)" attrs=member by dnattr=member read by dnattr=supannGroupeLecteurDN read by * break access to filter="(objectClass=groupOfNames)" attrs=member,@supannGroupe by dnattr=owner read by dnattr=supannGroupeAdminDN read by * break # -- attributs limités en recherche access to attrs=eduPersonPrincipalNamePrior,eduPersonUniqueId by self read by * search access to attrs=supannAutreMail,supannRessourceEtat,supannRessourceEtatDate,supannAliasLogin,supannListeRouge,supannFCSub by self read by * search # -- attributs publics access to attrs=entryDN,objectClass,structuralObjectClass,subschemaSubentry,hasSubordinates,aliasedObjectName,entryUUID by * read access to attrs=uid,cn,mail,o,ou,dc,l,displayName,supannCivilite,title,sn,givenName,description,labeledURI,preferredLanguage by * read access to attrs=eduPersonNickname,eduPersonPrincipalName,eduPersonOrgDN,eduPersonPrimaryOrgUnitDN,eduPersonOrgUnitDN by * read access to attrs=supannEtablissement,supannEntiteAffectation,supannEntiteAffectationPrincipale,supannTypeEntite,supannAffectation,supannActivite by * read access to attrs=eduPersonAffiliation,eduPersonPrimaryAffiliation,supannRole,supannRoleGenerique,supannRoleEntite by * read access to attrs=supannEtuAnneeInscription,supannEtuCursusAnnee,supannEtuDiplome,supannEtuElementPedagogique,supannEtuEtape,supannEtuInscription by * read access to attrs=supannEtuRegimeInscription,supannEtuSecteurDisciplinaire,supannEtuTypeDiplome by * read access to attrs=telephoneNumber,supannAutreTelephone,facsimileTelephoneNumber,mobile,postalAddress by * read access to attrs=supannCodePopulation,supannExtProfil,supannEmpProfil,supannEmpDateFin,supannExtDateFin,supannEtuDateFin by * read # -- tout le reste est privé access to * by self read by * none