Предпосылки: В организации, сведения о которой ведутся в нашем каталоге, два отдела: Developers и Designers. Информация о принадлежности пользователя отделу фиксируется как значение атрибута ou
в учётной записи каждого пользователя. На почтовом сервере организации обслуживаются почтовые ящики пользователей, адреса электронной почты фиксируются как значение атрибута mail
в учётной записи каждого пользователя.
Задача: Требуется создать списки рассылки, которые будут использоваться почтовым сервером для организации рассылки писем всем сотрудникам организации и отдельно сотрудникам каждого подразделения. Эти списки должны постоянно отражать актуальную структуру организации.
Поскольку мы будем использовать динамическое построение списков, то проблема того, что нужно сделать в первую очередь, — модифицировать записи пользователей, настроить само наложение или добавить заготовки под динамически формируемые записи, — остро не стоит. Вне зависимости от порядка этих действий в итоге мы получим работоспособные динамические списки.
В данном примере мы покажем "прямой" порядок: учётки, наложение, списки.
На этом этапе мы приведём наш каталог в соответствие с предпосылками к нашей задаче, то есть добавим к учётным записям пользователей, которые имеются в нашем каталоге в его исходном положении, признак их принадлежности к отделу и адрес электронной почты. Для этого сформируем такой 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 -c -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 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 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 dn: uid=antonova,ou=People,dc=mycompany,dc=ru objectClass: inetOrgPerson uid: antonova cn: Antonina Antonova sn: Antonova ou: Designers mail: antonova@mycompany.ru
Отлично! Необходимые для построения динамических списков рассылки сведения помещены в каталог. Переходим ко второму этапу.
Как мы помним, в нашей сборке OpenLDAP все механизмы манипуляции данными и наложения собраны в виде динамических модулей. Поэтому, перед тем, как приступить к настройкам наложения, его требуется подгрузить в работающий сервер slapd
. Как обычно, для этого мы создадим соответствующий LDIF-файл:
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: dynlist.la
Подгрузим наш модуль и убедимся, что он загружен:
$ ldapmodify -x -D cn=config -W -f ./101-add_dynlist_module.ldif Enter LDAP Password: modifying entry "cn=module{0},cn=config" $ ldapsearch -xLLL -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
Можно приступать к настройкам. Для данного примера они получились очень простыми:
dn: olcOverlay=dynlist,olcDatabase={1}mdb,cn=config
objectClass: olcDynamicList
olcOverlay: dynlist
olcDLattrSet: groupOfURLs memberURL
К записи нашей рабочей базы данных mdb
добавляется дочерняя запись наложения с объектным классом olcDynamicList
. Параметры работы наложения задаются значением атрибута olcDLattrSet
(строка 4). Как мы видим, у нас два разделённых пробелом параметра: имя объектного класса (groupOfURLs
) и имя типа атрибута (memberURL
). Наложение dynlist
наполняет динамическим содержимым записи, возвращаемые в ответ на поисковый запрос LDAP. Чтобы отобрать среди всех возвращаемых записей те, которые требуется модифицировать, наложению необходимы критерии отбора. Именно их мы в данном случае и задаём. То есть запись, отобранная для возврата в ответ на поисковый запрос LDAP, будет подвержена модификации (дополнена динамическим содержимым), только если эта запись строится на объектном классе groupOfURLs
и содержит атрибут memberURL
. Обратите внимание, что в настройках наложения никоим образом не регламентируется собственно динамическое содержимое, то есть то, чем именно будут наполняться записи. Определение динамического содержимого отдано в ведение пользователя каталога: оно задаётся в виде параметров поискового запроса, оформленных как LDAP URI, который помещается в значение атрибута, тип которого указан в параметрах наложения (в данном случае в значение атрибута memberURL
). То есть, зная правила составления LDAP URI, пользователь может настроить записи с динамическим содержимым под свою задачу.
Для нашего примера с классическими динамическими списками мы выбрали "классические" объектный класс и тип атрибута, которые указываются в примерах настройки в man-странице наложения dynlist. В стандартную схему данных сервера slapd
они не входят, поэтому перед тем, как их использовать, требуется указать их определения серверу. Это делается путём добавления набора схемы данных dyngroup
(входит в состав пакета OpenLDAP):
$ ldapadd -x -D cn=config -W -f /etc/ldap/schema/dyngroup.ldif Enter LDAP Password: adding new entry "cn=dyngroup,cn=schema,cn=config" $ ldapsearch -xLLL -D cn=config -W -b cn=schema,cn=config 1.1 Enter LDAP Password: dn: cn=schema,cn=config dn: cn={0}core,cn=schema,cn=config dn: cn={1}cosine,cn=schema,cn=config dn: cn={2}nis,cn=schema,cn=config dn: cn={3}inetorgperson,cn=schema,cn=config dn: cn={4}dyngroup,cn=schema,cn=config
Заодно посмотрим, что же было в добавленном нами наборе:
$ ldapsearch -xLLL -D cn=config -W -b cn={4}dyngroup,cn=schema,cn=config -o ldif-wrap=no Enter LDAP Password: dn: cn={4}dyngroup,cn=schema,cn=config objectClass: olcSchemaConfig cn: {4}dyngroup olcObjectIdentifier: {0}NetscapeRoot 2.16.840.1.113730 olcObjectIdentifier: {1}NetscapeLDAP NetscapeRoot:3 olcObjectIdentifier: {2}NetscapeLDAPattributeType NetscapeLDAP:1 olcObjectIdentifier: {3}NetscapeLDAPobjectClass NetscapeLDAP:2 olcObjectIdentifier: {4}OpenLDAPExp11 1.3.6.1.4.1.4203.666.11 olcObjectIdentifier: {5}DynGroupBase OpenLDAPExp11:8 olcObjectIdentifier: {6}DynGroupAttr DynGroupBase:1 olcObjectIdentifier: {7}DynGroupOC DynGroupBase:2 olcAttributeTypes: {0}( NetscapeLDAPattributeType:198 NAME 'memberURL' DESC 'Identifies an URL associated with each member of a group. Any type of labeled URL can be used.' SUP labeledURI ) olcAttributeTypes: {1}( DynGroupAttr:1 NAME 'dgIdentity' DESC 'Identity to use when processing the memberURL' SUP distinguishedName SINGLE-VALUE ) olcAttributeTypes: {2}( DynGroupAttr:2 NAME 'dgAuthz' DESC 'Optional authorization rules that determine who is allowed to assume the dgIdentity' EQUALITY authzMatch SYNTAX 1.3.6.1.4.1.4203.666.2.7 X-ORDERED 'VALUES' ) olcObjectClasses: {0}( NetscapeLDAPobjectClass:33 NAME 'groupOfURLs' SUP top STRUCTURAL MUST cn MAY ( memberURL $ businessCategory $ description $ o $ ou $ owner $ seeAlso ) ) olcObjectClasses: {1}( DynGroupOC:1 NAME 'dgIdentityAux' SUP top AUXILIARY MAY ( dgIdentity $ dgAuthz ) )
Сначала добавляются 8 новых идентификаторов объектов, затем 3 типа атрибута (memberURL
, dgIdentity
и dgAuthz
) и 2 объектных класса (groupOfURLs
и dgIdentityAux
). На данном этапе главное для нас — наличие типа атрибута memberURL
и объектного класса groupOfURLs
, которые используются в данном примере. Использование остальных типов атрибутов и объектного класса мы рассмотрим позже.
Наконец, добавим описанную выше запись с настройками нашего наложения и убедимся в её наличии:
$ ldapadd -x -D cn=config -W -f ./102-add_dynlist_overlay.ldif Enter LDAP Password: adding new entry "olcOverlay=dynlist,olcDatabase={1}mdb,cn=config" $ ldapsearch -xLLL -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
Наложение стало первым (индекс 0) для нашей рабочей базы данных. Пора переходить к следующему этапу.
Чтобы не нарушать стройной иерархии нашего каталога, для списков рассылки электронной почты мы заведём отдельный контейнер MailingLists. Согласно условиям задачи, составим сразу определения для трёх динамических списков рассылки: All (все сотрудники организации), Developers и Designers (сотрудники соответствующих отделов):
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))
Разберём подробнее первый список рассыки All (строки 5-8). Он строится на структурном объектном классе groupOfURLs
(тот самый класс, на который, согласно нашим настройкам, будет срабатывать наложение dynlist
). Согласно схеме данных, обязательным атрибутом данного класса является cn
, что удобно при формировании RDN записи. Одним из необязательных атрибутов этого же класса является memberURL
, который также фигурирует в настройках наложения dynlist
. Значением данного атрибута является LDAP URI, описывающий параметры дополнительного поискового запроса LDAP в тот же каталог. Разберём его по частям.
№ | Компонент URI | В нашем случае | Пояснение |
---|---|---|---|
1 | Схема URI | ldap:// | Означает, что будет выполняться запрос протокола LDAP. |
2 | Адрес сервера | не указан | Пустое место между вторым и третьим слешем означает, что адрес сервера не указан, и запрос будет обрабатываться на локальном сервере. |
3 | База поиска | ou=People,dc=mycompany,dc=ru | Поддерево, начиная с которого будут отбираться записи. |
4 | Возвращаемые атрибуты | Из найденных записей будут возвращены только указанные атрибуты. Если нужно указать несколько возвращаемых атрибутов, они перечисляются через запятую. Если в этом компоненте не будет указано никаких атрибутов, то будут возвращены все пользовательские атрибуты. | |
5 | Диапазон поиска | one | Будут рассматриваться только записи, непосредственно дочерние записи-базе поиска. |
6 | Поисковый фильтр | (objectClass=inetOrgPerson) | Среди всех записей, попадающих в диапазон поиска, будут отобраны только те, которые строятся на объектном классе inetOrgPerson |
Как видите, задаются все параметры стандартного поискового запроса. Итак, в нашей записи нет ничего лишнего: атрибут cn
со значением All, формирующим RDN записи, и атрибут memberURL
, значением которого является LDAP URI, описывающий поисковый запрос LDAP, ответные данные которого (значения атрибутов mail
найденных записей) будут формировать динамическое содержимое нашего списка.
Два других списка (Developers и Designers), фактически, отличаются только дополнительными критериями отбора в поисковых фильтрах LDAP URI, задаваемых в атрибутах memberURL
. По этим критериям среди всех учётных записей в ветке ou=People,dc=mycompany,dc=ru будут отбираться только учётные записи пользователей, принадлежащих соответствующим отделам.
Добавим наши динамические списки:
$ ldapadd -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./003-add_mailing_lists.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"
Попробуем извлечь добавленные записи. Заодно убедимся, что наложение dynlist
работает:
$ ldapsearch -xLLL -b ou=MailingLists,dc=mycompany,dc=ru -s one -o ldif-wrap=no 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: antonova@mycompany.ru mail: ivanov@mycompany.ru mail: petrov@mycompany.ru mail: sidorov@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)) mail: antonova@mycompany.ru mail: sidorov@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 mail: petrov@mycompany.ru
Как видно, в наших записях, кроме статически заданных нами атрибутов, появились динамически сформированные атрибуты mail
со значениями, отобранными в результате выполнения дополнительного поиска.
Разберём работу наложения dynlist
поэтапно:
ldapsearch
, записи, отобранные согласно этим критериям, пользователю не возвращаются, а передаются на обработку наложению dynlist
.groupOfURLs
и имеют атрибут memberURL
. В нашем случае в этот список попадут записи cn=All,ou=MailingLists,dc=mycompany,dc=ru, cn=Designers,ou=MailingLists,dc=mycompany,dc=ru и cn=Developers,ou=MailingLists,dc=mycompany,dc=ru.memberURL
. Например, для записи cn=Designers,ou=MailingLists,dc=mycompany,dc=ru будет выполнен запрос с базой поиска ou=People,dc=mycompany,dc=ru, диапазоном поиска one и фильтром (&(objectClass=inetOrgPerson)(ou=Designers)).mail
со значениями antonova@mycompany.ru и sidorov@mycompany.ru соответственно.Всё просто и удобно. Динамические списки будут актуальны всегда, если администратор будет своевременно корректировать учётные записи пользователей, например, когда кто-то из них переведётся в другой отдел или уволится.
Убедимся в этом. Допустим, Сидор Сидоров уволился, а Пётр Петров решил перевестись в отдел дизайна. В каталог будут внесены следующие изменения:
dn: uid=sidorov,ou=People,dc=mycompany,dc=ru
changetype: delete
dn: uid=petrov,ou=People,dc=mycompany,dc=ru
changetype: modify
replace: ou
ou: Designers
Внесём изменения в каталог и убедимся в корректности полученных в результате записей:
$ ldapmodify -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./004-replace_users.ldif Enter LDAP Password: deleting entry "uid=sidorov,ou=People,dc=mycompany,dc=ru" modifying entry "uid=petrov,ou=People,dc=mycompany,dc=ru" $ ldapsearch -xLLL -b ou=People,dc=mycompany,dc=ru -s one '(|(uid=sidorov)(uid=petrov))' dn: uid=petrov,ou=People,dc=mycompany,dc=ru objectClass: inetOrgPerson uid: petrov cn: Petr Petrov sn: Petrov mail: petrov@mycompany.ru ou: Designers
Запись sidorov отсутствует в каталоге, а в атрибуте ou
записи petrov указан отдел Designers. Посмотрим, что стало с динамическими списками:
$ ldapsearch -xLLL -b ou=MailingLists,dc=mycompany,dc=ru -s one mail dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru mail: antonova@mycompany.ru mail: ivanov@mycompany.ru mail: petrov@mycompany.ru dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru mail: antonova@mycompany.ru mail: petrov@mycompany.ru dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru mail: ivanov@mycompany.ru
Всё работает, как и ожидалось.
Случается, что сотрудник организации довольно длительное время может отсутствовать на рабочем месте. Тогда его почтовый ящик скорее всего забьётся сообщениями, которые к моменту его возвращения уже утратят свою актуальность. Мы можем попытаться "блокировать" рассылку почты для таких сотрудников, и динамические списки отлично подходят для решения такой задачи.
Предположим, Тоня Антонова ушла в декретный отпуск, и мы пометили её учётную запись как заблокированную путём добавления в неё атрибута l
со значением locked:
dn: uid=antonova,ou=People,dc=mycompany,dc=ru
changetype: modify
add: l
l: locked
Внесём изменения в каталог и убедимся, что всё в порядке:
$ ldapmodify -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./005-locked_antonova.ldif Enter LDAP Password: modifying entry "uid=antonova,ou=People,dc=mycompany,dc=ru" $ ldapsearch -xLLL -b dc=mycompany,dc=ru '(uid=antonova)' l dn: uid=antonova,ou=People,dc=mycompany,dc=ru l: locked
Мы можем использовать вновь полученное свойство учётных записей для модификации поисковых фильтров в LDAP URI записей динамических списков так, чтобы в результате их применения для построения динамического содержимого отбирались почтовые ящики только активных (незаблокированных) в данный момент пользователей. Составим такой LDIF-файл:
dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru
changetype: modify
replace: memberURL
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(&(objectClass=inetOrgPerson)(!(l=locked)))
dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru
changetype: modify
replace: memberURL
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(&(objectClass=inetOrgPerson)(ou=Developers)(!(l=locked)))
dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru
changetype: modify
replace: memberURL
memberURL: ldap:///ou=People,dc=mycompany,dc=ru?mail?one?(&(objectClass=inetOrgPerson)(ou=Designers)(!(l=locked)))
Как видно, в LDAP URI динамических списков всё осталось по-прежнему, за исключением фильтра отбора. В нём добавилось дополнительное условие: не рассматривать записи, в которых атрибут l
имеет значение locked.
Применим наши изменения:
$ ldapmodify -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./006-modify_mailing_lists.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 -b ou=MailingLists,dc=mycompany,dc=ru -s one mail dn: cn=All,ou=MailingLists,dc=mycompany,dc=ru mail: ivanov@mycompany.ru mail: petrov@mycompany.ru dn: cn=Designers,ou=MailingLists,dc=mycompany,dc=ru mail: petrov@mycompany.ru dn: cn=Developers,ou=MailingLists,dc=mycompany,dc=ru mail: ivanov@mycompany.ru
Как видим, в списках All и Designers не перечислен почтовый адрес antonova@mycompany.ru, чего мы и добивались.
Обратите внимание, что перенастройка динамического содержимого наших списков была произведена без внесения изменений в настройки наложения dynlist
в каталоге cn=config
. Все изменения производятся в рабочем каталоге и могут быть выполнены любым пользователем, у которого есть соответствующие права на внесение изменений в этот каталог.
В ходе этого урока мы познакомились с настройками наложения dynlist
для организации классических динамических списков. Часть настроек (какие записи должны наполняться динамическим содержимым) задаётся в качестве параметров наложения в конфигурационном каталоге cn=config
, часть (критерии отбора и формирования самого динамического содержимого) — непосредственно в рабочем каталоге. Подробно был разобран алгоритм формирования динамического содержимого записей. Итоговые настройки каталога dc=mycompany,dc=ru выглядят так:
$ 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 olcSuffix: dc=mycompany,dc=ru olcAccess: {0}to attrs=userPassword by self write by anonymous auth by * none olcAccess: {1}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 olcDbDirectory: /var/lib/ldap/dc=mycompany,dc=ru dn: olcOverlay={0}dynlist,olcDatabase={1}mdb,cn=config objectClass: olcDynamicList olcOverlay: {0}dynlist olcDlAttrSet: {0}groupOfURLs memberURL