Статические группы: наложение memberof

Содержание

Наложение memberof

Как сказано в man-странице наложения, оно предназначено для того, чтобы автоматически поддерживать обратное членство в группе. На практике это означает, что при использовании этого наложения между группой и её записями-членами создаётся взаимосвязь: при добавлении DN записи в качестве члена в некоторую группу в саму эту запись добавляется атрибут (по умолчанию это операционный атрибут memberOf), содержащий в качестве значения DN записи-группы. При удалении же DN записи из членов группы из самой записи будет удалён атрибут, значение которого содержит DN записи-группы.

Такая взаимосвязь позволяет администраторам строить довольно удобные фильтры для поиска записей пользователей, принадлежащих какой либо группе, например:

(&(uid=*)(memberOf=cn=MyPrettyGroup,ou=Groups,dc=mycompany,dc=ru))

Такие фильтры часто применяются для настройки библиотек/приложений, осуществляющих аутентификацию/авторизацию пользователей с использованием каталога. В данном случае они позволяют на ранней стадии выполнить отбор записей пользователей, которым разрешено выполнять аутентификацию, на основе принадлежности той или иной группе. Кроме того, подобные фильтры можно использовать и при настройке самого OpenLDAP, чтобы организовать взаимодействие между статическими и динамическими группами (списками).

Выглядит всё просто и удобно. Но есть нюансы, касающиеся как построения фильтров, так и использования самого наложения memberof.

Важное замечание по атрибутам с синтаксисом DN

Администраторы часто склонны переоценивать возможности построения фильтров на основе атрибутов с синтаксисом 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 и все остальные).

Важные замечания по использованию наложения memberof

1. Наложение работает непосредственно при изменении записей-групп. То есть, если мы попытаемся применить данное наложение к рабочему каталогу, в котором уже имеются записи пользователей и групп, то для этих уже имеющихся записей наложение не сработает и искомых атрибутов обратного членства в группе мы не получим. Поэтому лучше всего продумывать и внедрять настройки memberof сразу на этапе проектирования каталога. Но если момент уже упущен и мы настраиваем memberof на рабочем каталоге, то придётся производить дополнительные манипуляции с записями-группами: либо полностью их удалять и добавлять заново, либо удалять и вновь добавлять атрибуты членства этих записей-групп.

2. Результаты работы наложения memberof локальны для сервера, на котором оно настроено, и не реплицируются. Для корректной работы в реплицируемом окружении необходимо поддерживать аналогичные настройки наложения на всех серверах, участвующих в репликации, а также следить за тем, чтобы до завершения изменения настроек на всех репликах модификации записей-групп в рабочем каталоге не происходило (во избежание рассинхронизации содержимого каталога на разных репликах). Хорошим решением в данном случае будет перевод рабочего каталога в режим "только чтение" на всех серверах-поставщиках репликации до окончания перенастройки.

3. В силу того, что все атрибуты конфигурации наложения memberof могут иметь только одно значение, можно настроить один экземпляр наложения на отслеживание изменений записей-групп, сформированных только на одном конкретном объектном классе. Если же записи-группы в каталоге строятся на разных объектных классах и нам необходимо отслеживать обратное членство для всех таких групп, придётся подключать и настраивать для одной базы данных соответствующее количество наложений 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: person
objectClass: uidObject
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.

Фильтр отбора записей в 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: person
objectClass: uidObject
uid: petrov
cn: Petr Petrov
sn: Petrov
telephoneNumber: 111-22-33
description: Cool Developer

dn: uid=sidorov,ou=People,dc=mycompany,dc=ru
objectClass: person
objectClass: uidObject
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: person
objectClass: uidObject
uid: petrov
cn: Petr Petrov
sn: Petrov
telephoneNumber: 111-22-33
description: Cool Developer

dn: uid=sidorov,ou=People,dc=mycompany,dc=ru
objectClass: person
objectClass: uidObject
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
Pro-LDAP.ru 2013-2018 г. Последнее изменение страницы — 9 мая 2018 г. Вопросы и предложения принимаются на форуме проекта.