Статические группы: групповые условия в ACL и наложение refint

Содержание

Групповые условия в ACL OpenLDAP

На этом этапе мы попробуем разобраться с действиями системного администратора при ведении групповых записей в каталоге, а также с тем, как использовать группы для разграничения доступа к самому каталогу на базе OpenLDAP.

Сначала добавим в каталог две группы: OpenLDAP Admins и OpenLDAP Operators. Членам первой группы будет разрешено менять пароли учётных записей пользователей в каталоге, а членам второй — изменять любые данные в каталоге (за исключением паролей пользователей). Построим записи групп на объектном классе groupOfNames:

# Группа "Администраторы каталога"
dn: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
cn: OpenLDAP Admins
member: uid=ivanov,ou=People,dc=mycompany,dc=ru

# Группа "Операторы каталога"
dn: cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
cn: OpenLDAP Operators
member: uid=antonova,ou=People,dc=mycompany,dc=ru

Добавим их в каталог:

$ ldapadd -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./002-add_groups.ldif
Enter LDAP Password: 
adding new entry "cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru"

adding new entry "cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru"

Теперь мы можем усовершенствовать наши ACL (относительно исходных):

dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
olcAccess: to attrs=userPassword
  by self write
  by group.exact="cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru" write
  by anonymous auth
  by * none
olcAccess: to *
  by self write
  by group.exact="cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru" write
  by * read

Обратите внимание, что по правилам переноса строк в LDIF строка, являющаяся продолжением предыдущей, должна начинаться с пробельного символа, поэтому в данном случае, для сохранения правильного формата ACL, перед условиями by ACL мы выставили два пробела.

В оба имеющихся правила мы добавили стандартное групповое условие by в ACL (строки 6 и 11). Модификатор .exact после ключевого слова group говорит о том, что в групповом условии указано полное уникальное имя (DN) записи группы. Этот модификатор применяется по умолчанию, его можно было не задавать явно.

Согласно групповому условию в первом ACL (строка 6) члены группы cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru, то есть те записи, DN которых перечислены в атрибутах member этой группы, получат право на изменение (write) атрибута userPassword. А согласно групповому условию во втором ACL (строка 11) члены группы cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru получат право на изменение всего остального содержимого каталога (за исключением атрибута userPassword).

Применим изменения и проверим:

$ ldapmodify -D cn=config -W -f ./101-modify_acl_1.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 group.exact="cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru" write by anonymous auth by * none
olcAccess: {1}to * by self write by group.exact="cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru" write by * read

А теперь протестируем наши ACL утилитой slapacl. Пользователь, авторизовавшийся от имени учётной записи uid=ivanov,ou=People,dc=mycompany,dc=ru, как член группы cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru должен получить право на изменение атрибута userPassword, а пользователь uid=antonova,ou=People,dc=mycompany,dc=ru, не являющийся членом этой группы, — вообще не иметь никакого доступа (кроме userPassword своей записи). Убедимся в этом, протестировав доступ к атрибуту userPassword пользователя uid=petrov,ou=People,dc=mycompany,dc=ru:

# slapacl -D uid=ivanov,ou=People,dc=mycompany,dc=ru -b uid=petrov,ou=People,dc=mycompany,dc=ru userPassword
authcDN: "uid=ivanov,ou=people,dc=mycompany,dc=ru"
userPassword: write(=wrscxd)
# slapacl -D uid=antonova,ou=People,dc=mycompany,dc=ru -b uid=petrov,ou=People,dc=mycompany,dc=ru userPassword
authcDN: "uid=antonova,ou=people,dc=mycompany,dc=ru"
userPassword: none(=0)

Утилите slapacl для работы требуется считать списки контроля доступа из конфигурационной директории slapd.d, поэтому мы запускаем её с правами пользователя root.

Всё отлично. Теперь протестируем обратную ситуацию: согласно второму ACL пользователь uid=antonova,ou=People,dc=mycompany,dc=ru, являющийся членом группы cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru, должен иметь право изменять любую запись в каталоге (кроме атрибута userPassword), а пользователь uid=ivanov,ou=People,dc=mycompany,dc=ru, не являющийся членом этой группы, — только читать содержимое записей (свою собственную запись он, конечно же, может изменять). Убедимся в этом, протестировав доступ к атрибуту cn пользователя uid=petrov,ou=People,dc=mycompany,dc=ru:

# slapacl -D uid=antonova,ou=People,dc=mycompany,dc=ru -b uid=petrov,ou=People,dc=mycompany,dc=ru cn
authcDN: "uid=antonova,ou=people,dc=mycompany,dc=ru"
cn: write(=wrscxd)
# slapacl -D uid=ivanov,ou=People,dc=mycompany,dc=ru -b uid=petrov,ou=People,dc=mycompany,dc=ru cn
authcDN: "uid=ivanov,ou=people,dc=mycompany,dc=ru"
cn: read(=rscxd)

Наши ACL работают. Но, согласитесь, оставлять за администраторами каталога лишь право изменять пароли выглядит нелогично. Можно поступить просто и добавить пользователя uid=ivanov,ou=People,dc=mycompany,dc=ru в группу cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru:

dn: cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: member
member: uid=ivanov,ou=People,dc=mycompany,dc=ru

Однако не будем спешить и вспомним о том, что умный администратор — это "ленивый" администратор. В ACL OpenLDAP есть понятие набора (set), позволяющее создавать различные сложные варианты контроля доступа. В данном случае нам подойдёт вариант рекурсивной проверки "вложенных" групп. Сначала мы добавим в качестве члена в группу cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru "вложенную" группу cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru:

dn: cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: member
member: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru

Применим изменения от имени пользователя antonova (у которого есть соответствующие права на редактирование каталога):

$ ldapmodify -D uid=antonova,ou=People,dc=mycompany,dc=ru -w antonovaPassword -f ./003-add_group_to_group.ldif
modifying entry "cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru"

$ ldapsearch -xLLL -b 'cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru' member
dn: cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru
member: uid=antonova,ou=People,dc=mycompany,dc=ru
member: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru

Теперь модифицируем наш второй ACL:

dn: olcDatabase={1}mdb,cn=config
changetype: modify
delete: olcAccess
olcAccess: {1}
-
add: olcAccess
olcAccess: {1}to *
  by self write
  by set="[cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru]/member* & user" write
  by * read

Интересующее нас определение набора находится в условии by в 9-ой строке. Его можно "расшифровать" так: в качестве исходной точки берётся группа cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru, ищутся атрибуты member в ней, а также рекурсивно вниз во всех тех записях, которые были обнаружены в атрибутах member первоначальной и последующих записей (необходимость рекурсивного поиска обозначается знаком "звёздочки" *). В данном случае в итоге такого рекурсивного обхода у нас появляется набор из трёх записей (выдержка из log-файла slapd, запущенного в режиме журналирования acl):

ACL set[0]=uid=antonova,ou=people,dc=mycompany,dc=ru
ACL set[1]=cn=openldap admins,ou=groups,dc=mycompany,dc=ru
ACL set[2]=uid=ivanov,ou=people,dc=mycompany,dc=ru

После построения набора определяется, входит ли в него DN пользователя, прошедшего аутентификацию (подставляется вместо ключевого слова условий-наборов user). Если да, этому пользователю будут предоставлены соответствующие права. Подробно алгоритм работы данного набора разбирается в разделе 8.5.1 руководства администратора OpenLDAP 2.4.

Применим изменения и проверим (от имени rootDN конфигурационного каталога cn=config):

$ ldapmodify -D cn=config -W -f ./102-modify_acl_2.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 group.exact="cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru" write by anonymous auth by * none
olcAccess: {1}to * by self write by set="[cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru]/member* & user" write by * read

Протестируем работу ACL утилитой slapacl. Проверим, получит ли пользователь uid=ivanov,ou=People,dc=mycompany,dc=ru доступ к атрибуту cn пользователя uid=petrov,ou=People,dc=mycompany,dc=ru:

# slapacl -D uid=ivanov,ou=People,dc=mycompany,dc=ru -b uid=petrov,ou=People,dc=mycompany,dc=ru cn
authcDN: "uid=ivanov,ou=people,dc=mycompany,dc=ru"
cn: write(=wrscxd)

Да, всё работает, как ожидалось. Для убедительности проведём реальную проверку, изменив атрибут cn пользователя uid=petrov,ou=People,dc=mycompany,dc=ru:

dn: uid=petrov,ou=People,dc=mycompany,dc=ru
changetype: modify
replace: cn
cn: Petr Petrovich Petrov

Изменения выполняем от имени пользователя ivanov:

$ ldapmodify -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword -f ./004-modify_petrov.ldif
modifying entry "uid=petrov,ou=People,dc=mycompany,dc=ru"

$ ldapsearch -xLLL -b uid=petrov,ou=People,dc=mycompany,dc=ru
dn: uid=petrov,ou=People,dc=mycompany,dc=ru
objectClass: person
objectClass: uidObject
uid: petrov
sn: Petrov
cn: Petr Petrovich Petrov

Всё прошло успешно. Теперь администраторы каталога будут получать тот же доступ на изменение записей, что и операторы, и, сверх того, смогут изменять пароли учётных записей пользователей.

Но вернёмся к нашим групповым условиям ACL. Группы в предыдущих примерах строились на объектном классе groupOfNames, члены в них указывались в атрибутах member, и slapd при оценке ACL принимал это как само собой разумеющееся. Но что, если у нас группы строятся на совсем другом объектном классе и атрибутах? Для slapd это тоже не проблема. Для нашего следующего примера заведём "роли", то есть группы на объектном классе organizationalRole:

# Контейнер для ролей
dn: ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalUnit
ou: Roles

# Роль "Системный администратор"
dn: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
cn: SysAdmin
roleOccupant: uid=ivanov,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=petrov,ou=People,dc=mycompany,dc=ru

# Роль "Разработчик"
dn: cn=Developer,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
cn: Developer
roleOccupant: uid=antonova,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=sidorov,ou=People,dc=mycompany,dc=ru

Мы добавляем контейнер для ролей (чтобы не путаться в ролях и группах) и две роли: системный администратор и разработчик. Применим наши изменения:

$ ldapadd -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword -f ./005-add_roles.ldif
adding new entry "ou=Roles,dc=mycompany,dc=ru"

adding new entry "cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru"

adding new entry "cn=Developer,ou=Roles,dc=mycompany,dc=ru"

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

dn: olcDatabase={1}mdb,cn=config
changetype: modify
delete: olcAccess
olcAccess: {0}
-
add: olcAccess
olcAccess: {0}to dn.children="ou=People,dc=mycompany,dc=ru" attrs=userPassword
  by self write
  by group.exact="cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru" write
  by group/organizationalRole/roleOccupant.exact="cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru" write
  by anonymous auth
  by * none

Мы сузили диапазон действия нашего ACL записями в ветке ou=People,dc=mycompany,dc=ru (строка 7), поскольку учётные записи пользователей находятся именно там. Но основное изменение — это условие by в строке 10. По сути, это развёрнутый формат стандартного группового условия, в котором явно указывается, запись с каким объектным классом считать группой, и значения каких атрибутов считать членами этой группы. Условие в строке 10 говорит о том, что члены построенной на объектном классе organizationalRole группы cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru (DN которой задано полностью, на что указывает модификатор .exact), то есть записи, DN которых перечислены в значениях атрибута roleOccupant этой группы, получат права на изменения атрибута userPassword записей в ветке ou=People,dc=mycompany,dc=ru (строка 7). Групповое условие by в строке 9 точно такое же, просто объектный класс groupOfNames и атрибут member считаются значениями по умолчанию, и поэтому опущены.

Полный формат группового условия by даёт возможность использовать в качестве группы записи практически с любыми объектными классами и любыми атрибутами с синтаксисом DN, например, запись с классом inetOrgPerson, членами которой будут считаться значения атрибута secretary. Но прибегать к таким ухищрениям нецелесообразно, если, конечно, Вы не собираетесь всех запутать.

Применим изменения и проверим:

$ ldapmodify -D cn=config -W -f ./103-modify_acl_3.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 dn.children="ou=People,dc=mycompany,dc=ru" attrs=userPassword by self write by group.exact="cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru" write by group/organizationalRole/roleOccupant.exact="cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru" write by anonymous auth by * none
olcAccess: {1}to * by self write by set="[cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru]/member* & user" write by * read

Убедимся в корректности работы ACL с помощью утилиты slapacl. Проверим, получит ли пользователь uid=petrov,ou=People,dc=mycompany,dc=ru (как член группы cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru) доступ к атрибуту userPassword пользователя uid=antonova,ou=People,dc=mycompany,dc=ru:

# slapacl -D uid=petrov,ou=People,dc=mycompany,dc=ru -b uid=antonova,ou=People,dc=mycompany,dc=ru userPassword
authcDN: "uid=petrov,ou=people,dc=mycompany,dc=ru"
userPassword: write(=wrscxd)

Всё работает, как ожидалось.

Теперь предположим, что разработчики компании в данный момент работают над телефонным справочником, для чего им требуются права на изменение атрибутов telephoneNumber и description учётных записей пользователей. Чтобы предоставить им такие права на уровне каталога, введём новый ACL:

dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {1}to dn.regex="^uid=[^,]+,ou=People,(dc=[^,]+,dc=[^,]+)$" attrs=telephoneNumber,description
  by self write
  by group.expand="cn=OpenLDAP Operators,ou=Groups,$1" write
  by group/organizationalRole/roleOccupant.expand="cn=Developer,ou=Roles,$1" write
  by * none

Этот ACL будет вторым (индекс 1), он поместится между двумя существующими. Мы сознательно несколько избыточно усложнили его структуру, чтобы продемострировать использование регулярных выражений и модификатора подстановки .expand. В строке 4 мы задаём условие, что контроль доступа будет осуществляться над атрибутами telephoneNumber и description записей каталога, DN которых соответствуют указанному регулярному выражению. В данном случае регулярное выражение говорит о том, что RDN записи должно быть построено на значении атрибута uid, RDN первого предка должно быть ou=People, а RDN двух последующих предков должны быть построены на значении атрибута dc (эти два RDN, за счёт заключения их в скобки, будут помещены в переменную $1), и на этом DN записи заканчивается. Строки 5 и 6 нужны для того, чтобы контроль над этими атрибутами не был потерян самим пользователем и членами группы OpenLDAP Operators (обратите внимание на использование модификатора .expand и подстановки значения переменной $1 в DN группы в строке 6). Строка 7 — это уже знакомая нам полная форма группового условия by, просто немного видоизменённая модификатором .expand и подстановкой в DN группы значения переменной $1. Смысл строки 8 очевиден.

Применим изменения и проверим:

$ ldapmodify -D cn=config -W -f ./104-modify_acl_4.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 dn.children="ou=People,dc=mycompany,dc=ru" attrs=userPassword by self write by group.exact="cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru" write by group/organizationalRole/roleOccupant.exact="cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru" write by anonymous auth by * none
olcAccess: {1}to dn.regex="^uid=[^,]+,ou=People,(dc=[^,]+,dc=[^,]+)$" attrs=telephoneNumber,description by self write by group.expand="cn=OpenLDAP Operators,ou=Groups,$1" write by group/organizationalRole/roleOccupant.expand="cn=Developer,ou=Roles,$1" write by * none
olcAccess: {2}to * by self write by set="[cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru]/member* & user" write by * read

А теперь убедимся, что пользователь uid=sidorov,ou=People,dc=mycompany,dc=ru, как член группы cn=Developer,ou=Roles,dc=mycompany,dc=ru, получит доступ к атрибутам telephoneNumber, description, а заодно и к атрибуту cn пользователя uid=ivanov,ou=People,dc=mycompany,dc=ru:

# slapacl -D uid=sidorov,ou=People,dc=mycompany,dc=ru -b uid=ivanov,ou=People,dc=mycompany,dc=ru telephoneNumber description cn
authcDN: "uid=sidorov,ou=people,dc=mycompany,dc=ru"
telephoneNumber: write(=wrscxd)
description: write(=wrscxd)
cn: read(=rscxd)

По второму ACL получены права на изменение атрибутов telephoneNumber и description, а по третьему — на чтение атрибута cn.

Наконец, удостоверимся, что уже рассмотренное нами условие by с использованием наборов работает также и с нестандартными группами. Предоставим для пользователей, исполняющих роль SysAdmin те же права, что и для исполняющих роль Developer. Для этого добавим DN роли cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru в качестве значения атрибута roleOccupant в запись cn=Developer,ou=Roles,dc=mycompany,dc=ru:

dn: cn=Developer,ou=Roles,dc=mycompany,dc=ru
changetype: modify
add: roleOccupant
roleOccupant: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru

Применим изменения и проверим:

$ ldapmodify -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword -f ./006-add_role_to_role.ldif 
modifying entry "cn=Developer,ou=Roles,dc=mycompany,dc=ru"

$ ldapsearch -xLLL -b 'cn=Developer,ou=Roles,dc=mycompany,dc=ru' roleOccupant
dn: cn=Developer,ou=Roles,dc=mycompany,dc=ru
roleOccupant: uid=antonova,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=sidorov,ou=People,dc=mycompany,dc=ru
roleOccupant: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru

Модифицируем наш второй ACL. Теперь мы будем рекурсивно искать значения атрибута roleOccupant в записях, начиная с записи роли Developer (обратите внимание, что модификатор .expand и подстановка переменных работает и в наборах):

dn: olcDatabase={1}mdb,cn=config
changetype: modify
delete: olcAccess
olcAccess: {1}
-
add: olcAccess
olcAccess: {1}to dn.regex="^uid=[^,]+,ou=People,(dc=[^,]+,dc=[^,]+)$" attrs=telephoneNumber,description
  by self write
  by group.expand="cn=OpenLDAP Operators,ou=Groups,$1" write
  by set.expand="[cn=Developer,ou=Roles,$1]/roleOccupant* & user" write
  by * none

Применим изменения и проверим:

$ ldapmodify -D cn=config -W -f ./105-modify_acl_5.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 dn.children="ou=People,dc=mycompany,dc=ru" attrs=userPassword by self write by group.exact="cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru" write by group/organizationalRole/roleOccupant.exact="cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru" write by anonymous auth by * none
olcAccess: {1}to dn.regex="^uid=[^,]+,ou=People,(dc=[^,]+,dc=[^,]+)$" attrs=telephoneNumber,description by self write by group.expand="cn=OpenLDAP Operators,ou=Groups,$1" write by set.expand="[cn=Developer,ou=Roles,$1]/roleOccupant* & user" write by * none
olcAccess: {2}to * by self write by set="[cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru]/member* & user" write by * read

Осталось убедиться в нашей правоте. Удостоверимся, что пользователь uid=petrov,ou=People,dc=mycompany,dc=ru, как член группы cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru, "вложенной" в группу cn=Developer,ou=Roles,dc=mycompany,dc=ru, получит доступ к атрибутам telephoneNumber, description и cn пользователя uid=ivanov,ou=People,dc=mycompany,dc=ru:

# slapacl -D uid=petrov,ou=People,dc=mycompany,dc=ru -b uid=ivanov,ou=People,dc=mycompany,dc=ru telephoneNumber description cn
authcDN: "uid=petrov,ou=people,dc=mycompany,dc=ru"
telephoneNumber: write(=wrscxd)
description: write(=wrscxd)
cn: read(=rscxd)

Всё работает, как ожидалось. Таким образом, мы рассмотрели использование разных вариантов групповых условий by ACL slapd, а также условия by с наборами для рекурсивного поиска членов "вложенных" групп. Осталось рассмотреть ещё один интересный вариант ACL, связанный с группами: управление значениями атрибутов записи, в которых могут находиться DN других записей. Звучит запутанно, проще продемонстрировать на примере. Типичная задача с таким типом ACL формулируется так: есть некоторая группа, требуется позволить пользователям добавлять себя в члены этой группы (и удалять себя из её членов). К примеру, на корпоративном портале есть лента новостей, для доступа к ней нужно состоять в группе News Readers:

dn: cn=News Readers,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
cn: News Readers
member: cn=dummy_member

Поскольку группа пока пуста, а атрибут member обязателен для объектного класса groupOfNames, пришлось ввести фиктивного члена cn=dummy_member (это общепринятая практика для классов groupOfNames и groupOfUniqueNames). Добавим эту запись в каталог:

$ ldapadd -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword -f ./007-add_news_readers_group.ldif 
adding new entry "cn=News Readers,ou=Groups,dc=mycompany,dc=ru"

Чтобы любой желающий мог получить доступ к корпоративным новостям, предоставим пользователям добавлять и удалять DN своих записей в атрибут member нашей группы. Составим такой ACL:

dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {0}to dn.base="cn=News Readers,ou=Groups,dc=mycompany,dc=ru" attrs=member
  by dnattr=member selfwrite

Этот ACL будет добавлен перед всеми уже существующими (индекс 0). Согласно строке 4, мы ограничиваем доступ к атрибуту member группы News Readers. Самая интересная для нас строка 5. Условие by в ней говорит о том, что записи, DN которых перечислены в атрибуте member (формулировка dnattr=member) получат доступ уровня write только для добавления/удаления своего собственного DN (формулировка selfwrite) в атрибут member группы News Readers (согласно условию to в строке 4).

Чтобы члены группы OpenLDAP Operators не утратили контроль над записью группы News Readers, мы можем, как это делалось во всех предыдущих примерах, добавить им явно привилегии write:

dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {0}to dn.base="cn=News Readers,ou=Groups,dc=mycompany,dc=ru" attrs=member
  by dnattr=member selfwrite
  by group.exact="cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru" write

Но в этот раз мы поступим иначе, и укажем продолжать обход ACL, если было найдено совпадение с условием to, но не найдено ни одного совпадения с условиями by:

dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {0}to dn.base="cn=News Readers,ou=Groups,dc=mycompany,dc=ru" attrs=member
  by dnattr=member selfwrite
  by * break

В этом случае, если член группы OpenLDAP Operators захочет модифицировать атрибут member группы News Readers, он сможет это сделать, миновав этот ACL и получив привилегии в последнем из списка ACL. Применим наши изменения и проверим результат:

$ ldapmodify -D cn=config -W -f ./106-modify_acl_6.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 dn.base="cn=News Readers,ou=Groups,dc=mycompany,dc=ru" attrs=member by dnattr=member selfwrite by * break
olcAccess: {1}to dn.children="ou=People,dc=mycompany,dc=ru" attrs=userPassword by self write by group.exact="cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru" write by group/organizationalRole/roleOccupant.exact="cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru" write by anonymous auth by * none
olcAccess: {2}to dn.regex="^uid=[^,]+,ou=People,(dc=[^,]+,dc=[^,]+)$" attrs=telephoneNumber,description by self write by group.expand="cn=OpenLDAP Operators,ou=Groups,$1" write by set.expand="[cn=Developer,ou=Roles,$1]/roleOccupant* & user" write by * none
olcAccess: {3}to * by self write by set="[cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru]/member* & user" write by * read

К сожалению, инструмент slapacl (по крайней мере в OpenLDAP версии 2.4.45) не справляется с таким условием ACL:

# slapacl -D uid=sidorov,ou=People,dc=mycompany,dc=ru -b 'cn=News Readers,ou=Groups,dc=mycompany,dc=ru' member/write:uid=sidorov,ou=People,dc=mycompany,dc=ru
authcDN: "uid=sidorov,ou=people,dc=mycompany,dc=ru"
write access to member=uid=sidorov,ou=People,dc=mycompany,dc=ru: DENIED

Поэтому протестируем его путём внесения изменений в каталог. Сначала проверим, сможет ли пользователь sidorov добавить самого себя в группу News Readers:

dn: cn=News Readers,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: member
member: uid=sidorov,ou=People,dc=mycompany,dc=ru

Попробуем применить эти изменения:

$ ldapmodify -D uid=sidorov,ou=People,dc=mycompany,dc=ru -w sidorovPassword -f ./008-add_self_to_news_readres.ldif
modifying entry "cn=News Readers,ou=Groups,dc=mycompany,dc=ru"

$ ldapsearch -xLLL -b 'cn=News Readers,ou=Groups,dc=mycompany,dc=ru' member
dn: cn=News Readers,ou=Groups,dc=mycompany,dc=ru
member: cn=dummy_member
member: uid=sidorov,ou=People,dc=mycompany,dc=ru

Всё работает как задумано. Теперь проверим, сможет ли он добавить в эту группу другого пользователя:

dn: cn=News Readers,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: member
member: uid=petrov,ou=People,dc=mycompany,dc=ru

Попробуем применить эти изменения:

$ ldapmodify -D uid=sidorov,ou=People,dc=mycompany,dc=ru -w sidorovPassword -f ./009-add_another_to_news_readers.ldif
modifying entry "cn=News Readers,ou=Groups,dc=mycompany,dc=ru"
ldap_modify: Insufficient access (50)

Опять же, результат ожидаем: прав для внесения подобных изменений не хватает. Наконец проверим, сможет ли sidorov удалить себя из членов группы News Readers:

dn: cn=News Readers,ou=Groups,dc=mycompany,dc=ru
changetype: modify
delete: member
member: uid=sidorov,ou=People,dc=mycompany,dc=ru

Попробуем применить эти изменения:

$ ldapmodify -D uid=sidorov,ou=People,dc=mycompany,dc=ru -w sidorovPassword -f ./010-del_self_from_news_readers.ldif
modifying entry "cn=News Readers,ou=Groups,dc=mycompany,dc=ru"

$ ldapsearch -xLLL -b 'cn=News Readers,ou=Groups,dc=mycompany,dc=ru' member
dn: cn=News Readers,ou=Groups,dc=mycompany,dc=ru
member: cn=dummy_member

Все наши тесты прошли успешно. Теперь составим аналогичный ACL для класса organizationalRole. Предположим, для доступа к корпоративному wiki-контенту требуется получить роль Wiki Reader:

dn: cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
cn: Wiki Reader

Добавим эту запись в каталог:

$ ldapadd -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword -f ./011-add_wiki_reader_role.ldif
adding new entry "cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru"

Чтобы любой желающий мог получить доступ к корпоративному wiki, предоставим пользователям добавлять и удалять DN своих записей в атрибут roleOccupant записи нашей роли. Составим такой ACL (по аналогии с предыдущим):

dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {0}to dn.base="cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru" attrs=roleOccupant
  by dnattr=roleOccupant selfwrite
  by * break

Поскольку у этого ACL индекс 0, он будет оцениваться первым в списке ACL. Применим наши изменения и проверим результат:

$ ldapmodify -D cn=config -W -f ./107-modify_acl_7.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 dn.base="cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru" attrs=roleOccupant by dnattr=roleOccupant selfwrite by * break
olcAccess: {1}to dn.base="cn=News Readers,ou=Groups,dc=mycompany,dc=ru" attrs=member by dnattr=member selfwrite by * break
olcAccess: {2}to dn.children="ou=People,dc=mycompany,dc=ru" attrs=userPassword by self write by group.exact="cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru" write by group/organizationalRole/roleOccupant.exact="cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru" write by anonymous auth by * none
olcAccess: {3}to dn.regex="^uid=[^,]+,ou=People,(dc=[^,]+,dc=[^,]+)$" attrs=telephoneNumber,description by self write by group.expand="cn=OpenLDAP Operators,ou=Groups,$1" write by set.expand="[cn=Developer,ou=Roles,$1]/roleOccupant* & user" write by * none
olcAccess: {4}to * by self write by set="[cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru]/member* & user" write by * read

Для тестирования этого ACL выполним модификации каталога, аналогичные тем, которые мы проводили для группы News Readers (будем выполнять их от имени пользователя sidorov):

# Модификация 1: добавление себя в группу
dn: cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru
changetype: modify
add: roleOccupant
roleOccupant: uid=sidorov,ou=People,dc=mycompany,dc=ru

# Модификация 2: добавление другого человека в группу
dn: cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru
changetype: modify
add: roleOccupant
roleOccupant: uid=petrov,ou=People,dc=mycompany,dc=ru

# Модификация 3: удаление себя из группы
dn: cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru
changetype: modify
delete: roleOccupant
roleOccupant: uid=sidorov,ou=People,dc=mycompany,dc=ru

Попробуем выполнить эти модификации:

$ ldapmodify -c -D uid=sidorov,ou=People,dc=mycompany,dc=ru -w sidorovPassword -f ./012-mod_wiki_reader.ldif
modifying entry "cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru"

modifying entry "cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru"
ldap_modify: Insufficient access (50)

modifying entry "cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru"

Аргумент -c команды ldapmodify позволяет продолжить выполнение модификации каталога, если какая-то из операций завершится неудачно.

Все наши тесты завершились, как ожидалось. На этом мы практически закончили рассмотрение возможных групповых условий в ACL OpenLDAP.

Наложение refint

Ежедневная рутина системного администратора предполагает, кроме всего прочего, ведение базы учётных записей пользователей и их привилегий доступа к информационным ресурсам организации. С точки зрения каталога это означает добавление/удаление/модификацию записей пользователей, а также поддержание в актуальном состоянии членства в записях-группах. И если с добавлением новых членов в записи-группы обычно проблем не возникает, то необходимость изменения атрибутов членства в многочисленных группах при удалении учётной записи пользователя или изменении её DN нередко выпадает из поля зрения администраторов. Испокон веков для решения этой проблемы администраторы использовали различные скрипты. Разработчики OpenLDAP предлагают своё решение — наложение refint.

Суть работы этого наложения заключается в поддержании целостности "ссылочных" атрибутов, то есть тех атрибутов, значениями которых являются DN записей каталога. При изменении DN записи каталога, либо при удалении записи из каталога, соответствующие изменения будут внесены в значения тех "ссылочных" атрибутов, на слежение за которыми было настроено наложение refint.

Как видно, это наложение идеально подходит для атрибутов членства в группах: member, uniqueMember и roleOccupant. Но оно может быть использовано и с любыми другими атрибутами с синтаксисом DN: manager, secretary, seeAlso и другими. Поэтому, для того, чтобы сделать примеры настроек более интересными, назначим группам из нашего каталога "владельцев" (атрибут owner):

dn: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: owner
owner: uid=ivanov,ou=People,dc=mycompany,dc=ru

dn: cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: owner
owner: uid=antonova,ou=People,dc=mycompany,dc=ru

dn: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru
changetype: modify
add: objectclass
objectclass: extensibleObject
-
add: owner
owner: uid=ivanov,ou=People,dc=mycompany,dc=ru

dn: cn=Developer,ou=Roles,dc=mycompany,dc=ru
changetype: modify
add: objectclass
objectclass: extensibleObject
-
add: owner
owner: uid=petrov,ou=People,dc=mycompany,dc=ru

dn: cn=News Readers,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: owner
owner: uid=sidorov,ou=People,dc=mycompany,dc=ru

dn: cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru
changetype: modify
add: objectclass
objectclass: extensibleObject
-
add: owner
owner: uid=sidorov,ou=People,dc=mycompany,dc=ru

У объектного класса organizationalRole нет атрибута owner, чтобы добавить этот атрибут в запись пришлось воспользоваться специальным классом extensibleObject.

Внесём изменения и проверим:

$ ldapmodify -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword -f ./013-set_groups_owners.ldif
modifying entry "cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru"

modifying entry "cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru"

modifying entry "cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru"

modifying entry "cn=Developer,ou=Roles,dc=mycompany,dc=ru"

modifying entry "cn=News Readers,ou=Groups,dc=mycompany,dc=ru"

modifying entry "cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru"

$ ldapsearch -xLLL -b dc=mycompany,dc=ru '(owner=*)' owner
dn: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru
owner: uid=ivanov,ou=People,dc=mycompany,dc=ru

dn: cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru
owner: uid=antonova,ou=People,dc=mycompany,dc=ru

dn: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru
owner: uid=ivanov,ou=People,dc=mycompany,dc=ru

dn: cn=Developer,ou=Roles,dc=mycompany,dc=ru
owner: uid=petrov,ou=People,dc=mycompany,dc=ru

dn: cn=News Readers,ou=Groups,dc=mycompany,dc=ru
owner: uid=sidorov,ou=People,dc=mycompany,dc=ru

dn: cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru
owner: uid=sidorov,ou=People,dc=mycompany,dc=ru

Теперь приступим к настройке наложения refint. Для начала подгрузим соответствующий динамический модуль:

dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: refint.la

Применим изменения и проверим:

$ ldapmodify -D cn=config -W -f ./108-add_refint_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}refint.la

А теперь настроим слежение за целостностью атрибутов member, roleOccupant и owner в каталоге dc=mycompany,dc=ru:

dn: olcOverlay=refint,olcDatabase={1}mdb,cn=config
objectClass: olcRefintConfig
olcOverlay: refint
olcRefintAttribute: member
olcRefintAttribute: roleOccupant
olcRefintAttribute: owner
olcRefintNothing: cn=dummy_member

В данном случае мы указали два атрибута настроек наложения: olcRefintAttribute и olcRefintNothing. В первом из них задаются имена атрибутов, за целостностью значений которых будет следить наложение. Во втором атрибуте задаётся DN, которое будет подставлено в качестве значения отслеживаемого атрибута в случае, если все реальные значения этого атрибута будут удалены. Как уже упоминалось выше, некоторые объектные классы, например groupOfNames, требуют обязательного наличия атрибута членства, и при удалении всех членов возникнет нарушение целостности схемы данных каталога. Для предотвращения такой ситуации как раз и предназначена настройка, выполняемая с помощью атрибута olcRefintNothing. Значением этого атрибута должно быть правильно сформированное DN, в нашем случае cn=dummy_member.

Применим изменения и проверим:

$ ldapadd -D cn=config -W -f ./109-add_refint_overlay.ldif
Enter LDAP Password: 
adding new entry "olcOverlay=refint,olcDatabase={1}mdb,cn=config"

$ ldapsearch -xLLL -o ldif-wrap=no -D cn=config -W -b olcOverlay={0}refint,olcDatabase={1}mdb,cn=config
Enter LDAP Password: 
dn: olcOverlay={0}refint,olcDatabase={1}mdb,cn=config
objectClass: olcRefintConfig
olcOverlay: {0}refint
olcRefintAttribute: member
olcRefintAttribute: roleOccupant
olcRefintAttribute: owner
olcRefintNothing: cn=dummy_member

Теперь посмотрим как это работает. Предположим, что из организации уволился администратор Иван Иванов, и мы собираемся удалить его учётную запись uid=ivanov,ou=People,dc=mycompany,dc=ru. Но сначала проверим, в какие группы входит этот пользователь:

$ ldapsearch -xLLL -b dc=mycompany,dc=ru '(|(member=uid=ivanov,ou=People,dc=mycompany,dc=ru)(roleOccupant=uid=ivanov,ou=People,dc=mycompany,dc=ru)(owner=uid=ivanov,ou=People,dc=mycompany,dc=ru))'
dn: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
cn: OpenLDAP Admins
member: uid=ivanov,ou=People,dc=mycompany,dc=ru
owner: uid=ivanov,ou=People,dc=mycompany,dc=ru

dn: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: SysAdmin
roleOccupant: uid=ivanov,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=petrov,ou=People,dc=mycompany,dc=ru
owner: uid=ivanov,ou=People,dc=mycompany,dc=ru

Итак, у нас есть две таких группы. Обратим внимание, что в обоих группах пользователь ivanov также является "владельцем" группы (его запись указана в атрибуте owner).

Теперь перейдём к удалению записи из каталога. Его можно произвести разными способами. В данном случае мы будем выполнять его с помощью утилиты ldapmodify. Сформируем LDIF для удаления:

dn: uid=ivanov,ou=People,dc=mycompany,dc=ru
changetype: delete

И применим его:

$ ldapmodify -D uid=antonova,ou=People,dc=mycompany,dc=ru -w antonovaPassword -f ./014-delete_user_ivanov.ldif
deleting entry "uid=ivanov,ou=People,dc=mycompany,dc=ru"

Мы выполняли удаление от имени пользователя antonova. Однако, можно было бы выполнить его и от имени пользователя ivanov, пока его запись ещё присутствовала в каталоге.

Теперь проверим, что произошло с групповыми записями:

$ ldapsearch -xLLL -b dc=mycompany,dc=ru '(|(member=uid=ivanov,ou=People,dc=mycompany,dc=ru)(roleOccupant=uid=ivanov,ou=People,dc=mycompany,dc=ru)(owner=uid=ivanov,ou=People,dc=mycompany,dc=ru))'
$ ldapsearch -xLLL -b dc=mycompany,dc=ru '(cn=*Admin*)'
dn: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
cn: OpenLDAP Admins
owner: cn=dummy_member
member: cn=dummy_member

dn: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: SysAdmin
roleOccupant: uid=petrov,ou=People,dc=mycompany,dc=ru
owner: cn=dummy_member

Первый поисковый запрос ничего не вернул, поскольку при удалении записи uid=ivanov,ou=People,dc=mycompany,dc=ru сработало наложение refint и были удалены соответствующие значения тех атрибутов, которые оно отслеживало. Всё закономерно. Вторым запросом мы проверяем содержимое записей интересующих нас групп и убеждаемся, что наложение refint отработало, как и было настроено: атрибуты-ссылки на удалённую запись пользователя были удалены.

Также очевидно, что сработала и вторая настройка нашего наложения: атрибуты, для которых было удалено последнее значение в записи, получили "значение по умолчанию" cn=dummy_member. Если для атрибута member это необходимо (для соблюдения требования схемы данных), то для атрибутов roleOccupant и owner — совершенно необязательно. Чтобы такого не происходило, мы можем разделить настройки наложения refint, определив "значение по умолчанию" только для тех атрибутов, которым это действительно необходимо, а остальные отслеживаемые атрибуты оставить без такой подстановки. Но сначала приведём в порядок наши группы, заменив фиктивных членов и владельцев реальными:

# Замена члена и владельца группы OpenLDAP Admins
dn: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru
changetype: modify
replace: member
member: uid=antonova,ou=People,dc=mycompany,dc=ru
-
replace: owner
owner: uid=antonova,ou=People,dc=mycompany,dc=ru

# Замена владельца роли SysAdmin
dn: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru
changetype: modify
replace: owner
owner: uid=petrov,ou=People,dc=mycompany,dc=ru

Применим изменения и проверим:

$ ldapmodify -D uid=antonova,ou=People,dc=mycompany,dc=ru -w antonovaPassword -f ./015-replace_dn_attributes.ldif
modifying entry "cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru"

modifying entry "cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru"

$ ldapsearch -xLLL -b dc=mycompany,dc=ru '(cn=*Admin*)'
dn: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
cn: OpenLDAP Admins
member: uid=antonova,ou=People,dc=mycompany,dc=ru
owner: uid=antonova,ou=People,dc=mycompany,dc=ru

dn: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: SysAdmin
roleOccupant: uid=petrov,ou=People,dc=mycompany,dc=ru
owner: uid=petrov,ou=People,dc=mycompany,dc=ru

Теперь выполним проанонсированные модификации наложения refint:

dn: olcOverlay={0}refint,olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcRefintAttribute
olcRefintAttribute: member

dn: olcOverlay={1}refint,olcDatabase={1}mdb,cn=config
changetype: add
objectClass: olcRefintConfig
olcOverlay: {1}refint
olcRefintAttribute: roleOccupant
olcRefintAttribute: owner

Мы модифицируем уже имеющееся наложение refint и добавляем ещё одно. Удобнее будет рассмотреть получившиеся в итоге этих изменений записи, поэтому сначала применим изменения и выведем результаты:

$ ldapmodify -D cn=config -W -f ./110-modify_refint_overlay.ldif
Enter LDAP Password: 
modifying entry "olcOverlay={0}refint,olcDatabase={1}mdb,cn=config"

adding new entry "olcOverlay={1}refint,olcDatabase={1}mdb,cn=config"

$ ldapsearch -xLLL -o ldif-wrap=no -D cn=config -W -b cn=config '(objectClass=olcRefintConfig)'
Enter LDAP Password: 
dn: olcOverlay={0}refint,olcDatabase={1}mdb,cn=config
objectClass: olcRefintConfig
olcOverlay: {0}refint
olcRefintNothing: cn=dummy_member
olcRefintAttribute: member

dn: olcOverlay={1}refint,olcDatabase={1}mdb,cn=config
objectClass: olcRefintConfig
olcOverlay: {1}refint
olcRefintAttribute: roleOccupant
olcRefintAttribute: owner

Как мы видим, в первом наложении отслеживается атрибут member, и для него задано "значение по умолчанию" cn=dummy_member. Во втором наложении отслеживаются атрибуты roleOccupant и owner, и "значение по умолчанию" для них не задаётся. Именно такие настройки мы и обсуждали.

Посмотрим, как это работает на примере удаления записи пользователя petrov. На этот раз мы будем выполнять удаление с помощью утилиты ldapdelete. Перед удалением и после его выполнения отследим наличие "ссылочных" атрибутов в записях-группах, в которых этот пользователь был членом или владельцем (как в предыдущем случае):

$ ldapsearch -xLLL -b dc=mycompany,dc=ru '(|(member=uid=petrov,ou=People,dc=mycompany,dc=ru)(roleOccupant=uid=petrov,ou=People,dc=mycompany,dc=ru)(owner=uid=petrov,ou=People,dc=mycompany,dc=ru))'
dn: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: SysAdmin
roleOccupant: uid=petrov,ou=People,dc=mycompany,dc=ru
owner: uid=petrov,ou=People,dc=mycompany,dc=ru

dn: cn=Developer,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: Developer
roleOccupant: uid=antonova,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=sidorov,ou=People,dc=mycompany,dc=ru
roleOccupant: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru
owner: uid=petrov,ou=People,dc=mycompany,dc=ru

$ ldapdelete -D uid=antonova,ou=People,dc=mycompany,dc=ru -w antonovaPassword uid=petrov,ou=People,dc=mycompany,dc=ru
$ ldapsearch -xLLL -b dc=mycompany,dc=ru '(|(member=uid=petrov,ou=People,dc=mycompany,dc=ru)(roleOccupant=uid=petrov,ou=People,dc=mycompany,dc=ru)(owner=uid=petrov,ou=People,dc=mycompany,dc=ru))'
$ ldapsearch -xLLL -b dc=mycompany,dc=ru '(|(cn=SysAdmin)(cn=Developer))'
dn: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: SysAdmin

dn: cn=Developer,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: Developer
roleOccupant: uid=antonova,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=sidorov,ou=People,dc=mycompany,dc=ru
roleOccupant: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru

Результаты эксперимента показывают, что, во-первых, наложение отработало корректно при удалении "ссылочных" атрибутов (roleOccupant и owner), а во-вторых, излишней подстановки "значения по умолчанию" для этих атрибутов не произошло (роль SysAdmin, например, осталась без владельца и без членов).

Наконец, проверим поведение наложения refint при переименовании записи пользователя в каталоге. Предположим, что Тоня Антонова вышла замуж и взяла фамилию мужа. LDIF для внесения изменений в каталог:

# Переименование записи 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

Как и в предыдущих случаях, выведем содержимое групповых записей до и после изменений:

$ ldapsearch -xLLL -b dc=mycompany,dc=ru '(|(member=uid=antonova,ou=People,dc=mycompany,dc=ru)(roleOccupant=uid=antonova,ou=People,dc=mycompany,dc=ru)(owner=uid=antonova,ou=People,dc=mycompany,dc=ru))'
dn: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
cn: OpenLDAP Admins
member: uid=antonova,ou=People,dc=mycompany,dc=ru
owner: uid=antonova,ou=People,dc=mycompany,dc=ru

dn: cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
cn: OpenLDAP Operators
member: uid=antonova,ou=People,dc=mycompany,dc=ru
member: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru
owner: uid=antonova,ou=People,dc=mycompany,dc=ru

dn: cn=Developer,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: Developer
roleOccupant: uid=antonova,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=sidorov,ou=People,dc=mycompany,dc=ru
roleOccupant: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru

$ ldapmodify -D uid=antonova,ou=People,dc=mycompany,dc=ru -w antonovaPassword -f ./016-rename_antonova.ldif
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 '(|(member=uid=antonova,ou=People,dc=mycompany,dc=ru)(roleOccupant=uid=antonova,ou=People,dc=mycompany,dc=ru)(owner=uid=antonova,ou=People,dc=mycompany,dc=ru))'
$ ldapsearch -xLLL -b dc=mycompany,dc=ru '(|(member=uid=zhukova,ou=People,dc=mycompany,dc=ru)(roleOccupant=uid=zhukova,ou=People,dc=mycompany,dc=ru)(owner=uid=zhukova,ou=People,dc=mycompany,dc=ru))'
dn: cn=Developer,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: Developer
roleOccupant: uid=sidorov,ou=People,dc=mycompany,dc=ru
roleOccupant: cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru
roleOccupant: uid=zhukova,ou=People,dc=mycompany,dc=ru

dn: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
cn: OpenLDAP Admins
owner: uid=zhukova,ou=People,dc=mycompany,dc=ru
member: uid=zhukova,ou=People,dc=mycompany,dc=ru

dn: cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
cn: OpenLDAP Operators
member: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru
member: uid=zhukova,ou=People,dc=mycompany,dc=ru
owner: uid=zhukova,ou=People,dc=mycompany,dc=ru

Итак, все наши тесты прошли успешно и показали, что наложение refint — элегантный, эффективный и гибкий инструмент для автоматизации рутинных процессов системного администрирования.

Остаётся добавить, что модификации, выполняемые этим наложением, не распространяются при репликации каталога. Поэтому для корректной работы в каталогах с репликацией наложение refint должно быть идентичным образом настроено на всех поставщиках и потребителях репликации.

Ещё немного об ACL

Прежде чем подводить итоги, вернёмся ненадолго к нашим ACL. В связи с тем, что у наших групп появились владельцы, мы можем рассмотреть ещё один аспект ACL, связанный с атрибутами, имеющими синтаксис DN (мы уже опробовали конструкцию dnattr в условии by в ACL с индексами 0 и 1). Дополним эти ACL так, чтобы предоставить владельцам групп News Readers и Wiki Reader (записям, DN которых указан в атрибуте owner записей этих групп) право на добавление/удаление членов этих групп:

dn: olcDatabase={1}mdb,cn=config
changetype: modify
delete: olcAccess
olcAccess: {0}
-
add: olcAccess
olcAccess: {0}to dn.base="cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru" attrs=roleOccupant
  by dnattr=roleOccupant selfwrite
  by dnattr=owner write
  by * break
-
delete: olcAccess
olcAccess: {1}
-
add: olcAccess
olcAccess: {1}to dn.base="cn=News Readers,ou=Groups,dc=mycompany,dc=ru" attrs=member
  by dnattr=member selfwrite
  by dnattr=owner write
  by * break

Интересующие нас условия by описаны в строках 9 и 18 данного LDIF-файла. Они достаточно очевидны и в дополнительных разъяснениях не нуждаются. Применим изменения и проверим:

$ ldapmodify -D cn=config -W -f ./111-modify_acl_8.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=*)' olcAccess
Enter LDAP Password: 
dn: olcDatabase={1}mdb,cn=config
olcAccess: {0}to dn.base="cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru" attrs=roleOccupant by dnattr=roleOccupant selfwrite by dnattr=owner write by * break
olcAccess: {1}to dn.base="cn=News Readers,ou=Groups,dc=mycompany,dc=ru" attrs=member by dnattr=member selfwrite by dnattr=owner write by * break
olcAccess: {2}to dn.children="ou=People,dc=mycompany,dc=ru" attrs=userPassword by self write by group.exact="cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru" write by group/organizationalRole/roleOccupant.exact="cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru" write by anonymous auth by * none
olcAccess: {3}to dn.regex="^uid=[^,]+,ou=People,(dc=[^,]+,dc=[^,]+)$" attrs=telephoneNumber,description by self write by group.expand="cn=OpenLDAP Operators,ou=Groups,$1" write by set.expand="[cn=Developer,ou=Roles,$1]/roleOccupant* & user" write by * none
olcAccess: {4}to * by self write by set="[cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru]/member* & user" write by * read

Протестируем полученные ACL. Владельцем обеих групп у нас является пользователь sidorov. Попробуем от его имени добавить другого пользователя в члены этих групп:

dn: cn=News Readers,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: member
member: uid=zhukova,ou=People,dc=mycompany,dc=ru

dn: cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru
changetype: modify
add: roleOccupant
roleOccupant: uid=zhukova,ou=People,dc=mycompany,dc=ru

Применим изменения и проверим:

$ ldapmodify -D uid=sidorov,ou=People,dc=mycompany,dc=ru -w sidorovPassword -f ./017-add_zhukova_to_readers_groups.ldif
modifying entry "cn=News Readers,ou=Groups,dc=mycompany,dc=ru"

modifying entry "cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru"

$ ldapsearch -xLLL -b dc=mycompany,dc=ru '(cn=*Reader*)'
dn: cn=News Readers,ou=Groups,dc=mycompany,dc=ru
objectClass: groupOfNames
cn: News Readers
member: cn=dummy_member
member: uid=zhukova,ou=People,dc=mycompany,dc=ru
owner: uid=sidorov,ou=People,dc=mycompany,dc=ru

dn: cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: Wiki Reader
owner: uid=sidorov,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=zhukova,ou=People,dc=mycompany,dc=ru

Тест успешно пройден, наше условие работает. Остаётся добавить, что конструкцию dnattr можно использовать с любыми атрибутами с синтаксисом DN (например, manager или secretary), для предоставления прав записям, DN которых перечислены в этих атрибутах, на доступ различного уровня к абсолютно любым записям каталога (а не только записям-группам).

Итоговые настройки

В ходе этого урока мы познакомились с различными вариантами групповых условий в ACL OpenLDAP, в том числе с использованием наборов для работы с "вложенными" группами. Кроме того, мы рассмотрели специальную конструкцию dnattr, позволяющую предоставлять права на доступ в зависимости от содержимого записей каталога. Наконец, мы рассмотрели разные варианты настройки наложения refint. Итоговые настройки каталога 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 dn.base="cn=Wiki Reader,ou=Roles,dc=mycompany,dc=ru" attrs=roleOccupant
  by dnattr=roleOccupant selfwrite
  by dnattr=owner write
  by * break
olcAccess: {1}to dn.base="cn=News Readers,ou=Groups,dc=mycompany,dc=ru" attrs=member
  by dnattr=member selfwrite
  by dnattr=owner write
  by * break
olcAccess: {2}to dn.children="ou=People,dc=mycompany,dc=ru" attrs=userPassword
  by self write by group.exact="cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru" write
  by group/organizationalRole/roleOccupant.exact="cn=SysAdmin,ou=Roles,dc=mycompany,dc=ru" write
  by anonymous auth
  by * none
olcAccess: {3}to dn.regex="^uid=[^,]+,ou=People,(dc=[^,]+,dc=[^,]+)$" attrs=telephoneNumber,description
  by self write
  by group.expand="cn=OpenLDAP Operators,ou=Groups,$1" write
  by set.expand="[cn=Developer,ou=Roles,$1]/roleOccupant* & user" write
  by * none
olcAccess: {4}to *
  by self write
  by set="[cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru]/member* & user" 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}refint,olcDatabase={1}mdb,cn=config
objectClass: olcRefintConfig
olcOverlay: {0}refint
olcRefintAttribute: member
olcRefintNothing: cn=dummy_member

dn: olcOverlay={1}refint,olcDatabase={1}mdb,cn=config
objectClass: olcRefintConfig
olcOverlay: {1}refint
olcRefintAttribute: roleOccupant
olcRefintAttribute: owner
Pro-LDAP.ru 2013-2018 г. Последнее изменение страницы — 14 июня 2018 г. Вопросы и предложения принимаются на форуме проекта.