5. Нестандартная авторизация

Содержание

Постановка задачи

Предпосылки: В организации, сведения о которой ведутся в нашем каталоге, два отдела: Developers и Designers. Информация о принадлежности пользователя отделу фиксируется как значение атрибута ou в учётной записи каждого пользователя. На почтовом сервере организации обслуживаются почтовые ящики пользователей, адреса электронной почты фиксируются как значение атрибута mail в учётной записи каждого пользователя.

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

Задача: Требуется создать списки рассылки, которые будут использоваться почтовым сервером для организации рассылки писем всем сотрудникам организации и отдельно сотрудникам каждого подразделения. Кроме того, для разграничения доступа различных приложений требуется организовать объекты классических групп для имеющихся отделов. Списки рассылки и групповые объекты должны постоянно отражать актуальную структуру организации.

Решение

Как и всегда, начинать будем с исходного положения каталога. Сначала мы должны привести каталог в соответствие выдвинутым условиям задачи, то есть дополнить учётные записи пользователей требуемыми атрибутами и модифицировать условия получения доступа к этим учётным записям (иными словами, привести в соответствие ACL). Затем мы настроим наложение dynlist, предварительно загрузив необходимые для его работы данные и динамический модуль. Наконец, введём в каталог динамические объекты списков и групп, дополнительные настройки авторизации которых позволят решить поставленную задачу.

Шаг 1. Приведение каталога в соответствие условиям задачи

Данный шаг состоит из двух этапов. Сначала нужно дополнить учётные записи каталога перечисленными в предпосылках к задаче атрибутами с признаком принадлежности сотрудника к тому или иному отделу, а также адресом электронной почты. Для этого сформируем такой LDIF-файл:

dn: uid=antonova,ou=People,dc=mycompany,dc=ru
changetype: modify
add: ou
ou: Designers
-
add: mail
mail: antonova@mycompany.ru

dn: uid=ivanov,ou=People,dc=mycompany,dc=ru
changetype: modify
add: ou
ou: Developers
-
add: mail
mail: ivanov@mycompany.ru

dn: uid=petrov,ou=People,dc=mycompany,dc=ru
changetype: modify
add: ou
ou: Developers
-
add: mail
mail: petrov@mycompany.ru

dn: uid=sidorov,ou=People,dc=mycompany,dc=ru
changetype: modify
add: ou
ou: Designers
-
add: mail
mail: sidorov@mycompany.ru

Применим его и посмотрим, что получилось:

$ ldapmodify -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./002-add_users_attributes.ldif
Enter LDAP Password:
modifying entry "uid=antonova,ou=People,dc=mycompany,dc=ru"

modifying entry "uid=ivanov,ou=People,dc=mycompany,dc=ru"

modifying entry "uid=petrov,ou=People,dc=mycompany,dc=ru"

modifying entry "uid=sidorov,ou=People,dc=mycompany,dc=ru"

$ ldapsearch -xLLL -b dc=mycompany,dc=ru '(objectClass=inetOrgPerson)'
dn: uid=antonova,ou=People,dc=mycompany,dc=ru
objectClass: inetOrgPerson
uid: antonova
cn: Antonina Antonova
sn: Antonova
ou: Designers
mail: antonova@mycompany.ru

dn: uid=ivanov,ou=People,dc=mycompany,dc=ru
objectClass: inetOrgPerson
uid: ivanov
cn: Ivan Ivanov
sn: Ivanov
ou: Developers
mail: ivanov@mycompany.ru

dn: uid=petrov,ou=People,dc=mycompany,dc=ru
objectClass: inetOrgPerson
uid: petrov
cn: Petr Petrov
sn: Petrov
ou: Developers
mail: petrov@mycompany.ru

dn: uid=sidorov,ou=People,dc=mycompany,dc=ru
objectClass: inetOrgPerson
uid: sidorov
cn: Sidor Sidorov
sn: Sidorov
ou: Designers
mail: sidorov@mycompany.ru

Наши учётные записи готовы. Второй этап — ограничить доступ к ним согласно условиям задачи, то есть чтобы прошедший аутентификацию пользователь мог получить доступ только к своей учётной записи, а анонимный доступ к учётным записям был бы запрещён.

Для этого нам нужно внести изменения в настройки нашей рабочей базы данных mdb, добавив к двум имеющимся у нас ACL ещё один:

dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {1}to dn.one="ou=People,dc=mycompany,dc=ru"
  by self write
  by * none

Как видно, ограничения коснутся записей каталога, непосредственно дочерних по отношению к контейнеру ou=People,dc=mycompany,dc=ru (условие to в строке 4), в котором хранятся учётки пользователей. При подключении к каталогу от имени какого-либо пользователя к своей учётной записи он будет иметь полный доступ (условие by в строке 5), все остальные записи будут для него недоступны. Также отказано в доступе к учётным записям будет и в случае анонимного подключения к каталогу. Поскольку данный ACL имеет X-ORDERED-индекс 1 (то есть должен оказаться вторым), он займёт место между двумя уже имеющимися правилами в настройках рабочей базы данных.

Добавим наш ACL и выведем список всех имеющихся:

$ ldapmodify -x -D cn=config -W -f ./101-add_people_branch_acl.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.one="ou=People,dc=mycompany,dc=ru" by self write by * none
olcAccess: {2}to * by self write by * read

Отлично, все наши ACL на месте. Проверим работу установленных ограничений, для этого попробуем получить доступ к ветке ou=People,dc=mycompany,dc=ru сначала анонимно, а потом от имени одной из имеющихся учётных записей:

$ ldapsearch -xLLL -b ou=People,dc=mycompany,dc=ru -s one
$ ldapsearch -xLLL -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword -b ou=People,dc=mycompany,dc=ru -s one
dn: uid=ivanov,ou=People,dc=mycompany,dc=ru
objectClass: inetOrgPerson
uid: ivanov
cn: Ivan Ivanov
sn: Ivanov
userPassword:: aXZhbm92UGFzc3dvcmQ=
ou: Developers
mail: ivanov@mycompany.ru

Как и ожидалось, анонимный доступ к ou=People,dc=mycompany,dc=ru запрещён, а пользователь получает доступ лишь к своей учётке (в том числе и к своему паролю, согласно первому ACL в списке). Условия нашей задачи соблюдаются, можно переходить ко второму шагу.

Шаг 2. Добавление и настройка наложения dynlist

Как и в предыдущих уроках, для организации работы с динамическими объектами (группами и списками) нам нужно подгрузить динамический модуль наложения и добавить запись настройки самого наложения к рабочей базе данных mdb. Однако, в данном случае для решения задачи обхода имеющихся ограничений контроля доступа нам потребуется также расширить схему данных каталога специальными атрибутами, которые использует в своей работе наложение dynlist.

В одном из примеров мы уже подгружали входящий в поставку OpenLDAP LDIF-файл dyngroup.ldif, как раз содержащий требуемые для нашего наложения элементы схемы данных. Если внимательно посмотреть на содержимое добавляемого набора схемы, то, кроме объектного класса groupOfURLs с атрибутом memberURL, мы увидим ещё объектный класс dgIdentityAux с атрибутами dgIdentity и dgAuthz, которые нам и потребуются в ходе этого урока.

В итоге для настройки slapd мы сформируем такой LDIF-файл:

include: file:///etc/ldap/schema/dyngroup.ldif

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

dn: olcOverlay=dynlist,olcDatabase={1}mdb,cn=config
objectClass: olcDynamicList
olcOverlay: dynlist
olcDLattrSet: {0}groupOfURLs memberURL
olcDLattrSet: {1}organizationalRole memberURL roleOccupant

В строке 1 мы подгружаем требуемый набор схемы данных dyngroup.ldif, в строках 3-6 — динамический модуль наложения dynlist. В оставшихся строках 8-12 запись с настройками наложения добавляется в качестве дочерней к записи нашей рабочей базы данных. Как видно, настраивается два типа динамических объектов: списки рассылки будут организованы на объектном классе groupOfURLs, параметры поиска и формирования динамического содержимого будут указаны в значении атрибута memberURL, а динамические группы будут строиться на объектном классе organizationalRole (атрибут членства в группе — roleOccupant), причём параметры поиска динамического содержимого также будут указаны в значении атрибута memberURL.

Попробуем применить указанные настройки и проверить результат:

$ ldapadd -x -D cn=config -W -f ./102-add_dynlist.ldif
Enter LDAP Password:
adding new entry "cn=dyngroup,cn=schema,cn=config"

modifying entry "cn=module{0},cn=config"

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

$ ldapsearch -xLLL -o ldif-wrap=no -D cn=config -W -b cn=schema,cn=config '(cn=*dyngroup)' dn
Enter LDAP Password:
dn: cn={4}dyngroup,cn=schema,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}dynlist.la

$ ldapsearch -xLLL -o ldif-wrap=no -D cn=config -W -b olcDatabase={1}mdb,cn=config -s one
Enter LDAP Password: 
dn: olcOverlay={0}dynlist,olcDatabase={1}mdb,cn=config
objectClass: olcDynamicList
olcOverlay: {0}dynlist
olcDlAttrSet: {0}groupOfURLs memberURL
olcDlAttrSet: {1}organizationalRole memberURL roleOccupant

Всё на месте, пора переходить к следующему шагу.

Шаг 3. Добавление и настройка динамических объектов в рабочем каталоге

Начнём с того, что мы уже не раз делали: добавим в рабочий каталог объекты динамических списков рассылки (в отдельный контейнер) и групп. Сформируем такой LDIF-файл:

# Контейнер для списков рассылки
dn: ou=MailingLists,dc=mycompany,dc=ru
objectClass: organizationalUnit
ou: MailingLists

# Списки рассылки
dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru
objectClass: groupOfURLs
cn: All
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(objectClass=inetOrgPerson)

dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru
objectClass: groupOfURLs
cn: Developers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(&(objectClass=inetOrgPerson)(ou=Developers))

dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru
objectClass: groupOfURLs
cn: Designers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(&(objectClass=inetOrgPerson)(ou=Designers))

# Группы отделов
dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: Developers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru??one?(&(objectClass=inetOrgPerson)(ou=Developers))

dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: Designers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru??one?(&(objectClass=inetOrgPerson)(ou=Designers))

Динамические объекты сформированы так, чтобы их объектные классы и атрибуты с параметрами поиска динамического содержимого удовлетворяли заданным нами настройкам наложения dynlist. Добавим наши объекты в каталог:

$ ldapadd -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./003-add_dynamic_objects.ldif
Enter LDAP Password:
adding new entry "ou=MailingLists,dc=mycompany,dc=ru"

adding new entry "cn=All,ou=MailingLists,dc=mycompany,dc=ru"

adding new entry "cn=Developers,ou=MailingLists,dc=mycompany,dc=ru"

adding new entry "cn=Designers,ou=MailingLists,dc=mycompany,dc=ru"

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

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

Динамические объекты добавлены, попробуем просмотреть их:

$ ldapsearch -xLLL -o ldif-wrap=no -b dc=mycompany,dc=ru '(memberURL=*)'
dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru
objectClass: groupOfURLs
cn: All
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(objectClass=inetOrgPerson)

dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru
objectClass: groupOfURLs
cn: Developers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(&(objectClass=inetOrgPerson)(ou=Developers))

dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru
objectClass: groupOfURLs
cn: Designers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(&(objectClass=inetOrgPerson)(ou=Designers))

dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: Developers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru??one?(&(objectClass=inetOrgPerson)(ou=Developers))

dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: Designers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru??one?(&(objectClass=inetOrgPerson)(ou=Designers))

Всё в порядке, за исключением одной маленькой детали: в записях нет динамического содержимого. И проблема тут не в ошибках настройки, а в принятой в компании политике доступа к учётным записям пользователей (тот самый ACL, который был добавлен на первом шаге). В этом легко можно убедиться, выполнив поиск не анонимно, а от имени одного из пользователей:

$ ldapsearch -xLLL -o ldif-wrap=no -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword -b dc=mycompany,dc=ru '(memberURL=*)'
dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru
objectClass: groupOfURLs
cn: All
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(objectClass=inetOrgPerson)
mail: ivanov@mycompany.ru

dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru
objectClass: groupOfURLs
cn: Developers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(&(objectClass=inetOrgPerson)(ou=Developers))
mail: ivanov@mycompany.ru

dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru
objectClass: groupOfURLs
cn: Designers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(&(objectClass=inetOrgPerson)(ou=Designers))

dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: Developers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru??one?(&(objectClass=inetOrgPerson)(ou=Developers))
roleOccupant: uid=ivanov,ou=People,dc=mycompany,dc=ru

dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: Designers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru??one?(&(objectClass=inetOrgPerson)(ou=Designers))

Обратите внимание на динамические атрибуты, появившиеся в списках рассылки All и Developers, а также в группе Developers, они отражают информацию только по самому пользователю, подключившемуся к каталогу — всё согласно настроек контроля доступа.

Конечно, это ограничение можно обойти, подключаясь к каталогу от имени rootDN этого каталога. Но, согласитесь, такое решение не выглядит оптимальным. Как раз на случай наличия в рабочем окружении сложных настроек контроля доступа в наложении dynlist предусмотрен механизм нестандартной авторизации при выполнении поиска и формирования динамического содержимого. Суть её изложена в man-странице наложения dynlist, выдержку из которой мы приведём здесь:

По умолчанию поиск по URI и последующее наполнение записи выполняется с использованием идентификационной сущности текущего пользователя LDAP. Эту идентификационную сущность можно переопределить путём указания в атрибуте dgIdentity записи группы DN другого пользователя LDAP. В этом случае при поиске по URI и наполнении объекта будет использоваться идентификационная сущность пользователя, указанного в dgIdentity. Если в качестве значения dgIdentity задана строка нулевой длины, наполнение записи будет выполняться анонимно. Обратите внимание, что атрибут dgIdentity определён в наборе схемы данных dyngroup, который должен быть загружен перед тем, как использовать возможность авторизации с dgIdentity.

Всё просто: если у нас имеется пользователь с требуемыми правами доступа к каталогу, то DN этого пользователя можно указать в качестве значения атрибута dgIdentity в записи динамического объекта, и тогда поиск динамического содержимого будет осуществляться от имени данного уполномоченного пользователя.

Попробуем воплотить это в жизнь. Для начала нам нужен некий пользователь с правами на доступ к контейнеру ou=People,dc=mycompany,dc=ru. Внесём изменения в списки контроля доступа к нашему рабочему каталогу, дополнив второй ACL правами на чтение для специального пользователя dynlistReader (строка 8):

dn: olcDatabase={1}mdb,cn=config
changetype: modify
delete: olcAccess
olcAccess: {1}
-
add: olcAccess
olcAccess: {1}to dn.one="ou=People,dc=mycompany,dc=ru"
  by dn.exact="cn=dynlistReader,ou=System,dc=mycompany,dc=ru" read
  by self write
  by * none

Внесём изменения в конфигурационный каталог cn=config и посмотрим, что получилось:

$ ldapmodify -x -D cn=config -W -f ./103-replace_people_branch_acl.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 -s base 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.one="ou=People,dc=mycompany,dc=ru" by dn.exact="cn=dynlistReader,ou=System,dc=mycompany,dc=ru" read by self write by * none
olcAccess: {2}to * by self write by * read

Обратите внимание, что запись в каталоге для пользователя cn=dynlistReader,ou=System,dc=mycompany,dc=ru мы не заводили, поэтому подключиться нормальным образом от имени этого пользователя к каталогу мы не сможем. Тем не менее у этой фиктивной записи есть права на чтение, и мы можем этим воспользоваться.

Сформируем LDIF-файл для модификации наших динамических объектов:

dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru
changetype: modify
add: objectClass
objectClass: dgIdentityAux
-
add: dgIdentity
dgIdentity: cn=dynlistReader,ou=System,dc=mycompany,dc=ru

dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru
changetype: modify
add: objectClass
objectClass: dgIdentityAux
-
add: dgIdentity
dgIdentity: cn=dynlistReader,ou=System,dc=mycompany,dc=ru

dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru
changetype: modify
add: objectClass
objectClass: dgIdentityAux
-
add: dgIdentity
dgIdentity: cn=dynlistReader,ou=System,dc=mycompany,dc=ru

dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: dgIdentity
dgIdentity: cn=dynlistReader,ou=System,dc=mycompany,dc=ru

dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: dgIdentity
dgIdentity: cn=dynlistReader,ou=System,dc=mycompany,dc=ru

Итак, мы дополнили каждую запись динамического объекта сведениями о том, что мы хотим проводить поиск в каталоге динамического содержимого от имени (с правами) записи cn=dynlistReader,ou=System,dc=mycompany,dc=ru. Обратите внимание, что в объектах динамических списков рассылки для добавления нового атрибута нам пришлось подключать вспомогательный объектный класс dgIdentityAux, который входит в набор схемы данных dyngroup и содержит требуемый нам тип атрибута dgIdentity. Для записей же динамических групп это не потребовалось, поскольку они уже включали специальный вспомогательный класс extensibleObject, позволяющий добавить в запись любой известный LDAP-серверу пользовательский атрибут.

Применим наши изменения:

$ ldapmodify -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./004-mod_dynamic_objects_add_dgIdentity.ldif
Enter LDAP Password: 
modifying entry "cn=All,ou=MailingLists,dc=mycompany,dc=ru"

modifying entry "cn=Developers,ou=MailingLists,dc=mycompany,dc=ru"

modifying entry "cn=Designers,ou=MailingLists,dc=mycompany,dc=ru"

modifying entry "cn=Developers,ou=Groups,dc=mycompany,dc=ru"

modifying entry "cn=Designers,ou=Groups,dc=mycompany,dc=ru"

Проверим, что получилось:

$ ldapsearch -xLLL -o ldif-wrap=no -b dc=mycompany,dc=ru '(memberURL=*)'
dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru
objectClass: groupOfURLs
objectClass: dgIdentityAux
cn: All
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(objectClass=inetOrgPerson)
dgIdentity: cn=dynlistReader,ou=System,dc=mycompany,dc=ru
mail: antonova@mycompany.ru
mail: ivanov@mycompany.ru
mail: petrov@mycompany.ru
mail: sidorov@mycompany.ru

dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru
objectClass: groupOfURLs
objectClass: dgIdentityAux
cn: Developers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(&(objectClass=inetOrgPerson)(ou=Developers))
dgIdentity: cn=dynlistReader,ou=System,dc=mycompany,dc=ru
mail: ivanov@mycompany.ru
mail: petrov@mycompany.ru

dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru
objectClass: groupOfURLs
objectClass: dgIdentityAux
cn: Designers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(&(objectClass=inetOrgPerson)(ou=Designers))
dgIdentity: cn=dynlistReader,ou=System,dc=mycompany,dc=ru
mail: antonova@mycompany.ru
mail: sidorov@mycompany.ru

dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: Developers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru??one?(&(objectClass=inetOrgPerson)(ou=Developers))
dgIdentity: cn=dynlistReader,ou=System,dc=mycompany,dc=ru
roleOccupant: uid=ivanov,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=petrov,ou=People,dc=mycompany,dc=ru

dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru
objectClass: organizationalRole
objectClass: extensibleObject
cn: Designers
memberURL: ldap:///ou=People,dc=mycompany,dc=ru??one?(&(objectClass=inetOrgPerson)(ou=Designers))
dgIdentity: cn=dynlistReader,ou=System,dc=mycompany,dc=ru
roleOccupant: uid=antonova,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=sidorov,ou=People,dc=mycompany,dc=ru

Как видите, наша нестандартная авторизация работает, и даже выполняя анонимный поисковый запрос, мы получаем желаемое динамическое содержимое. При этом ограничения на доступ к учётным записям пользователей остаются в силе (анонимный поиск в ветке ou=People,dc=mycompany,dc=ru не даёт результата):

$ ldapsearch -xLLL -b ou=People,dc=mycompany,dc=ru -s one

Таким образом, поставленная задача успешно решена.

Что ещё можно сделать. "Вторичная" авторизация, или ограничение на поиск динамического содержимого

Функционал нестандартной авторизации наложения dynlist позволяет устанавливать собственные ограничения на поиск динамического содержимого. Ещё раз обратимся к разделу "Авторизация" man-страницы наложения:

Если в записи группы кроме атрибута dgIdentity присутствует атрибут dgAuthz, его значение используется для определения того, какие идентификационные сущности авторизованы использовать для наполнения этой группы идентификационную сущность, указанную в атрибуте dgIdentity. Значения в атрибуте dgAuthz должны соответствовать экспериментальному синтаксису OpenLDAP authz.

То есть, можно определять конкретные идентификационные сущности, которые будут уполномочены (авторизованы) воспользоваться нестандартной авторизацией при поиске динамического содержимого. Идентификационные сущности можно задавать различными путями в соответствии с синтаксисом OpenLDAP authz, определение которого можно найти в описании директивы authz-policy в man-странице slapd.conf(5). Для нашего примера мы воспользуемся самым простым вариантом: будем задавать идентификационные сущности в виде DN учётной записи пользователя.

Для начала поставим простой эксперимент: попробуем ограничить доступ к возможности построения динамического содержимого списков рассылки. Составим следующий LDIF-файл:

dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru
changetype: modify
add: dgAuthz
dgAuthz: dn:*

dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru
changetype: modify
add: dgAuthz
dgAuthz: dn:uid=ivanov,ou=People,dc=mycompany,dc=ru
dgAuthz: dn:uid=petrov,ou=People,dc=mycompany,dc=ru

dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru
changetype: modify
add: dgAuthz
dgAuthz: uid=antonova,ou=People,dc=mycompany,dc=ru
dgAuthz: uid=sidorov,ou=People,dc=mycompany,dc=ru

Мы добавляем в записи динамических списков атрибут dgAuthz. Для списка All в значении этого атрибута мы указываем специальную конструкцию dn:* (строка 4). Согласно синтаксису authz, она совпадает с любым неанонимным DN подключения. Для списков отделов мы ограничили доступ к возможности построения динамического содержимого сотрудниками этих отделов (строки 9-10 и 15-16).

Применим изменения:

$ ldapmodify -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./005-mod_dynamic_objects_add_dgAuthz.ldif
Enter LDAP Password:
modifying entry "cn=All,ou=MailingLists,dc=mycompany,dc=ru"

modifying entry "cn=Developers,ou=MailingLists,dc=mycompany,dc=ru"

modifying entry "cn=Designers,ou=MailingLists,dc=mycompany,dc=ru"

Теперь попробуем получить содержимое наших динамических объектов (и списков, и групп). Сначала сделаем это анонимно:

$ ldapsearch -xLLL -o ldif-wrap=no -b dc=mycompany,dc=ru '(dgIdentity=*)' mail roleOccupant
dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru

dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru

dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru

dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru
roleOccupant: uid=ivanov,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=petrov,ou=People,dc=mycompany,dc=ru

dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru
roleOccupant: uid=antonova,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=sidorov,ou=People,dc=mycompany,dc=ru

Как видно, группы (где мы не накладывали ограничений) продолжают наполняться динамическим содержимым, а списки — нет. Теперь попробуем выполнить поиск от имени конкретного пользователя:

$ ldapsearch -xLLL -o ldif-wrap=no -b dc=mycompany,dc=ru -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword '(dgIdentity=*)' mail roleOccupant
dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru
mail: antonova@mycompany.ru
mail: ivanov@mycompany.ru
mail: petrov@mycompany.ru
mail: sidorov@mycompany.ru

dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru
mail: ivanov@mycompany.ru
mail: petrov@mycompany.ru

dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru

dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru
roleOccupant: uid=ivanov,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=petrov,ou=People,dc=mycompany,dc=ru

dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru
roleOccupant: uid=antonova,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=sidorov,ou=People,dc=mycompany,dc=ru

Поскольку поиск у нас неанонимный, то список All получил динамическое содержимое. Также получил динамическое содержимое и список Developers, где DN пользователя ivanov (от имени которого выполнялся поиск) явно указано в значении атрибута dgAuthz. А вот список Designers не получил, поскольку среди значений атрибута dgAuthz записи этого списка не было перечислено DN пользователя ivanov. Как и в предыдущем случае, обе динамические группы (на которые не накладывалось дополнительных ограничений) динамическое содержимое получили. Таким образом, ограничения наложения dynlist на возможность выполнения поиска динамического содержимого только уполномоченными на это идентификационными сущностями работают, как заявлено.

В финале этого урока рассмотрим более жизнеспособный пример. Допустим, мы хотим оставить в силе действующий в компании запрет на доступ к учётным записям пользователей, а также не хотим, чтобы пользователи, подключаясь к каталогу от имени своей учётной записи, могли косвенным образом получить информацию о принадлежности сотрудников подразделениям по динамическому содержимому списков рассылки и групп. С другой стороны, нам необходимо обеспечить работоспособность того программного обеспечения, которое строит свою работу на основании этих динамических списков и групп. В этом случае мы можем завести специальные учётные записи для чтения содержимого динамических объектов (отдельную для почтового сервера, отдельную для приложений, которым требуется информация по группам пользователей) и предоставить в динамических объектах права на поиск динамического содержимого именно этим учётным записям.

Итак, создадим простейшие учётные записи, содержащие только имя пользователя и пароль, в специальном контейнере ou=System,dc=mycompany,dc=ru. Этот контейнер мы уже применяли для наших "фиктивных" учёток (rootDN и dynlistReader), пришла пора создать его явно:

dn: ou=System,dc=mycompany,dc=ru
objectClass: organizationalUnit
ou: System

dn: cn=mailerReader,ou=System,dc=mycompany,dc=ru
objectClass: person
cn: mailerReader
sn: mailerReader
userPassword: mailerReaderPassword

dn: cn=rolesReader,ou=System,dc=mycompany,dc=ru
objectClass: person
cn: rolesReader
sn: rolesReader
userPassword: rolesReaderPassword

Добавим наши записи в каталог и убедимся в их наличии:

$ ldapadd -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./006-add_system_users.ldif
Enter LDAP Password:
adding new entry "ou=System,dc=mycompany,dc=ru"

adding new entry "cn=mailerReader,ou=System,dc=mycompany,dc=ru"

adding new entry "cn=rolesReader,ou=System,dc=mycompany,dc=ru"

$ ldapsearch -xLLL -o ldif-wrap=no -b ou=System,dc=mycompany,dc=ru
dn: ou=System,dc=mycompany,dc=ru
objectClass: organizationalUnit
ou: System

dn: cn=rolesReader,ou=System,dc=mycompany,dc=ru
objectClass: person
cn: rolesReader
sn: rolesReader

dn: cn=mailerReader,ou=System,dc=mycompany,dc=ru
objectClass: person
cn: mailerReader
sn: mailerReader

Ну а теперь нам осталось внести изменения в записи динамических объектов. В случае динамических списков рассылки мы заменим значения атрибута dgAuthz на DN пользователя mailerReader, а в динамические группы, в которых до этого не было данного атрибута, мы просто добавим его, указав в значении DN пользователя rolesReader:

dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru
changetype: modify
replace: dgAuthz
dgAuthz: dn:cn=mailerReader,ou=System,dc=mycompany,dc=ru

dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru
changetype: modify
replace: dgAuthz
dgAuthz: dn:cn=mailerReader,ou=System,dc=mycompany,dc=ru

dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru
changetype: modify
replace: dgAuthz
dgAuthz: dn:cn=mailerReader,ou=System,dc=mycompany,dc=ru

dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: dgAuthz
dgAuthz: dn:cn=rolesReader,ou=System,dc=mycompany,dc=ru

dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: dgAuthz
dgAuthz: dn:cn=rolesReader,ou=System,dc=mycompany,dc=ru

Применим данные изменения:

$ ldapmodify -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./007-mod_dynamic_objects_replace_dgAuthz.ldif
Enter LDAP Password:
modifying entry "cn=All,ou=MailingLists,dc=mycompany,dc=ru"

modifying entry "cn=Developers,ou=MailingLists,dc=mycompany,dc=ru"

modifying entry "cn=Designers,ou=MailingLists,dc=mycompany,dc=ru"

modifying entry "cn=Developers,ou=Groups,dc=mycompany,dc=ru"

modifying entry "cn=Designers,ou=Groups,dc=mycompany,dc=ru"

Нам осталось проверить правильность настроек контроля доступа при поиске и формировании динамического содержимого. Для этого выполним три поисковых запроса: от имени одного из сотрудников компании и от имени двух заведённых нами специальных учётных записей:

$ ldapsearch -xLLL -o ldif-wrap=no -b dc=mycompany,dc=ru -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword '(dgIdentity=*)' mail roleOccupant
dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru

dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru

dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru

dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru

dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru

$ ldapsearch -xLLL -o ldif-wrap=no -b dc=mycompany,dc=ru -D cn=mailerReader,ou=System,dc=mycompany,dc=ru -w mailerReaderPassword '(dgIdentity=*)' mail roleOccupant
dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru
mail: antonova@mycompany.ru
mail: ivanov@mycompany.ru
mail: petrov@mycompany.ru
mail: sidorov@mycompany.ru

dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru
mail: ivanov@mycompany.ru
mail: petrov@mycompany.ru

dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru
mail: antonova@mycompany.ru
mail: sidorov@mycompany.ru

dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru

dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru

$ ldapsearch -xLLL -o ldif-wrap=no -b dc=mycompany,dc=ru -D cn=rolesReader,ou=System,dc=mycompany,dc=ru -w rolesReaderPassword '(dgIdentity=*)' mail roleOccupant
dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru

dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru

dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru

dn: cn=Developers,ou=Groups,dc=mycompany,dc=ru
roleOccupant: uid=ivanov,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=petrov,ou=People,dc=mycompany,dc=ru

dn: cn=Designers,ou=Groups,dc=mycompany,dc=ru
roleOccupant: uid=antonova,ou=People,dc=mycompany,dc=ru
roleOccupant: uid=sidorov,ou=People,dc=mycompany,dc=ru

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

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

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

  1. При обнаружении в каталоге записей, которые должны быть обработаны наложением dynlist, всегда предпринимается попытка поиска в них атрибутов dgIdentity и dgAuthz, независимо от того, определялись ли они в записи и был ли вообще загружен набор схемы данных dyngroup. То есть обработка этих атрибутов — постоянная часть функционала наложения.
  2. Если попытаться определить только значения атрибута dgAuthz, не задавая при этом значений dgIdentity, например, когда динамическое содержимое и так заполняется, а хочется как раз наложить ограничение на его построение, то ничего не выйдет: наложение dynlist не будет отдельно обрабатывать атрибуты dgAuthz. Это ограничение функционала довольно просто обойти: достаточно задать в записи динамического объекта атрибут dgIdentity с каким-нибудь фиктивным DN, например cn=dymmy, и наложение начнёт обрабатывать значения атрибутов dgAuthz.
  3. В отличие от ACL, которые задаются на уровне конфигурационного каталога cn=config и требуют соответствующих прав на внесение изменений в данный каталог, функционал авторизации наложения dynlist настраивается на уровне рабочего каталога и любой пользователь, имеющий права на запись в рабочем каталоге, может установить/поменять атрибуты динамических объектов, тем самым задавая дополнительные права и ограничения на построение динамического содержимого. Администраторам рабочего каталога следует помнить об этом и принимать адекватные меры защиты.
  4. На уровне ACL можно задать ограничения на вывод атрибутов dgIdentity и dgAuthz, также как на прошлом уроке мы ограничивали доступ к атрибуту labeledURI. На работу наложения сокрытие этих атрибутов не повлияет.

Итоговые настройки каталога 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.one="ou=People,dc=mycompany,dc=ru"
  by dn.exact="cn=dynlistReader,ou=System,dc=mycompany,dc=ru" read
  by self write
  by * none
olcAccess: {2}to *
  by self write
  by * read
olcRootDN: cn=manager,ou=System,dc=mycompany,dc=ru
olcRootPW: {SSHA}PKFrwbIL/zLd3gabPPLxn1vNq2jQHj4g
olcDbIndex: objectClass eq
olcDbIndex: cn eq,sub,subinitial

dn: olcOverlay={0}dynlist,olcDatabase={1}mdb,cn=config
objectClass: olcDynamicList
olcOverlay: {0}dynlist
olcDlAttrSet: {0}groupOfURLs memberURL
olcDlAttrSet: {1}organizationalRole memberURL roleOccupant
Pro-LDAP.ru 2013-2020 г. Последнее изменение страницы — 9 января 2020 г. Вопросы и предложения принимаются на форуме проекта.