Предпосылки: В организации, сведения о которой ведутся в нашем каталоге, два отдела: Developers и Designers. Информация о принадлежности пользователя отделу фиксируется как значение атрибута ou
в учётной записи каждого пользователя. Разграничение доступа к интернет-ресурсам на прокси-сервере организации решено выполнить на основании принадлежности пользователей группам соответствующих отделов в каталоге.
Задача: Требуется создать классические группы, постоянно отражающие актуальную структуру организации.
Как мы помним, объекты групп в каталоге можно построить на разных объектных классах. В данном примере для построения групп мы будем использовать класс groupOfNames
с атрибутом членства member
. Если Вы планируете строить динамические группы на классе groupOfUniqueNames
(атрибут членства uniqueMember
) или organizationalRole
(атрибут членства roleOccupant
), просто внесите в примеры настройки соответствующие изменения.
Начинать, как всегда, будем с исходного положения каталога. В данном примере порядок работы будет такой: настройка наложения, модификация записей пользователей, добавление записей динамических групп.
Как всегда, первым делом нужно подгрузить модуль наложения. Традиционный LDIF для этого действия:
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: dynlist.la
Применим его и проверим загруженные модули:
$ ldapmodify -x -D cn=config -W -f ./101-add_dynlist_module.ldif Enter LDAP Password: modifying entry "cn=module{0},cn=config" $ ldapsearch -xLLL -D cn=config -W -b cn=module{0},cn=config olcModuleLoad Enter LDAP Password: dn: cn=module{0},cn=config olcModuleLoad: {0}back_mdb.la olcModuleLoad: {1}dynlist.la
Переходим к настройкам наложения. Для данного примера мы сформируем следующую запись наложения с объектным классом olcDynamicList
(дочернюю по отношению к рабочей базе данных):
dn: olcOverlay=dynlist,olcDatabase={1}mdb,cn=config
objectClass: olcDynamicList
olcOverlay: dynlist
olcDLattrSet: groupOfNames labeledURI member
Параметры настройки наложения указываются в значении атрибута olcDLattrSet
(строка 4). Как и в предыдущем примере, у нас есть три разделённых пробелами параметра. Первые два из них, как мы уже знаем, задают имена объектного класса и типа атрибута, по которым наложение dynlist
отбирает среди результирующего набора операции поиска LDAP те записи, которые требуется дополнить динамическим содержимым. В качестве объектного класса мы выбрали общепринятый для групповых записей каталога класс groupOfNames
, а тип атрибута labeledURI
является стандартным для хранения URI, которые необходимы нам для задания параметров поиска динамического содержимого.
Наиболее интересным в данном примере является третий параметр. Здесь он представляет собой ещё одно имя типа атрибута (member
). В этом случае, то есть когда третьим параметром наложения является единичное имя типа атрибута, наложение dynlist
переходит в режим составления динамических групп. В этом режиме наложения также осуществляется вторичный поиск в каталоге по критериям, задаваемым в LDAP URI той записи, которую требуется дополнить динамическим содержимым. Если в результате такого вторичного поиска будут найдены какие-либо записи, то уникальные имена (DN) этих записей будут подставлены в запись динамической группы в качестве значений атрибута, имя которого указано в третьем параметре наложения. То есть динамическим содержимым будут имена записей-членов группы в значениях атрибута членства. В данном случае был выбран стандартный для объектного класса groupOfNames
атрибут членства member
.
Как видно, алгоритм работы наложения в режиме составления динамических групп даже несколько проще, чем при составлении динамических списков.
Добавим нашу запись с настройками наложения и убедимся в её наличии:
$ ldapadd -x -D cn=config -W -f ./102-add_dynlist_overlay.ldif Enter LDAP Password: adding new entry "olcOverlay=dynlist,olcDatabase={1}mdb,cn=config" $ ldapsearch -xLLL -D cn=config -W -b olcDatabase={1}mdb,cn=config -s one Enter LDAP Password: dn: olcOverlay={0}dynlist,olcDatabase={1}mdb,cn=config objectClass: olcDynamicList olcOverlay: {0}dynlist olcDlAttrSet: {0}groupOfNames labeledURI member
Наложение стало первым (индекс 0) для нашей рабочей базы данных. Переходим к следующему этапу.
Дополним записи пользователей признаком принадлежности к отделам. Для этого сформируем такой LDIF-файл:
dn: uid=antonova,ou=People,dc=mycompany,dc=ru
changetype: modify
add: ou
ou: Designers
dn: uid=ivanov,ou=People,dc=mycompany,dc=ru
changetype: modify
add: ou
ou: Developers
dn: uid=petrov,ou=People,dc=mycompany,dc=ru
changetype: modify
add: ou
ou: Developers
dn: uid=sidorov,ou=People,dc=mycompany,dc=ru
changetype: modify
add: ou
ou: Designers
Применим его к нашему каталогу и посмотрим, в каком состоянии будут записи пользователей после модификации:
$ ldapmodify -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./002-add_users_attributes.ldif Enter LDAP Password: modifying entry "uid=antonova,ou=People,dc=mycompany,dc=ru" modifying entry "uid=ivanov,ou=People,dc=mycompany,dc=ru" modifying entry "uid=petrov,ou=People,dc=mycompany,dc=ru" modifying entry "uid=sidorov,ou=People,dc=mycompany,dc=ru" $ ldapsearch -xLLL -b ou=People,dc=mycompany,dc=ru -s one dn: uid=ivanov,ou=People,dc=mycompany,dc=ru objectClass: inetOrgPerson uid: ivanov cn: Ivan Ivanov sn: Ivanov ou: Developers dn: uid=petrov,ou=People,dc=mycompany,dc=ru objectClass: inetOrgPerson uid: petrov cn: Petr Petrov sn: Petrov ou: Developers dn: uid=sidorov,ou=People,dc=mycompany,dc=ru objectClass: inetOrgPerson uid: sidorov cn: Sidor Sidorov sn: Sidorov ou: Designers dn: uid=antonova,ou=People,dc=mycompany,dc=ru objectClass: inetOrgPerson uid: antonova cn: Antonina Antonova sn: Antonova ou: Designers
Отлично! Переходим к следующему этапу.
Наконец, сформируем LDIF для добавления в каталог записей динамических групп двух наших отделов:
dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
objectClass: extensibleObject
cn: Developers
member: cn=dummy
labeledURI: ldap:///ou=People,dc=mycompany,dc=ru??one?(&(objectClass=inetOrgPerson)(ou=Developers))
dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
objectClass: extensibleObject
cn: Designers
member: cn=dummy
labeledURI: ldap:///ou=People,dc=mycompany,dc=ru??one?(&(objectClass=inetOrgPerson)(ou=Designers))
Записи групп строятся на структурном объектном классе groupOfNames
, который мы указывали в настройках наложения dynlist
. Обязательные атрибуты этого класса: cn
, который мы использовали для формирования RDN, и member
, в котором хранятся DN записей-членов группы. В связи с тем, что атрибут member
должен обязательно присутствовать в записи, мы добавили в наши группы фиктивных членов (строки 5 и 12). Это общепринятая практика для групп с объектным классом groupOfNames
.
Условия для поиска динамического содержимого оформлены в виде LDAP URI в значениях атрибутов labeledURI
динамических записей (строки 6 и 13). Тип атрибута labeledURI
не входит в число обязательных или допустимых атрибутов класса groupOfNames
. В данном примере для включения таких атрибутов в наши записи мы воспользовались специальным вспомогательным объектным классом extensibleObject, позволяющим включать в запись любой известный LDAP-серверу пользовательский атрибут.
Критерии поиска, задаваемые в LDAP URI, практически полностью совпадают с критериями из предыдущего примера. Единственное отличие — отсутствие списка возвращаемых атрибутов (4-й компонент URI), поскольку для построения динамических групп никаких атрибутов не требуется. Если список возвращаемых атрибутов будет не пуст (например, если Вы скопируете откуда-то готовый URI и забудете очистить список атрибутов), наложение всё равно построит динамическую группу. Но в некоторых случаях, например, при составлении ACL на основе динамических групп, наличие атрибутов в 4-м компоненте URI является критичным: ACL просто не будут работать. Так что хорошей практикой является составление корректных URI, соответствующих условиям решаемой задачи.
Добавим записи наших групп в каталог:
$ $ ldapadd -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./003-add_groups.ldif Enter LDAP Password: adding new entry "cn=Developers,ou=Groups,dc=mycompany,dc=ru" adding new entry "cn=Designers,ou=Groups,dc=mycompany,dc=ru"
А теперь попробуем запросить их:
$ ldapsearch -xLLL -b ou=Groups,dc=mycompany,dc=ru -s one -o ldif-wrap=no dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru objectClass: groupOfNames objectClass: extensibleObject cn: Designers member: cn=dummy member: uid=antonova,ou=People,dc=mycompany,dc=ru member: uid=sidorov,ou=People,dc=mycompany,dc=ru labeledURI: ldap:///ou=People,dc=mycompany,dc=ru??one?(&(objectClass=inetOrgPerson)(ou=Designers)) dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru objectClass: groupOfNames objectClass: extensibleObject cn: Developers member: cn=dummy member: uid=ivanov,ou=People,dc=mycompany,dc=ru member: uid=petrov,ou=People,dc=mycompany,dc=ru labeledURI: ldap:///ou=People,dc=mycompany,dc=ru??one?(&(objectClass=inetOrgPerson)(ou=Developers))
Как видно, кроме фиктивных членов группы наполнились атрибутами членства, значения которых представляют собой уникальные имена (DN) записей пользователей, работающих в соответствующих отделах. Наложение dynlist
сработало, как ожидалось.
Итак, у нас есть группы с динамическими членами. Но можем ли мы составить на основе таких групп групповые ACL для разграничения доступа к каталогу OpenLDAP? То есть, чтобы пользователи на основе принадлежности к динамическим группам получали те или иные права доступа к каталогу? Давайте попробуем.
Предположим, что мы хотим разрешить пользователям, принадлежащим группе cn=Developers,ou=Groups,dc=mycompany,dc=ru, изменять значения атрибута description
в учётных записях любых пользователей в каталоге. Для этого мы можем добавить в запись настроек нашей рабочей базы данных следующий ACL:
dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {1}to dn.children="ou=People,dc=mycompany,dc=ru" attrs=description
by self write
by group/groupOfNames/member="cn=Developers,ou=Groups,dc=mycompany,dc=ru" write
by * read
Как мы помним, для нашей базы данных уже определено два ACL, так что при добавлении приведённого выше ACL он займёт место между ними. Применим изменения и посмотрим, что получилось:
$ ldapmodify -x -D cn=config -W -f ./103-add_group_acl.ldif Enter LDAP Password: modifying entry "olcDatabase={1}mdb,cn=config" $ ldapsearch -xLLL -D cn=config -W -b olcDatabase={1}mdb,cn=config -s base -o ldif-wrap=no olcAccess Enter LDAP Password: dn: olcDatabase={1}mdb,cn=config olcAccess: {0}to attrs=userPassword by self write by anonymous auth by * none olcAccess: {1}to dn.children="ou=People,dc=mycompany,dc=ru" attrs=description by self write by group/groupOfNames/member="cn=Developers,ou=Groups,dc=mycompany,dc=ru" write by * read olcAccess: {2}to * by self write by * read
Хорошо. Теперь посмотрим, как это повлияло на привилегии пользователей. Для примера мы будем тестировать права на изменения атрибута description
записи uid=sidorov,ou=People,dc=mycompany,dc=ru при доступе к каталогу от имени пользователей antonova (не является членом группы Developers) и ivanov (является членом группы Developers).
Сначала протестируем привилегии пользователей утилитой slapacl
(для её работы требуется считать списки контроля доступа из конфигурационной директории slapd.d
, поэтому мы запускаем её с правами пользователя root):
# slapacl -D uid=antonova,ou=People,dc=mycompany,dc=ru -b uid=sidorov,ou=People,dc=mycompany,dc=ru description dynlist_db_open: unable to fetch attributeDescription "dgIdentity": 17 (attribute type undefined) dynlist_db_open: unable to fetch attributeDescription "dgAuthz": 17 (attribute type undefined) authcDN: "uid=antonova,ou=people,dc=mycompany,dc=ru" description: read(=rscxd) # slapacl -D uid=ivanov,ou=People,dc=mycompany,dc=ru -b uid=sidorov,ou=People,dc=mycompany,dc=ru description dynlist_db_open: unable to fetch attributeDescription "dgIdentity": 17 (attribute type undefined) dynlist_db_open: unable to fetch attributeDescription "dgAuthz": 17 (attribute type undefined) authcDN: "uid=ivanov,ou=people,dc=mycompany,dc=ru" description: read(=rscxd)
Как мы видим, в обоих случаях получены права только на чтение, независимо от того, принадлежит ли пользователь, запрашивающий доступ к каталогу, динамической группе Developers, или нет. Однако возможно, что утилита slapacl
просто не умеет работать с динамическими группами. Попробуем провести натурные испытания. Для этого составим такой LDIF с модификацией атрибута description
записи пользователя sidorov:
dn: uid=sidorov,ou=People,dc=mycompany,dc=ru
changetype: modify
replace: description
description: The most talented designer
Попробуем применить эти изменения:
$ ldapmodify -x -D uid=antonova,ou=People,dc=mycompany,dc=ru -w antonovaPassword -f ./004-modify_user_description.ldif modifying entry "uid=sidorov,ou=People,dc=mycompany,dc=ru" ldap_modify: Insufficient access (50) $ ldapmodify -x -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword -f ./004-modify_user_description.ldif modifying entry "uid=sidorov,ou=People,dc=mycompany,dc=ru" ldap_modify: Insufficient access (50)
Эксперимент показал, что пользователи и в самом деле не получают права на изменения на основе принадлежности динамической группе. Значит, ACL на основе динамических групп не работают? Всё не так просто, и чтобы докопаться до истины, надо очень внимательно перечитать man-страницу slapd.access(5), а конкретнее, часть материала, касающуюся групповых условий в поле <who> ACL. Приведём здесь несколько сокращённую выдержку:
Выражение group[/<objectclass>[/<attrname>]]=<group> означает, что доступ предоставляется тем запрашивающим, чьё DN перечислено в групповой записи, DN которой задано в <group>. Необязательные параметры <objectclass> и <attrname> определяют объектный класс групповой записи и тип атрибута членства в ней. Значения по умолчанию - groupOfNames и member, соответственно. Для статических групп указанный тип атрибута должен иметь синтаксис DistinguishedName или NameAndOptionalUID. Для динамических групп тип атрибута должен быть подтипом от labeledURI. В динамических группах будут оцениваться только LDAP URI формы ldap:///<base>??<scope>?<filter> с поиском только по локальному серверу.
То есть, для ACL на основе динамических групп нужно указать тип атрибута, содержащий LDAP URI с параметрами вторичного поиска (в нашем примере это как раз labeledURI
), и, кроме того, грамотно составить сам URI (о чём мы говорили выше). Но тогда какой объектный класс следует указывать в групповом условии ACL? Экспериментальным путём было установлено, что это должен быть класс, содержащий атрибут, в котором указан LDAP URI. Для нашего примера мы должны указать класс extensibleObject
. Если бы, как в предыдущем примере, для подключения атрибута labeledURI
мы бы использовали объектный класс labeledURIObject
, то в групповом условии ACL указывались бы именно эти класс и тип атрибута. Наконец, если бы для создания динамической группы использовался объектный класс groupOfURLs
и его атрибут memberURL
(производный от labeledURI
), то в групповом условии ACL мы должны были бы указать их имена.
Вооружившись этими знаниями, переопределим наш второй ACL (с индексом 1) так:
dn: olcDatabase={1}mdb,cn=config
changetype: modify
delete: olcAccess
olcAccess: {1}
-
add: olcAccess
olcAccess: {1}to dn.children="ou=People,dc=mycompany,dc=ru" attrs=description
by self write
by group/extensibleObject/labeledURI="cn=Developers,ou=Groups,dc=mycompany,dc=ru" write
by * read
Внесём изменения в настройки базы данных и проверим получившиеся в итоге ACL:
$ ldapmodify -x -D cn=config -W -f ./104-modify_group_acl.ldif Enter LDAP Password: modifying entry "olcDatabase={1}mdb,cn=config" $ ldapsearch -xLLL -D cn=config -W -b olcDatabase={1}mdb,cn=config -s base -o ldif-wrap=no olcAccess Enter LDAP Password: dn: olcDatabase={1}mdb,cn=config olcAccess: {0}to attrs=userPassword by self write by anonymous auth by * none olcAccess: {1}to dn.children="ou=People,dc=mycompany,dc=ru" attrs=description by self write by group/extensibleObject/labeledURI="cn=Developers,ou=Groups,dc=mycompany,dc=ru" write by * read olcAccess: {2}to * by self write by * read
Всё в порядке. Попробуем повторить наше тестирование на предмет получения пользователями привилегий на основе их принадлежности динамической группе. Сначала протестируем права пользователей утилитой slapacl
:
# slapacl -D uid=antonova,ou=People,dc=mycompany,dc=ru -b uid=sidorov,ou=People,dc=mycompany,dc=ru description dynlist_db_open: unable to fetch attributeDescription "dgIdentity": 17 (attribute type undefined) dynlist_db_open: unable to fetch attributeDescription "dgAuthz": 17 (attribute type undefined) authcDN: "uid=antonova,ou=people,dc=mycompany,dc=ru" description: read(=rscxd) # slapacl -D uid=ivanov,ou=People,dc=mycompany,dc=ru -b uid=sidorov,ou=People,dc=mycompany,dc=ru description dynlist_db_open: unable to fetch attributeDescription "dgIdentity": 17 (attribute type undefined) dynlist_db_open: unable to fetch attributeDescription "dgAuthz": 17 (attribute type undefined) authcDN: "uid=ivanov,ou=people,dc=mycompany,dc=ru" description: write(=wrscxd)
Как мы видим, на этот раз пользователь antonova (не является членом группы Developers) получает права на чтение атрибута decsription
записи пользователя sidorov, а пользователь ivanov (является членом группы Developers) — права на изменение этого атрибута. То есть наш ACL работает.
Убедимся в этом, проведя наше натурное испытание. Сначала попробуем применить описанный выше LDIF модификации атрибута decsription
записи пользователя sidorov от имени пользователя antonova:
$ ldapmodify -x -D uid=antonova,ou=People,dc=mycompany,dc=ru -w antonovaPassword -f ./004-modify_user_description.ldif modifying entry "uid=sidorov,ou=People,dc=mycompany,dc=ru" ldap_modify: Insufficient access (50)
Как и следовало ожидать, у пользователя antonova недостаточно прав для выполнения этого действия. Попробуем провести ту же модификацию от имени пользователя ivanov:
$ ldapmodify -x -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword -f ./004-modify_user_description.ldif modifying entry "uid=sidorov,ou=People,dc=mycompany,dc=ru"
Модификация проведена. Убедимся в этом:
$ ldapsearch -xLLL -b uid=sidorov,ou=People,dc=mycompany,dc=ru description dn: uid=sidorov,ou=People,dc=mycompany,dc=ru description: The most talented designer
Итак, пользователь ivanov получил права на изменение атрибута decsription
записи пользователя sidorov на основании его членства в динамической группе cn=Developers,ou=Groups,dc=mycompany,dc=ru, а значит, ACL доступа к каталогу OpenLDAP с групповыми условиями на основе динамических групп работают.
В ходе этого урока мы познакомились с ещё одним вариантом третьего параметра настроек наложения dynlist
, включающим режим построения динамических групп. Также мы рассмотрели порядок составления групповых условий ACL OpenLDAP на основе динамических групп. Итоговые настройки каталога dc=mycompany,dc=ru выглядят так (для удобства чтения ACL мы разбили их вывод на несколько строк):
$ ldapsearch -xLLL -o ldif-wrap=no -D cn=config -W -b olcDatabase={1}mdb,cn=config Enter LDAP Password: dn: olcDatabase={1}mdb,cn=config objectClass: olcMdbConfig olcDatabase: {1}mdb olcDbDirectory: /var/lib/ldap/dc=mycompany,dc=ru olcSuffix: dc=mycompany,dc=ru olcAccess: {0}to attrs=userPassword by self write by anonymous auth by * none olcAccess: {1}to dn.children="ou=People,dc=mycompany,dc=ru" attrs=description by self write by group/extensibleObject/labeledURI="cn=Developers,ou=Groups,dc=mycompany,dc=ru" write by * read olcAccess: {2}to * by self write by * read olcRootDN: cn=manager,ou=System,dc=mycompany,dc=ru olcRootPW: {SSHA}PKFrwbIL/zLd3gabPPLxn1vNq2jQHj4g olcDbIndex: objectClass eq olcDbIndex: cn eq,sub,subinitial dn: olcOverlay={0}dynlist,olcDatabase={1}mdb,cn=config objectClass: olcDynamicList olcOverlay: {0}dynlist olcDlAttrSet: {0}groupOfNames labeledURI member