Как сказано в man-странице наложения, оно предназначено для того, чтобы автоматически поддерживать обратное членство в группе. На практике это означает, что при использовании этого наложения между группой и её записями-членами создаётся взаимосвязь: при добавлении DN записи в качестве члена в некоторую группу в саму эту запись добавляется атрибут (по умолчанию это операционный атрибут memberOf
), содержащий в качестве значения DN записи-группы. При удалении же DN записи из членов группы из самой записи будет удалён атрибут, значение которого содержит DN записи-группы.
Такая взаимосвязь позволяет администраторам строить довольно удобные фильтры для поиска записей пользователей, принадлежащих какой либо группе, например:
(&(uid=*)(memberOf=cn=MyPrettyGroup,ou=Groups,dc=mycompany,dc=ru))
Такие фильтры часто применяются для настройки библиотек/приложений, осуществляющих аутентификацию/авторизацию пользователей с использованием каталога. В данном случае они позволяют на ранней стадии выполнить отбор записей пользователей, которым разрешено выполнять аутентификацию, на основе принадлежности той или иной группе. Кроме того, подобные фильтры можно использовать и при настройке самого OpenLDAP, чтобы организовать взаимодействие между статическими и динамическими группами (списками).
Выглядит всё просто и удобно. Но есть нюансы, касающиеся как построения фильтров, так и использования самого наложения memberof
.
Администраторы часто склонны переоценивать возможности построения фильтров на основе атрибутов с синтаксисом DN. Неоднократно приходилось наблюдать попытки составить фильтры типа:
(&(uid=*)(memberOf=cn=*,ou=Groups,dc=mycompany,dc=ru))
Или даже такие:
(&(uid=*)(memberOf=cn=*Group,ou=Groups,dc=mycompany,dc=ru))
К сожалению, такие фильтры работать (возвращать результаты) не будут. А всё потому, что атрибут memberOf
имеет синтаксис DN, для которого в RFC 4517 определено единственное правило соответствия distinguishedNameMatch, которое является правилом соответствия типа equality, то есть сравнение с использованием такого правила вернёт истину только в случае полного совпадения значения атрибута со значением в правой части AVA фильтра. Поэтому единственный приемлемый вариант фильтра будет таков:
(&(uid=*)(memberOf=cn=MyPrettyGroup,ou=Groups,dc=mycompany,dc=ru))
Справедливости ради следует отметить, что для атрибутов с синтаксисом DN (как и для всех остальных) также работают фильтры типа present:
(&(uid=*)(memberOf=*))
В данном случае будут отобраны записи каталога, в которых имеются сразу и атрибуты uid
, и атрибуты memberOf
. Но практическая ценность подобного рода фильтров невысока.
Разумеется, эти же ограничения распространяются и на любые другие атрибуты с синтаксисом DN (member
, manager
, secretary
, seeAlso
и все остальные).
1. Наложение работает непосредственно при изменении записей-групп. То есть, если мы попытаемся применить данное наложение к рабочему каталогу, в котором уже имеются записи пользователей и групп, то для этих уже имеющихся записей наложение не сработает и искомых атрибутов обратного членства в группе мы не получим. Поэтому лучше всего продумывать и внедрять настройки memberof
сразу на этапе проектирования каталога. Но если момент уже упущен и мы настраиваем memberof
на рабочем каталоге, то придётся производить дополнительные манипуляции с записями-группами: либо полностью их удалять и добавлять заново, либо удалять и вновь добавлять атрибуты членства этих записей-групп.
2. Результаты работы наложения memberof
локальны для сервера, на котором оно настроено, и не реплицируются. Для корректной работы в реплицируемом окружении необходимо поддерживать аналогичные настройки наложения на всех серверах, участвующих в репликации, а также следить за тем, чтобы до завершения изменения настроек на всех репликах модификации записей-групп в рабочем каталоге не происходило (во избежание рассинхронизации содержимого каталога на разных репликах). Хорошим решением в данном случае будет перевод рабочего каталога в режим "только чтение" на всех серверах-поставщиках репликации до окончания перенастройки.
3. В силу того, что все атрибуты конфигурации наложения memberof могут иметь только одно значение, можно настроить один экземпляр наложения на отслеживание изменений записей-групп, сформированных только на одном конкретном объектном классе. Если же записи-группы в каталоге строятся на разных объектных классах и нам необходимо отслеживать обратное членство для всех таких групп, придётся подключать и настраивать для одной базы данных соответствующее количество наложений memberof
.
Перейдём непосредственно к настройкам наложения. Начинать работу с каталогом мы вновь будем с исходного положения.
Предположим, в нашей компании есть отделы разработки и дизайна, в каталоге они будут представлены группами cn=Developers,ou=Groups,dc=mycompany,dc=ru и cn=Designers,ou=Groups,dc=mycompany,dc=ru (объектный класс groupOfNames
). Для начальников этих подразделений предусмотрены соответствующие роли cn=Chief of Developers,ou=Roles,dc=mycompany,dc=ru и cn=Chief of Designers,ou=Roles,dc=mycompany,dc=ru (объектный класс organizationalRole
). Всё вместе это выглядит следующим образом (файл 002-add_groups_and_roles.ldif
):
# Группа "Разработчики"
dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
cn: Developers
member: uid=ivanov,ou=People,dc=mycompany,dc=ru
member: uid=petrov,ou=People,dc=mycompany,dc=ru
# Группа "Дизайнеры"
dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
cn: Designers
member: uid=antonova,ou=People,dc=mycompany,dc=ru
member: uid=sidorov,ou=People,dc=mycompany,dc=ru
# Контейнер для ролей
dn: ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalUnit
ou: Roles
# Роль "Начальник отдела разработки"
dn: cn=Chief of Developers,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
cn: Chief of Developers
roleOccupant: uid=ivanov,ou=People,dc=mycompany,dc=ru
# Роль "Начальник отдела дизайна"
dn: cn=Chief Of Designers,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
cn: Chief Of Designers
roleOccupant: uid=antonova,ou=People,dc=mycompany,dc=ru
Для удобства настройки библиотек и приложений, взаимодействующих с нашим каталогом, мы решили использовать в учётных записях пользователей атрибут обратного членства в группе memberOf
, и для его автоматического поддержания в каталоге применить наложение memberof
. Как было сказано выше, наложение должно быть настроено до того, как в каталог будут добавлены соответствующие группы. Чтобы обезопасить себя от случайного занесения кем-то из администраторов/пользователей записей-групп, на время перенастройки OpenLDAP переведём рабочий каталог в состояние "только для чтения". Для этого создадим LDIF-файл такого содержания:
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcReadOnly
olcReadOnly: TRUE
Применим его и проверим:
$ ldapmodify -x -D cn=config -W -f ./201-set_readonly.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 olcReadOnly Enter LDAP Password: dn: olcDatabase={1}mdb,cn=config olcReadOnly: TRUE
Во избежание рассинхронизации содержимого каталога в реплицируемом окружении, перед перенастройкой OpenLDAP необходимо перевести в режим "только для чтения" каждый сервер-поставщик репликации (то есть выполнить на них те же манипуляции).
На всякий случай проверим, что рабочий каталог защищён от записи. Попытаемся добавить наши группы:
$ ldapadd -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./002-add_groups_and_roles.ldif Enter LDAP Password: adding new entry "cn=Developers,ou=Groups,dc=mycompany,dc=ru" ldap_add: Server is unwilling to perform (53) additional info: operation restricted
Всё в порядке, можно приступать к настройкам. Для начала подгрузим динамический модуль наложения memberof
:
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: memberof.la
Применим измения и проверим:
$ $ ldapmodify -x -D cn=config -W -f ./101-add_memberof_module.ldif Enter LDAP Password: modifying entry "cn=module{0},cn=config" $ ldapsearch -xLLL -o ldif-wrap=no -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}memberof.la
Перейдём непосредственно к настройкам. Поскольку у нас планируется отслеживать членство в группах, построенных на двух разных объектных классах (groupOfNames
и organizationalRole
), то нам понадобится настроить два экземпляра наложения memberof
. У каждого атрибута настройки данного наложения есть разумное значение по умолчанию, что позволяет не указывать лишние атрибуты. Для объектного класса groupOfNames
мы можем вообще ничего не указывать: по умолчанию наложение срабатывает на записи именно с этим классом и его атрибутом членства member
. А чтобы наложение сработало на объектный класс organizationalRole
, мы должны указать это явно: сам класс в значении атрибута olcMemberOfGroupOC
, а его атрибут членства roleOccupant
— в значении атрибута olcMemberOfMemberAD
. Заодно, в качестве примера, явно укажем атрибут обратного членства в группе (тот самый memberOf
) в значении атрибута olcMemberOfMemberOfAD
. Итоговый LDIF для настройки двух экземпляров наложения:
dn: olcOverlay=memberof,olcDatabase={1}mdb,cn=config
objectClass: olcMemberOf
olcOverlay: memberof
dn: olcOverlay=memberof,olcDatabase={1}mdb,cn=config
objectClass: olcMemberOf
olcOverlay: memberof
olcMemberOfGroupOC: organizationalRole
olcMemberOfMemberAD: roleOccupant
olcMemberOfMemberOfAD: memberOf
Применим его и проверим:
$ ldapadd -x -D cn=config -W -f ./102-add_2_memberof_overlays.ldif Enter LDAP Password: adding new entry "olcOverlay=memberof,olcDatabase={1}mdb,cn=config" adding new entry "olcOverlay=memberof,olcDatabase={1}mdb,cn=config" $ ldapsearch -xLLL -o ldif-wrap=no -D cn=config -W -b olcDatabase={1}mdb,cn=config -s one Enter LDAP Password: dn: olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config objectClass: olcMemberOf olcOverlay: {0}memberof dn: olcOverlay={1}memberof,olcDatabase={1}mdb,cn=config objectClass: olcMemberOf olcOverlay: {1}memberof olcMemberOfGroupOC: organizationalRole olcMemberOfMemberAD: roleOccupant olcMemberOfMemberOfAD: memberOf
На данном этапе настройки завершены, поэтому мы можем снять защиту от записи с рабочего каталога:
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcReadOnly
olcReadOnly: FALSE
Применим наш LDIF и проверим:
$ ldapmodify -x -D cn=config -W -f ./202-unset_readonly.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 olcReadOnly Enter LDAP Password: dn: olcDatabase={1}mdb,cn=config olcReadOnly: FALSE
Наконец, добавим анонсированные в самом начале записи группы:
$ ldapadd -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./002-add_groups_and_roles.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" adding new entry "ou=Roles,dc=mycompany,dc=ru" adding new entry "cn=Chief of Developers,ou=Roles,dc=mycompany,dc=ru" adding new entry "cn=Chief Of Designers,ou=Roles,dc=mycompany,dc=ru"
Убедимся, что наложение memberof
сработало:
$ ldapsearch -xLLL -b dc=mycompany,dc=ru '(|(member=*)(roleOccupant=*)(memberof=*))' member roleOccupant memberof dn: uid=antonova,ou=People,dc=mycompany,dc=ru memberOf: cn=Designers,ou=Groups,dc=mycompany,dc=ru memberOf: cn=Chief Of Designers,ou=Roles,dc=mycompany,dc=ru dn: uid=ivanov,ou=People,dc=mycompany,dc=ru memberOf: cn=Developers,ou=Groups,dc=mycompany,dc=ru memberOf: cn=Chief of Developers,ou=Roles,dc=mycompany,dc=ru dn: uid=petrov,ou=People,dc=mycompany,dc=ru memberOf: cn=Developers,ou=Groups,dc=mycompany,dc=ru dn: uid=sidorov,ou=People,dc=mycompany,dc=ru memberOf: cn=Designers,ou=Groups,dc=mycompany,dc=ru dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru member: uid=ivanov,ou=People,dc=mycompany,dc=ru member: uid=petrov,ou=People,dc=mycompany,dc=ru dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru member: uid=antonova,ou=People,dc=mycompany,dc=ru member: uid=sidorov,ou=People,dc=mycompany,dc=ru dn: cn=Chief of Developers,ou=Roles,dc=mycompany,dc=ru roleOccupant: uid=ivanov,ou=People,dc=mycompany,dc=ru dn: cn=Chief Of Designers,ou=Roles,dc=mycompany,dc=ru roleOccupant: uid=antonova,ou=People,dc=mycompany,dc=ru
Итак, группы добавлены, а обратное членство в этих группах корректно отслеживается наложением memberof
. Напомним, что атрибут memberOf
является операционным и по умолчанию не выводится. Вывод его нужно запрашивать явно:
$ ldapsearch -xLLL -b uid=ivanov,ou=People,dc=mycompany,dc=ru '*' memberOf dn: uid=ivanov,ou=People,dc=mycompany,dc=ru objectClass: inetOrgPerson uid: ivanov cn: Ivan Ivanov sn: Ivanov memberOf: cn=Developers,ou=Groups,dc=mycompany,dc=ru memberOf: cn=Chief of Developers,ou=Roles,dc=mycompany,dc=ru
Прежде чем перейти к расширенным настройкам наложения memberof
, отвлечёмся ненадолго на один интересный вариант ACL OpenLDAP.
Полный вариант условия to
ACL подразумевает, помимо критериев dn
(доступ к записям, начиная с указанной, и в определённом диапазоне) и attrs
(доступ к указанным атрибутам), ещё и критерий filter
. В нём может быть задан любой валидный LDAP-фильтр, который будет использоваться для дополнительного отбора записей среди тех, которые удовлетворили критерию dn
. Если критерий filter
не указан, неявно подразумевается использование фильтра (objectClass=*)
.
Мы можем использовать данный критерий для решения такой практической задачи: предоставить доступ начальникам отделов на правку определённых атрибутов (например, telephoneNumber
и description
) в учётных записях сотрудников своих подразделений. То есть сотрудник, исполняющий роль Chief of Developers, должен получить доступ на правку только учёток пользователей, являющихся членами группы Developers, а сотрудник, исполняющий роль Chief of Designers, должен получить доступ на правку только учёток членов группы Designers. В виде ACL это можно описать следующим образом:
dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {1}to dn.children="ou=People,dc=mycompany,dc=ru"
filter=(memberOf=cn=Developers,ou=Groups,dc=mycompany,dc=ru)
attrs=telephoneNumber,description
by self write
by group/organizationalRole/roleOccupant.exact="cn=Chief Of Developers,ou=Roles,dc=mycompany,dc=ru" write
by * read
-
add: olcAccess
olcAccess: {2}to dn.children="ou=People,dc=mycompany,dc=ru"
filter=(memberOf=cn=Designers,ou=Groups,dc=mycompany,dc=ru)
attrs=telephoneNumber,description
by self write
by group/organizationalRole/roleOccupant.exact="cn=Chief Of Designers,ou=Roles,dc=mycompany,dc=ru" write
by * read
Эти два ACL аналогичны по структуре, поэтому подробнее рассмотрим первый из них. В нём доступ предоставляется к дочерним записям ветки каталога ou=People,dc=mycompany,dc=ru, в которой находятся учётные записи пользователей (строка 4). Согласно фильтру в строке 5, из всех записей в ветке ou=People,dc=mycompany,dc=ru доступ будет предоставляться лишь к тем, в которых атрибут memberOf
имеет значение cn=Developers,ou=Groups,dc=mycompany,dc=ru (члены группы Developers). Наконец, согласно строке 6, в полученном наборе записей доступ будет предоставлен только к атрибутам telephoneNumber
и description
. Далее следуют условия by
(кому и какой уровень доступа будет предоставлен). Условия в строках 7 и 9 стандартны, а в строке 8 определяется полноформатное групповое условие ACL, которое мы подробно разбирали ранее. Согласно этому условию, доступ на запись к атрибутам, указанным в условии to
, предоставляется членам группы Chief Of Developers (что и обговаривалось при постановке задачи).
Применим наши изменения и посмотрим итоговый набор ACL для нашего рабочего каталога:
$ ldapmodify -x -D cn=config -W -f ./103-add_filter_acls.ldif Enter LDAP Password: modifying entry "olcDatabase={1}mdb,cn=config" $ ldapsearch -xLLL -o ldif-wrap=no -D cn=config -W -b olcDatabase={1}mdb,cn=config 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" filter=(memberOf=cn=Developers,ou=Groups,dc=mycompany,dc=ru) attrs=telephoneNumber,description by self write by group/organizationalRole/roleOccupant.exact="cn=Chief Of Developers,ou=Roles,dc=mycompany,dc=ru" write by * read olcAccess: {2}to dn.children="ou=People,dc=mycompany,dc=ru" filter=(memberOf=cn=Designers,ou=Groups,dc=mycompany,dc=ru) attrs=telephoneNumber,description by self write by group/organizationalRole/roleOccupant.exact="cn=Chief Of Designers,ou=Roles,dc=mycompany,dc=ru" write by * read olcAccess: {3}to * by self write by * read
Протестируем работу ACL утилитой slapacl. Проверим, получит ли пользователь ivanov (начальник отдела разработки) доступ к атрибутам telephoneNumber
, description
, а также cn
пользователей petrov (сотрудника этого же отдела) и sidorov (который сотрудником этого отдела не является):
# slapacl -D uid=ivanov,ou=People,dc=mycompany,dc=ru -b uid=petrov,ou=People,dc=mycompany,dc=ru telephoneNumber description cn authcDN: "uid=ivanov,ou=people,dc=mycompany,dc=ru" telephoneNumber: write(=wrscxd) description: write(=wrscxd) cn: read(=rscxd) # slapacl -D uid=ivanov,ou=People,dc=mycompany,dc=ru -b uid=sidorov,ou=People,dc=mycompany,dc=ru telephoneNumber description cn authcDN: "uid=ivanov,ou=people,dc=mycompany,dc=ru" telephoneNumber: read(=rscxd) description: read(=rscxd) cn: read(=rscxd)
Утилите slapacl
для работы требуется считать списки контроля доступа из конфигурационной директории slapd.d
, поэтому мы запускаем её с правами пользователя root
.
Наши тесты отработали, как ожидалось: начальник отдела может поменять атрибуты telephoneNumber
и description
(но не атрибут cn
) в учётках пользователей своего отдела, а в остальных учётках — нет.
Теперь проведём натурные испытания. Попытаемся изменить те самые атрибуты в записях пользователей petrov и sidorov:
dn: uid=petrov,ou=People,dc=mycompany,dc=ru
changetype: modify
add: telephoneNumber
telephoneNumber: 111-22-33
-
add: description
description: Cool Developer
dn: uid=sidorov,ou=People,dc=mycompany,dc=ru
changetype: modify
add: telephoneNumber
telephoneNumber: 222-33-44
-
add: description
description: Cool Designer
Выполним изменения от имени пользователя ivanov:
$ ldapmodify -x -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword -f ./003-test_add_attrs.ldif modifying entry "uid=petrov,ou=People,dc=mycompany,dc=ru" modifying entry "uid=sidorov,ou=People,dc=mycompany,dc=ru" ldap_modify: Insufficient access (50) $ ldapsearch -xLLL -b dc=mycompany,dc=ru '(|(uid=petrov)(uid=sidorov))' dn: uid=petrov,ou=People,dc=mycompany,dc=ru objectClass: inetOrgPerson uid: petrov cn: Petr Petrov sn: Petrov telephoneNumber: 111-22-33 description: Cool Developer dn: uid=sidorov,ou=People,dc=mycompany,dc=ru objectClass: inetOrgPerson uid: sidorov cn: Sidor Sidorov sn: Sidorov
Запись пользователя petrov успешно изменена, а на модификацию записи пользователя sidorov прав не хватило. Наши ACL работают!
А теперь попытаемся произвести те же изменения от имени пользователя antonova (начальник отдела дизайна):
$ ldapmodify -x -D uid=antonova,ou=People,dc=mycompany,dc=ru -w antonovaPassword -c -f ./003-test_add_attrs.ldif modifying entry "uid=petrov,ou=People,dc=mycompany,dc=ru" ldap_modify: Insufficient access (50) modifying entry "uid=sidorov,ou=People,dc=mycompany,dc=ru" $ ldapsearch -xLLL -b dc=mycompany,dc=ru '(|(uid=petrov)(uid=sidorov))' dn: uid=petrov,ou=People,dc=mycompany,dc=ru objectClass: inetOrgPerson uid: petrov cn: Petr Petrov sn: Petrov telephoneNumber: 111-22-33 description: Cool Developer dn: uid=sidorov,ou=People,dc=mycompany,dc=ru objectClass: inetOrgPerson uid: sidorov cn: Sidor Sidorov sn: Sidorov telephoneNumber: 222-33-44 description: Cool Designer
Опция -c
программы ldapmodify
говорит о том, что при возникновении ошибок следует не завершать работу, а переходить к обработке следующей операции модификации.
Всё работает, как ожидалось. Можем переходить к дальнейшим настройкам наложения memberof
.
У наложения memberof
есть два разных механизма поддержания целостности содержимого каталога. Работа первого из них в целом аналогична работе наложения refint (с некоторыми ограничениями), и заключается в обеспечении целостности "ссылочных" атрибутов членства в группе для записей-групп. То есть при удалении/перемещении/переименовании записей-членов группы, значения атрибутов членства в записи-группе могут автоматически меняться соответствующим образом.
Второй механизм — это, если так можно выразится, refint
наоборот, то есть попытка обработать добавление в запись-группу в качестве члена DN несуществующей записи. В такой ситуации наложение memberof
, в зависимости от настроек, может вести себя по-разному. Рассмотрим оба этих механизма подробнее.
За обработку целостности "ссылочных" атрибутов в наложении memberof
отвечает всего один булевый атрибут конфигурации: olcMemberOfRefInt
. То есть её можно либо включить, либо выключить, и по умолчанию она выключена. Если же её включить, то наложение будет следить за целостностью значений атрибута членства в группе, указанного в атрибуте конфигурации olcMemberOfMemberAD
, аналогично тому, как это делает наложение refint. Есть, правда, небольшой нюанс, касающийся известной проблемы соблюдения требований схемы данных для объектных классов groupOfNames
и groupOfUniqueNames
, а именно обязательного наличия в записях-группах с этими классами хотя бы одного члена. В наложении memberof
(в отличие от refinf
) обработка ситуации удаления последнего члена из группы не предусмотрена, и при удалении записи пользователя, DN которой будет единственным значением атрибута членства в группе, наложение memberof
"молча" позволит удалить запись пользователя, при этом DN этой записи так и останется последним значением атрибута членства в записи-группе, и будет ссылаться в никуда, нарушая целостность каталога. К счастью, эта проблема имеет очень простое компромиссное решение: в запись-группу заранее добавляются заведомо фиктивные члены, которые в любом случае останутся последними членами при удалении последнего реального члена:
dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: member
member: cn=dummy_member
dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: member
member: cn=dummy_member
Внесём изменения и проверим:
$ ldapmodify -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./004-add_dummy_members.ldif Enter LDAP Password: modifying entry "cn=Developers,ou=Groups,dc=mycompany,dc=ru" modifying entry "cn=Designers,ou=Groups,dc=mycompany,dc=ru" $ ldapsearch -xLLL -b ou=Groups,dc=mycompany,dc=ru 'member=*' member dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru member: uid=antonova,ou=People,dc=mycompany,dc=ru member: uid=sidorov,ou=People,dc=mycompany,dc=ru member: cn=dummy_member dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru member: uid=ivanov,ou=People,dc=mycompany,dc=ru member: uid=petrov,ou=People,dc=mycompany,dc=ru member: cn=dummy_member
После того, как мы себя "обезопасили", можно включить отслеживание целостности атрибутов членства в группе силами наложения memberof
:
dn: olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config
changetype: modify
add: olcMemberOfRefInt
olcMemberOfRefInt: TRUE
dn: olcOverlay={1}memberof,olcDatabase={1}mdb,cn=config
changetype: modify
add: olcMemberOfRefInt
olcMemberOfRefInt: TRUE
Применим изменения и проверим получившиеся записи; на время изменения настроек наложения переведём рабочий каталог в режим "только для чтения":
$ ldapmodify -x -D cn=config -W -f ./201-set_readonly.ldif Enter LDAP Password: modifying entry "olcDatabase={1}mdb,cn=config" $ ldapmodify -x -D cn=config -W -f ./104-memberof_add_refint.ldif Enter LDAP Password: modifying entry "olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config" modifying entry "olcOverlay={1}memberof,olcDatabase={1}mdb,cn=config" $ ldapsearch -xLLL -D cn=config -W -b olcDatabase={1}mdb,cn=config objectClass=olcMemberOf Enter LDAP Password: dn: olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config objectClass: olcMemberOf olcOverlay: {0}memberof olcMemberOfRefInt: TRUE dn: olcOverlay={1}memberof,olcDatabase={1}mdb,cn=config objectClass: olcMemberOf olcOverlay: {1}memberof olcMemberOfGroupOC: organizationalRole olcMemberOfMemberAD: roleOccupant olcMemberOfMemberOfAD: memberOf olcMemberOfRefInt: TRUE $ ldapmodify -x -D cn=config -W -f ./202-unset_readonly.ldif Enter LDAP Password: modifying entry "olcDatabase={1}mdb,cn=config"
Протестируем работу наложения в новом режиме. Предположим, что Ваня Иванов уволился из компании, а Тоня Антонова вышла замуж и сменила фамилию. LDIF для внесения изменений в каталог:
# Удаление записи ivanov
dn: uid=ivanov,ou=People,dc=mycompany,dc=ru
changetype: delete
# Переименование записи antonova
dn: uid=antonova,ou=People,dc=mycompany,dc=ru
changetype: modrdn
newrdn: uid=zhukova
deleteoldrdn: 1
# Модификация записи zhukova
dn: uid=zhukova,ou=People,dc=mycompany,dc=ru
changetype: modify
replace: cn
cn: Antonina Zhukova
-
replace: sn
sn: Zhukova
-
replace: userPassword
userPassword: zhukovaPassword
Применим изменения и проверим, что получилось с нашими группами и ролями:
$ ldapmodify -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./005-test_refint.ldif Enter LDAP Password: deleting entry "uid=ivanov,ou=People,dc=mycompany,dc=ru" modifying rdn of entry "uid=antonova,ou=People,dc=mycompany,dc=ru" modifying entry "uid=zhukova,ou=People,dc=mycompany,dc=ru" $ ldapsearch -xLLL -b dc=mycompany,dc=ru '(|(objectClass=groupOfNames)(objectClass=organizationalRole))' member roleOccupant dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru member: uid=petrov,ou=People,dc=mycompany,dc=ru member: cn=dummy_member dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru member: uid=sidorov,ou=People,dc=mycompany,dc=ru member: cn=dummy_member member: uid=zhukova,ou=People,dc=mycompany,dc=ru dn: cn=Chief of Developers,ou=Roles,dc=mycompany,dc=ru dn: cn=Chief Of Designers,ou=Roles,dc=mycompany,dc=ru roleOccupant: uid=zhukova,ou=People,dc=mycompany,dc=ru
При удалении из каталога записи uid=ivanov,ou=People,dc=mycompany,dc=ru были удалены соответствующие значения атрибутов членства из группы Developers и роли Chief of Developers. При переименовании записи uid=antonova,ou=People,dc=mycompany,dc=ru были изменены соответствующие значения атрибутов членства в группе Designers и роли Chief of Designers. Наложение memberof
с настройками поддержания целостности атрибутов членства в группе работает, как и ожидалось.
Вывод: если нам необходимо отслеживать целостность атрибутов членства в группе и у нас уже включено наложение memberof
, добавлять в стек наложений ещё и наложение refint
нет смысла — memberof
прекрасно справляется с этой задачей самостоятельно. Если же нам нужно дополнительно отслеживать целостность и других "ссылочных" атрибутов (owner
, meneger
, seeAlso
и т.п.), то без включения наложения refint не обойтись.
Предположим, мы хотим добавить в качестве члена в группу Developers DN записи, которой нет в нашем каталоге:
dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: member
member: uid=volkov,ou=People,dc=mycompany,dc=ru
Как в этом случае поведёт себя наложение memberof
? Это зависит от настройки параметра обработки "висячих" записей, который задаётся значением атрибута olcMemberOfDangling
наложения. У этого параметра три фиксированных значения: ignore
, drop
и error
. В варианте ignore
(используется по умолчанию) несуществующий член "молча" (без выдачи какого-либо сообщения) добавится в запись-группу. Поскольку этот вариант используется по умолчанию, мы смогли на предыдущем этапе беспрепятственно добавить в наши группы членов cn=dummy_member. В варианте drop
при попытке добавления несуществующего члена наложение "молча" отбросит эту операцию, то есть член не будет добавлен, но и ошибки сгенерировано не будет. Наконец, в варианте error
попытка добавления несуществующего члена приведёт к аварийному завершению операции с выдачей сообщения об ошибке. По умолчанию это будет ошибка с кодом 19 (constraintViolation
), но её можно переопределить, задав числовой код или строковое обозначение ошибки из приложения A.2 RFC 4511 в качестве значения атрибута olcMemberOfDanglingError
.
Какой вариант данной настройки использовать для того или иного каталога, зависит от задач, которые ставятся перед этим каталогом. В качестве примера мы приведём самый строгий вариант настройки: error
, как максимально обеспечивающий поддержание целостности содержимого каталога. Заодно, опять же для примера, при попытке добавления несуществующего члена мы будем выдавать ошибку 53 (unwillingToPerform
). LDIF для внесения изменений в настройки наложений memberof
будет выглядеть следующим образом:
dn: olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config
changetype: modify
add: olcMemberOfDangling
olcMemberOfDangling: error
-
add: olcMemberOfDanglingError
olcMemberOfDanglingError: 53
dn: olcOverlay={1}memberof,olcDatabase={1}mdb,cn=config
changetype: modify
add: olcMemberOfDangling
olcMemberOfDangling: error
-
add: olcMemberOfDanglingError
olcMemberOfDanglingError: unwillingToPerform
Обратите внимание на строки 7 и 15 данного LDIF. В качестве значения атрибута olcMemberOfDanglingError
указана одна и та же ошибка, только в первый раз в виде кода, а во второй — в виде строкового обозначения.
Применим изменения и проверим получившиеся записи; на время изменения настроек наложения переведём рабочий каталог в режим "только для чтения":
$ ldapmodify -x -D cn=config -W -f ./201-set_readonly.ldif Enter LDAP Password: modifying entry "olcDatabase={1}mdb,cn=config" $ ldapmodify -x -D cn=config -W -f ./105-memberof_add_dangling.ldif Enter LDAP Password: modifying entry "olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config" modifying entry "olcOverlay={1}memberof,olcDatabase={1}mdb,cn=config" $ ldapsearch -xLLL -D cn=config -W -b olcDatabase={1}mdb,cn=config objectClass=olcMemberOf Enter LDAP Password: dn: olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config objectClass: olcMemberOf olcOverlay: {0}memberof olcMemberOfRefInt: TRUE olcMemberOfDangling: error olcMemberOfDanglingError: 53 dn: olcOverlay={1}memberof,olcDatabase={1}mdb,cn=config objectClass: olcMemberOf olcOverlay: {1}memberof olcMemberOfGroupOC: organizationalRole olcMemberOfMemberAD: roleOccupant olcMemberOfMemberOfAD: memberOf olcMemberOfRefInt: TRUE olcMemberOfDangling: error olcMemberOfDanglingError: unwillingToPerform $ ldapmodify -x -D cn=config -W -f ./202-unset_readonly.ldif Enter LDAP Password: modifying entry "olcDatabase={1}mdb,cn=config"
Протестируем работу наложения в новом режиме. Попытаемся добавить несуществующего члена в группу Developers:
$ ldapmodify -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./006-test_add_nonexistent_member.ldif Enter LDAP Password: modifying entry "cn=Developers,ou=Groups,dc=mycompany,dc=ru" ldap_modify: Server is unwilling to perform (53) additional info: adding non-existing object as group member
Операция завершилась неудачно с выдачей заданной нами ошибки 53. В качестве дополнительной информации сообщается о добавлении несуществующего объекта в члены группы. Наложение memberof
работает, как и ожидалось.
В ходе этого урока мы познакомились с различными вариантами настройки наложения memberof
. Кроме базового функционала поддержания признака обратного членства в группе в записи пользователя, состоящего в этой группе, оно способно также разными способами поддерживать целостность содержимого каталога. Кроме того, мы рассмотрели критерий filter
условия to
ACL OpenLDAP, с помощью которого можно накладывать дополнительные ограничения при отборе записей, к которым будет применяться правило контроля доступа. В частности, мы рассмотрели фильтрацию записи пользователей по их принадлежности группе с помощью поддерживаемого наложением memberof
атрибута memberOf
. Наконец, мы рассмотрели пример перевода рабочего каталога в режим "только для чтения" и обратно. Итоговые настройки каталога 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" filter=(memberOf=cn=Developers,ou=Groups,dc=mycompany,dc=ru) attrs=telephoneNumber,description by self write by group/organizationalRole/roleOccupant.exact="cn=Chief Of Developers,ou=Roles,dc=mycompany,dc=ru" write by * read olcAccess: {2}to dn.children="ou=People,dc=mycompany,dc=ru" filter=(memberOf=cn=Designers,ou=Groups,dc=mycompany,dc=ru) attrs=telephoneNumber,description by self write by group/organizationalRole/roleOccupant.exact="cn=Chief Of Designers,ou=Roles,dc=mycompany,dc=ru" write by * read olcAccess: {3}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 olcReadOnly: FALSE dn: olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config objectClass: olcMemberOf olcOverlay: {0}memberof olcMemberOfRefInt: TRUE olcMemberOfDangling: error olcMemberOfDanglingError: 53 dn: olcOverlay={1}memberof,olcDatabase={1}mdb,cn=config objectClass: olcMemberOf olcOverlay: {1}memberof olcMemberOfGroupOC: organizationalRole olcMemberOfMemberAD: roleOccupant olcMemberOfMemberOfAD: memberOf olcMemberOfRefInt: TRUE olcMemberOfDangling: error olcMemberOfDanglingError: unwillingToPerform