Предпосылки: В организации, сведения о которой ведутся в нашем каталоге, два отдела: Developers и Designers. Информация о принадлежности пользователя отделу фиксируется как значение атрибута ou
в учётной записи каждого пользователя. На почтовом сервере организации обслуживаются почтовые ящики пользователей, адреса электронной почты фиксируются как значение атрибута mail
в учётной записи каждого пользователя.
Принятая в организация политика разграничения доступа к каталогу строго ограничивает доступ к учётным записям пользователей: прошедший аутентификацию пользователь может получить доступ только к своей учётной записи, анонимный доступ к учётным записям запрещён.
Задача: Требуется создать списки рассылки, которые будут использоваться почтовым сервером для организации рассылки писем всем сотрудникам организации и отдельно сотрудникам каждого подразделения. Кроме того, для разграничения доступа различных приложений требуется организовать объекты классических групп для имеющихся отделов. Списки рассылки и групповые объекты должны постоянно отражать актуальную структуру организации.
Как и всегда, начинать будем с исходного положения каталога. Сначала мы должны привести каталог в соответствие выдвинутым условиям задачи, то есть дополнить учётные записи пользователей требуемыми атрибутами и модифицировать условия получения доступа к этим учётным записям (иными словами, привести в соответствие ACL). Затем мы настроим наложение dynlist
, предварительно загрузив необходимые для его работы данные и динамический модуль. Наконец, введём в каталог динамические объекты списков и групп, дополнительные настройки авторизации которых позволят решить поставленную задачу.
Данный шаг состоит из двух этапов. Сначала нужно дополнить учётные записи каталога перечисленными в предпосылках к задаче атрибутами с признаком принадлежности сотрудника к тому или иному отделу, а также адресом электронной почты. Для этого сформируем такой 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 в списке). Условия нашей задачи соблюдаются, можно переходить ко второму шагу.
Как и в предыдущих уроках, для организации работы с динамическими объектами (группами и списками) нам нужно подгрузить динамический модуль наложения и добавить запись настройки самого наложения к рабочей базе данных 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
Всё на месте, пора переходить к следующему шагу.
Начнём с того, что мы уже не раз делали: добавим в рабочий каталог объекты динамических списков рассылки (в отдельный контейнер) и групп. Сформируем такой 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
можно частично (в рамках задачи поиска и построения динамического содержимого динамических объектов) обходить установленные на уровне настроек каталога ограничения на доступ к объектам каталога. Кроме того, мы увидели и обратную возможность: средствами того же наложения установить дополнительные ограничения путём выдачи полномочий лишь определённым идентификационным сущностям выполнять поиск и построение динамического содержимого динамических объектов. В этой связи нужно отметить несколько моментов:
dynlist
, всегда предпринимается попытка поиска в них атрибутов dgIdentity
и dgAuthz
, независимо от того, определялись ли они в записи и был ли вообще загружен набор схемы данных dyngroup
. То есть обработка этих атрибутов — постоянная часть функционала наложения.dgAuthz
, не задавая при этом значений dgIdentity
, например, когда динамическое содержимое и так заполняется, а хочется как раз наложить ограничение на его построение, то ничего не выйдет: наложение dynlist
не будет отдельно обрабатывать атрибуты dgAuthz
. Это ограничение функционала довольно просто обойти: достаточно задать в записи динамического объекта атрибут dgIdentity
с каким-нибудь фиктивным DN, например cn=dymmy
, и наложение начнёт обрабатывать значения атрибутов dgAuthz
.cn=config
и требуют соответствующих прав на внесение изменений в данный каталог, функционал авторизации наложения dynlist
настраивается на уровне рабочего каталога и любой пользователь, имеющий права на запись в рабочем каталоге, может установить/поменять атрибуты динамических объектов, тем самым задавая дополнительные права и ограничения на построение динамического содержимого. Администраторам рабочего каталога следует помнить об этом и принимать адекватные меры защиты.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