Использование модулей Perl Net::LDAP
Для автоматизации и организации программного доступа к Active Directory (AD) обычно выбирают интерфейс API, называемый Microsoft Active Directory Service Interfaces, ADSI. ADSI достаточно прост в использовании и имеет понятный интерфейс, позволяющий легко управлять объектами в AD. Благодаря комбинации ADSI и ADO для осуществления запросов можно выполнять практически любые действия, автоматизирующие использование AD.
Поскольку ADSI построен на модели COM, разработанной и применяемой только Microsoft, воспользоваться ADSI на отличных от Windows платформах не удастся. Использование ADSI для разработки сценариев или приложений для других операционных систем, а также разработка независимых от платформы сценариев или приложений затруднены. К счастью, существует альтернатива: можно использовать Lightweight Directory Access Protocol (LDAP) и его API.
Записи журналов Microsoft для поддерживаемых стандартов не всегда хороши, но с появлением AD специалисты Microsoft внесли изменения, которые заметно улучшили положение. AD поддерживает не только LDAP, но и другие стандарты, такие как DNS, Simple Network Time Protocol (SNTP), Secure Sockets Layer (SSL), Transport Layer Security (TLS) и Kerberos. Одна из причин взаимодействия AD с LDAP состоит в том, что LDAP делает AD более независимой от платформы с точки зрения клиента. Поскольку LDAP является стандартом, вы не ограничены Windows-клиентами и Windows-платформой. LDAP существует уже достаточно давно и имеет клиентов практически для всех платформ. Это означает, что вы можете писать и использовать сценарии для клиентов, поддерживающих LDAP и осуществляющих запросы и обновления AD с выбранной платформы. Такая возможность будет полезна для администраторов, которым необходимо разрабатывать сценарии и приложения для отличных от Windows платформ, а также для тех, кто пишет кроссплатформенные приложения и сценарии, использующие AD.
Многие думают, что LDAP — это всего-навсего протокол. Но, в отличие от большинства стандартов для протоколов, LDAP имеет определенный стандартом IETF) RFC программный интерфейс API. Он описан в RFC 1823 (http://www.ietf.org/rfc/rfc1823.txt) и обычно именуется C-style LDAP API. API содержит базовый набор функций для осуществления запросов и обновления основанного на LDAP каталога. Специалисты Microsoft создали соответствующий пакет разработчика, SDK, который используется для работы с LDAP API.
C-style LDAP API, являющийся практически стандартным для использования с LDAP, поначалу кажется удобным. Его недостаток заключается в том, что он не является объектно-ориентированным и недостаточно хорошо взаимодействует с некоторыми языками, такими как Java. Поэтому Sun Microsystems разработала собственный LDAP API, известный как Java Naming and Directory Interface, JNDI (см. http://java.sun.com/products/jndi). JNDI обладает расширенными возможностями по сравнению с LDAP API. JNDI является также интерфейсом к DNS, по идеологии похожим на ADSI, и может использоваться в качестве общего интерфейса к службе каталога.
В сообществе разработчиков Perl были созданы наборы Perl-модулей для LDAP, в основе которых лежит Netscape LDAP SDK, более известный как PerLDAP. В имени модулей PerLDAP имеется префикс Mozilla::LDAP. К сожалению, такие модули Perl требуют для использования дополнительной установки Netscape LDAP SDK. Другая группа разработчиков создала основанную на чистом Perl реализацию таких же модулей Perl, известных как perl-ldap. По именам они не пересекаются с модулями Netscape PerLDAP. Модули perl-ldap именуются префиксом Net::LDAP. Большое преимущество модулей Net::LDAP заключается в том, что вы можете установить модули Net::LDAP практически на любую систему, поддерживающую Perl. В результате появляется возможность написания основанных на LDAP клиентов без использования каких-либо внешних SDK или дополнительного программного обеспечения. Модули Net::LDAP используют большинство имен функций C-style API, но в отличие от них являются объектно-ориентированными и более простыми в применении.
В этой статье я объясню, как установить и использовать модули Net::LDAP для осуществления запросов к AD. Поскольку статья адресована пользователям, имеющим опыт работы с Perl, я не буду описывать процедуру установки. Мы обсудим использование модулей Net::LDAP для создания и обновления объектов в AD.
Установка Net::LDAP
У опытных пользователей Perl установка модулей не вызывает затруднений. При использовании для установки Net:: LDAP среды Comprehensive Perl Archive Network (CPAN) можно выполнить следующую команду:
> perl -MCPAN -e shell cpan> install Net::LDAP
Если вы никогда не использовали среду CPAN, рекомендую изучить ее возможности как можно тщательнее, поскольку это значительно упростит процедуру установки и обновления модулей. Модуль CPAN.pm поставляется со многими дистрибутивами Perl. Когда пользователь запускает этот модуль в первый раз, программа установки проводит его через установку среды CPAN. Инструкции о том, как установить модули CPAN на различные платформы, можно найти на сайтах, посвященных CPAN (например, http://www.cpan.org/modules/INSTALL.html).
Ссылки на последнюю версию библиотеки perl-ldap вместе с документацией есть на домашней странице perl-ldap (http://perl-ldap.sourceforge.net). Версия кода perl-ldap, использованного в этой статье, perl-ldap-0.26.
Начало работы с Net::LDAP
Начнем с простого примера использования модулей Net::LDAP. Листинг 1 содержит код, возвращающий основную информацию из каталога. В этой программе я задействовал процедуру new() для создания нового соединения с контроллером домена (DC), имеющим имя dc1.
После этого я вызвал процедуру root_dse для получения нужных атрибутов из Root DSE, который является хранилищем (репозитарием) информации о контроллере домена DC. В этом примере мне нужно получить атрибуты, определяющие контекст именования домена, поэтому я установил значение параметра attrs в defaultNamingContext. Чтобы получить все атрибуты в Root DSE, необходимо установить значение параметра attrs в (*). Затем я использовал процедуру get_value() для получения значений нужных атрибутов. Например, значение, возвращаемое для домена mycorp.com будет dc=mycorp, dc=com.
Заметим, что листинг 1 не содержит какого-либо кода для аутентификации. Root DSE доступен по анонимному доступу, чтобы приложения имели некую стартовую точку, определив ее из основной информации о каталоге. Для выполнения более сложных запросов понадобится включить в приложение аутентификационный код, подобный показанному в листинге 2. В этом коде я вызывал процедуру bind() и задавал требуемое полное имя distinguished name (DN) и пароль для учетной записи, от имени которой хотел регистрироваться. Метод bind() возвращает объект — сообщение Net::LDAP::Message, который я использую для определения того, были ли случаи ошибок во время аутентификации. Если ошибки были, метод code() возвращает код ошибки, а метод error() возвращает текстовое сообщение о ней. Для окончания сессии авторизации я использую процедуру unbind().
Запросы к AD
Для тех, кому хорошо известны параметры, использующиеся для поиска в LDAP, предпочтительным будет поиск при помощи Net::LDAP. Для выполнения поиска в LDAP обычно задаются три параметра; начальное полное имя DN, границы (scope) и фильтр. Параметр base DN задает точку, с которой начинается поиск. Параметр scope (границы) определяет границы (диапазон) поиска. Можно использовать одно из следующих значений:
- base - соответствует только объекту, который задан в начальном DN;
- onelevel или one - соответствует объектам, расположенным на один уровень ниже начального DN (т. е. прямые потомки родителя), исключая начальный DN,
- subtree или sub - соответствует любым объектам, расположенным ниже начального DN, исключая начальный DN.
Параметр filter является префиксом строки, определяющей критерии, которым должны соответствовать объекты. Синтаксис фильтра определен в RFC 2254 (http://www.ietf.org/rfc/rfc2254.txt). В таблице показано несколько простых фильтров поиска.
В листинге 3 показан код программы, выполняющей запросы к AD для поиска всех пользователей, фамилия которых Allen. Заметьте, что переменная $user расположена в программном коде, выделенном в блоке A. Вместо задания полного имени пользователя user DN, я задаю основное имя пользователя user principal name (UPN), которое является идентификатором в стиле электронной почты и которое пользователь вводит при регистрации. Соответствие пользовательских имен UPN адресам электронной почты — достаточно распространенная практика. Если DNS-имя леса не соответствует DNS-суффиксу, используемому в e-mail-адресах, можно создать дополнительный суффикс UPN, выполнив шаги, описанные в статье Microsoft «HOW TO: Add UPN Suffixes to a Forest» (http://support.microsoft.com/?kbid=243629). Если вы используете UPN, вам нет необходимости задавать в программе полное DN-имя пользователя.
Как видно из программного кода в блоке B листинга 3, я использовал для осуществления запросов процедуру search(). Были заданы три параметра (т. е. base DN, scope и filter), которые мы рассмотрели выше. Дополнительно был задан параметр attrs, задающий массив атрибутов, которые необходимо вывести. Если не задать параметр attrs, операция поиска возвратит все атрибуты, имеющие значение. Если в результате поиска возвращается большое число атрибутов, возможно, кто-то захочет уменьшить количество возвращаемых данных, использовав для этого параметр.
Как видно из программного кода в блоке C листинга 3, я использовал процедуру entries() для последовательного отображения возвращаемых процедурой поиска данных. Этот метод возвращает массив объектов Net::LDAP::Entry. Для каждого из таких объектов я использовал процедуру dump() для вывода всех возвращаемых атрибутов. Если необходим доступ к определенному атрибуту, можно использовать процедуру get_value() так, как это было продемонстрировано в листинге 1.
Создание утилиты поиска в LDAP
Теперь, зная основы использования модулей Net::LDAP для выполнения операций поиска, давайте рассмотрим код простой утилиты командной строки ldapsearch.pl, показанной в листинге 4. При помощи этой утилиты можно производить запросы к AD. Ldapsearch.pl основывается на широко используемой утилите поиска в LDAP (ldapsearch), доступной в большинстве пакетов SDK для LDAP и серверов каталога LDAP (за исключением AD).
Основной код ldapsearch.pl по большей части не отличается от программного кода в листинге 3, кроме ключей командной строки, семь из которых являются обязательными. Вы уже знакомы с некоторыми из этих ключей: ключи -b, -s и -f задают, соответственно, параметры для поиска base DN (базовое полное имя), scope (границы, диапазон) и filter (фильтр). Дополнительные ключи -D и -w задают полное имя пользователя и пароль и применяются в целях авторизации. Ключ -h задает имя сервера LDAP. Ключ -a определяет атрибуты, которые нужно запросить. Необходимо задать атрибуты в виде списка, разделенного запятыми. Последний ключ-p является необязательным параметром. Рассмотрим его назначение.
Чтобы выполнить обработку ключей командной строки, я использовал модуль Perl с именем GetOpt::Std. Этот модуль обеспечивает базовую функциональность для обработки ключей. Поскольку большинство ключей обязательные, ldapsearch.pl включает в себя программный код, проверяющий наличие ключей. Этот программный код показан в блоке A листинга 4.
Блок B листинга 3 выделяет программный код, выполняющий установку соединения. Разница между этим кодом и кодом в листинге 2 — в использовании ключа port (-p). В командной строке можно использовать ключ -p для задания альтернативного порта, например 3268 для глобального каталога Global Catalog (GC). Если ключ -p не используется, сценарий по умолчанию задействует порт 389, который является стандартным портом LDAP.
Программный код в блоке C листинга 4 выполняет поиск и вывод соответствующих атрибутов. Этот программный код также похож на представленный в листинге 3, за исключением параметра attr в процедуре поиска, поскольку введен ключ -а, задающий атрибуты для вывода. Я использовал функцию split для включения в массив списка, разделенного запятыми.
Экран. Результаты работы ldapsearch.pl |
На экране 1 показаны результаты работы сценария ldapsearch.pl. Первые три строки содержат параметры, используемые для запуска сценария из командной строки. Как видно по этим параметрам, я запускал сценарий для хоста dc1 (ключ -h). Сценарий выполняет поиск в контейнере cn=computers, dc=mycorp, dc=com (ключ -b) и в подконтейнерах (ключ -s) для всех объектов «компьютер», имеющих имя, начинающееся с app (ключ -f ). Результаты запроса определяют строки, следующие за параметрами. Поскольку я задал возвращение только атрибута cn для каждого из объектов (ключ -a ), результатом будет показ полного имени DN соответствующего объекта и его атрибута cn.
Добавление объектов
Использование Net::LDAP для добавления объектов имеет свои преимущества, например, листинг 5 содержит код, который добавляет объект John Doe. Блок A в листинге 5 выводит параметры, которые необходимо изменить для получения работающей программы. Для переменной $dc требуется задать контроллер домена, на котором будут выполняться операции добавления. Переменным $user и $passwd следует присвоить соответствующее имя и пароль, под которыми нужно подключиться к заданному контроллеру домена. Переменная $parent_dn должна содержать полное имя DN родительского контейнера, в котором предполагается разместить объект John Doe. Программный код после блока A в листинге 1 подключается к заданному контроллеру домена и использует введенные имя и пароль для соединения с ним.
Код блока B листинга 5 вызывает процедуру add(). Первым параметром является полное имя DN нового добавляемого объекта. Второй параметр attrs указывает на массив ссылок, содержащий атрибуты, которые присваиваются новому объекту. Необходимо включить некоторые обязательные параметры, например object Class, которые не имеют значения по умолчанию, или добавить методы обработки на случай ошибки. Например, чтобы добавить объект «пользователь», необходимо, как минимум, задать user для параметра objectClass и имя пользователя для параметра sAMAccountName.
Метод add() возвращает объект Net::LDAP::Message, включающий процедуру code(), которая позволяет определить, были ли ошибки. Когда метод code() объекта Net::LDAP::Message возвращает значение 0, это означает, что контакт успешно добавлен. Если же метод code() возвращает иное значение, следовательно, была обнаружена ошибка, и метод error() отобразит соответствующее сообщение.
Листинг 5 добавляет в AD только один объект. Этот код можно расширить для добавления тысяч объектов. Можно использовать модули Perl Database Interface (DBI) для осуществления запросов к базе данных и пополнения AD восстановленной информацией. DBI-модули есть на сайте CPAN (http://www.perl.com/CPAN-local/modules/by-module/DBI).
Удаление объектов
Удалять объекты, используя Net::LDAP, даже проще, чем добавлять их. Все, что необходимо для проведения операции, это ввести полное имя объекта DN, который требуется удалить при помощи процедуры Net::LDAP delete(). Например, листинг 6 содержит код, удаляющий все объекты контактов в организационном подразделении ou=Co ntacts, dc=mycorp, dc=com.
Программный код блока A показывает параметры, которые необходимо задать. Программный код блока B листинга 6 осуществляет поиск объектов для удаления. Ранее мы рассмотрели, как используется метод search(), включая процесс установки каждого из параметров. В нашем случае мы устанавливаем параметр base для задания полного имени DN-контейнера, содержащего объекты, подлежащие удалению. Нам требуется удалить все объекты в этом контейнере, но не сам контейнер. Для этого присвоим параметру scope значение ?one?, чтобы исключить начальное DN из поиска. Поиск будет вестись на уровне, следующем за начальным DN. Параметр filter содержит фильтр поиска, который задает просмотр объектов контактов. Параметр attrs содержит массив ссылок на отдельный атрибут ?cn?. Метод search() возвращает соответствующие объекты как массив объектов Net::LDAP::Entry.
Программный код блока C в листинге 6 использует процедуру объекта Net::LDAP::Entry dn() для получения DN для каждого из соответствующих объектов, так чтобы метод delete() мог удалить объект. Метод code() проверяет результат на предмет того, была ли обнаружена ошибка, и выводит соответствующее сообщение.
В некоторых случаях вместо удаления каждого из объектов в OU, возможно, понадобится удалить родительский OU и все его дочерние объекты за одну операцию. К сожалению, выполнить операцию удаления такого типа при помощи простой команды delete не удастся. Придется использовать расширение протокола Lightweight Directory Access Protocol (LDAP), называемое control, для информирования сервера об удалении отдельных контейнеров и всех его дочерних объектов. Применение расширения Net::LDAP controls выходит за рамки данной статьи и послужит темой для последующих публикаций. А если кому-то уже сейчас необходимо узнать о функциях controls, можно ознакомиться с документацией на сайте, посвященном perl-ldap (http://perl-ldap.sourceforge.net).
Изменение объектов
Net::LDAP обеспечивает очень хорошую гибкость при необходимости изменения атрибутов объекта. Вы имеете возможность добавлять, удалять и перемещать объекты и их значения на индивидуальной основе для каждого атрибута. Вы также можете выполнить изменения всех свойств объекта за одну операцию.
Подобно методам add() и delete(), метод modify() в качестве первого параметра имеет полное имя DN объекта, подлежащего изменению. Вторым является параметр, задающий тип выполняемой модификации. Метод modify() имеет несколько режимов работы и можно выбрать наиболее подходящий.
Параметр add. Этот параметр добавляет или устанавливает значение атрибута, который до этого не имел присвоенного значения. Можно использовать параметр add для добавления нового значения в атрибут, имеющий несколько параметров. Параметр add использует ссылки на хеш (функции хеша). Ключ хеша содержит имя атрибута, а значение ключа хеша содержит значение атрибута. Например, для того чтобы добавить атрибут mail и присвоить ему значение jdoe@mycorp.com, можно воспользоваться следующим программным кодом:
$ldap->modify($dn, add => { mail => ?jdoe@mycorp.com? } );
Параметр delete. Параметр удаляет определенное значение, связанное с атрибутом. Он также использует ссылку на хеш, содержащий соответствующие пары ключевых значений для удаления, подобно параметру add, либо ссылается на массив атрибутов, для которого нужно удалить все значения. Например, для удаления всех значений, связанных с атрибутами mail и displayname, можно воспользоваться следующим программным кодом:
$ldap->modify($dn, delete => [ ?mail?, ?displayname? ] );
Параметр replace. Параметр replace используется для изменения существующих параметров атрибута заданными значениями. Он задействует те же параметры, что и add. Так, например, если ввести неправильный адрес email при добавлении атрибута mail, можно изменить его на правильный следующим программным кодом:
$ldap->modify($dn, replace => { mail => ?joe@mycorp.com?});
Параметр changes. Параметр используется для группировки набора параметров add, delete и replace в единый вызов. Параметр changes использует ссылки на массив, содержащий пары «имя параметра — значение». Например, для удаления значения, связанного с атрибутом mail, и добавления атрибутов givenname и sn, значения которых соответственно John и Doe, можно воспользоваться кодом:
$ldap->modify($dn, changes => [ add => [ givenname => "John" ], add => [ sn => "Doe"], delete => [ 'mail' ] ] );
Листинг 7 использует параметры, о которых было рассказано, для изменения объекта «пользователь». Подобно методам add() и delete(), метод modify() возвращает объект Net::LDAP::Message, метод code() которого используется для определения факта возникновения ошибки.
Переименование и перемещение объектов
Чтобы иметь полный набор возможностей для управления объектами AD, необходима функция переименования и перемещения объектов. Метод Net::LDAP moddn() позволяет выполнить оба названных действия. Подобно методам, описанным выше, метод moddn() также имеет в качестве первого параметра полное имя объекта (DN), который предполагается переименовать или переместить. Второй параметр может состоять из одного или нескольких.
Параметры newrdn и deleteoldrdn. В AD вы идентифицируете объект по его полному имени DN, которое включает относительное имя relative distinguished name (RDN). RDN определяет имя объекта. Возьмем, например, объект с полным именем DN cn=jdoe, cn=users, dc=mycorp, dc=com. В этом случае относительным именем, RDN, будет jdoe. Параметр newrdn можно использовать для присвоения объекту нового RDN. Значение newrdn должно включать не только имя объекта, (например, jsmith), но и его идентификатор (например, cn=).
Листинг 8 содержит программный код, переименовывающий объект user cn=jdoe, cn=users, dc=mycorp, dc=com в cn=jsmith, cn=users, dc=mycorp, dc=com. Блок A листинга 8 выделяет код, включающий параметр newrdn. Этот код содержит в себе и параметр deleteoldrdn. Установив параметр deleteoldrdn в 1 (т. е. true), вы тем самым удаляете объект, имеющий старое имя RDN. Если же не включать параметр deleteoldrdn или установить его значение в 0 (т. е. false), старый объект, будучи переименованным или перемещенным, останется. Для того чтобы избежать такой ситуации, следует использовать параметр deleteoldrdn со значением 1.
Параметр newsuperior. Этот параметр применяется для перемещения объекта в другой родительский контейнер. Данным параметром задается для полного имени новый родительский контейнер. Например, листинг 9 содержит код, перемещающий все пользовательские объекты, атрибут department которых имеет значение Sales в OU Sales. Как показано в листинге 9, переменная $old_parent определяет родительский контейнер, в котором вы ведете поиск объектов, а переменная $new_parent задает родительский контейнер, в который предстоит перенести объекты. В листинге 9 в блоке B выделен код, ведущий поиск $old_parent для всех объектов пользователей с атрибутом department, равным Sales. Программный код в блоке C листинга 4 использует оператор foreach для перемещения всех подобных объектов в $new_parent.
Итак, в этой статье мы рассмотрели, как использовать модули Net::LDAP для управления объектами в AD. При использовании этих модулей Perl можно выполнять все необходимые манипуляции для управления данными в AD.
Робби Аллен - Технический руководитель в компании Cisco Systems. MVP по Windows Server Directory Services. rallen@rallenhome.com
Таблица. Примеры фильтров поиска
Фильтр поиска | Критерий поиска |
(&(objectclass=user)(objectcategory=Person)) | Все пользовательские объекты в AD |
(&(objectclass=user)(objectcategory=Person)(sn=Allen)) | Все объекты типа пользователь, имеющие фамилию Allen |
(&(objectclass=computer)(objectcategory=Computer)(name=w2k*)) | Все объекты типа "компьютер", начинающиеся с w2k. |
(&(objectclass=contact)(objectcategory=Person)(!(description=*))) | Все объекты контакты, не имеющие заполненного атрибута Description |