Понимание LDAP-протокола, иерархии данных и компонентов записей

Автор оригинала: Justin Ellingwood

Введение


LDAP, или Lightweight Directory Access Protocol, является открытым протоколом, используемым для хранения и получения данных из каталога с иерархической структурой. Обычно используемый для хранения информации об организации, ее активах и пользователях, LDAP является гибким решением для определения любого типа сущностей и их свойств.


Big Tree

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


Что такое «служба каталогов»?


Служба каталогов используется для хранения, организации и представления данных в формате «ключ-значение». Обычно каталоги оптимизированы для поиска, поиска и операций чтения поверх операций записи, поэтому они очень хорошо работают с данными, на которые часто ссылаются, но редко меняются.


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


Что такое LDAP?


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


Поскольку LDAP является открытым протоколом, существует множество различных реализаций. Проект OpenLDAP является одним из наиболее хорошо поддерживаемых вариантов с открытым исходным кодом.


Основные компоненты данных LDAP


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


Атрибуты


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


Установка значения для атрибута производится с помощью имени атрибута и значения атрибута, разделенного двоеточием и пробелом. Пример атрибута под названием mail, который определяет почтовый адрес, будет выглядеть следующим образом:


mail: admin@example.com

При обращении к атрибуту и его данным (когда он не задан), две стороны соединяются знаком равенства:


mail=example.com

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


Записи


Атрибуты сами по себе не очень полезны. Чтобы иметь смысл, они должны быть связаны с чем-то. В LDAP вы используете атрибуты в пределах записи (entry). Запись, по сути, представляет собой набор атрибутов под именем, используемый для описания чего-либо.


Например, вы можете иметь запись для пользователя в вашей системе, или для каждого предмета инвентаризации. Это примерно аналогично строке в системе реляционной базы данных, или одной странице в адресной книге (атрибуты здесь будут представлять различные поля в каждой из этих моделей). В то время как атрибут определяет качество или характеристику чего-либо, запись описывает сам предмет, просто собирая эти атрибуты под именем.


Пример записи, отображаемой в LDIF (LDAP Data Interchange Format), будет выглядеть примерно так:


dn: sn=Ellingwood,ou=people,dc=digitalocean,dc=com
objectclass: person
sn: Ellingwood
cn: Justin Ellingwood

Приведенный выше пример может быть валидной записью в системе LDAP.


DIT


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


Например, если можно иметь записи как для пользователя, так и для объекта инвентаризации, как кто-то сможет отличить их друг от друга? Один из способов отличить записи разных типов — это создание отношений и групп. Это в значительной степени зависит от того, где находится запись при ее создании. Все записи добавляются в систему LDAP в виде веток на деревьях, называемых Data Information Trees, или DIT-ы.


DIT представляет собой организационную структуру, похожую на файловую систему, где каждая запись (кроме записи верхнего уровня) имеет ровно одну родительскую запись и под ней может находиться любое количество дочерних записей. Поскольку записи в LDAP-дереве могут представлять практически все, некоторые записи будут использоваться в основном для организационных целей, подобно каталогам внутри файловой системы.


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


В примере записи в разделе выше мы видим одно указание на DIT, в строке dn:


sn=Ellingwood,ou=people,dc=digitalocean,dc=com

Эта строка называется distinguished name ("dn", "отличительное имя") записи (подробнее об этом позже) и используется для идентификации записи. Она функционирует как полный путь до "корня" DIT. В данном случае у нас есть запись под названием sn=Ellingwood, которую мы создаем. Прямым родителем является запись с именем ou=people, которая, вероятно, используется в качестве контейнера для записей, описывающих людей. Родители этой записи произошли от доменного имени digitalocean.com, которое выступает как корень нашей DIT.


Определение компонентов данных LDAP


В последнем разделе мы обсудили, как представлены данные в системе LDAP. Однако мы также должны поговорить о том, как определяются компоненты, которые хранят данные. Например, мы упомянули, что данные должны соответствовать типу, определённому для каждого атрибута. Откуда берутся эти определения? Давайте начнем с самого низа (с точки зрения сложности), и пройдём весь путь вверх.


Определения атрибутов


Атрибуты определяются с использованием достаточно сложного синтаксиса. Они должны указывать имя атрибута, любые другие имена/названия, которые могут быть использованы для ссылки на атрибут, тип данных, а также множество других метаданных. Эти метаданные могут описывать атрибут, указывая LDAP, как сортировать или сравнивать значение атрибута, и пояснять, как он соотносится с другими атрибутами.


Например, это определение для атрибута name:


attributetype ( 2.5.4.41 NAME 'name' DESC 'RFC4519: common supertype of name attributes'
        EQUALITY caseIgnoreMatch
        SUBSTR caseIgnoreSubstringsMatch
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )

name — это имя атрибута. Номер в первой строке — это глобально уникальный OID (object id, идентификатор объекта), присвоенный атрибуту, чтобы отличить его от всех остальных атрибутов. Остальная часть записи определяет, как можно сравнить запись во время поиска, и имеет указатель, поясняющий, где найти информацию по требуемому типу данных атрибута.


Одной из важных частей определения атрибута является то, может ли атрибут быть задан в записи более одного раза. Например, в определении может быть определено, что фамилия может быть определена в записи только один раз, но атрибут "niece" позволительно указывать несколько раз в одной записи. Атрибуты по умолчанию многозначны и должны содержать флаг SINGLE-VALUE, если их можно задавать в записи только один раз.


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


Определения классов объектов


Атрибуты собираются в сущностях, называемых ObjectClass (классы объектов). ObjectClasses — это просто группировка связанных атрибутов, которая была бы полезна при описании конкретной вещи. Например, "person" — это objectClass.


Записи имеют возможность использовать атрибуты objectClass путем задания специального атрибут с названием objectClass, задающий objectClass, который вы хотите использовать. На самом деле, objectClass — это единственный атрибут, который можно задать в записи, не указывая более objectClass.


Таким образом, если Вы создаете запись для описания человека, то objectClass person (или любой из более специфических объектов objectClasses, производных от person — об этом мы поговорим позже) позволяет использовать все атрибуты внутри этого objectClass:


dn: . . .
objectClass: person

После этого у вас появляется возможность установить внутри записи следующие аттрибуты:


  • cn: Общее имя (Common name)
  • description: Понятное человеку описание записи
  • seeAlso: Ссылка на связанные записи
  • sn: Фамилия (Surename)
  • telephoneNumber: Номер телефона
  • userPassword: Пароль пользователя

Атрибут objectClass может использоваться несколько раз, если вам нужны атрибуты из разных классов объектов, но есть правила, которые диктуют, что разрешено. При этом objectClasses-ы бывают нескольких "типов":


Два основных типа ObjectClasses — это структурный (structural) и дополнительный (auxiliary). Запись должна должна иметь ровно один структурный класс, но может иметь ноль или более вспомогательных классов, используемых для дополнения списка атрибутов, доступных этому классу. Структурный ObjectClasses используется для создания и определения записи, а вспомогательные ObjectClasses-ы добавляют дополнительную функциональность через дополнительные атрибуты.


Определения ObjectClass определяют, являются ли предоставляемые атрибуты обязательными (обозначаются спецификацией MUST) или необязательными (обозначаются спецификацией MAY). Несколько ObjectClasses могут предоставлять одни и те же атрибуты, а категоризация атрибута MAY или MUST может варьироваться от objectClass-а к objectClass-у.


В качестве примера, объект Класс person определяется так:


objectclass ( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top STRUCTURAL
  MUST ( sn $ cn )
  MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )

Это определяется как структурный объектClass, что означает, что он может быть использован для создания записи. Созданная запись должна содержать заданными атрибуты surname и commonname, и может, при желании, содержать аттрибуты userPassword, telephoneNumber, seeAlso, или description.


Схемы


Определения ObjectClass и определения атрибутов, в свою очередь, сгруппированы в конструкции, известной как schema (схема). В отличие от традиционных реляционных баз данных, схемы в LDAP представляют собой просто наборы взаимосвязанных ObjectClasses и атрибутов. Один DIT может иметь много различных схем, так что он может создавать нужные ему записи и атрибуты.


Схемы часто включают дополнительные определения атрибутов и могут требовать атрибутов, определенных в других схемах. Например, объектный класс person, о котором мы говорили выше, требует, чтобы атрибут surname или sn был установлен для любых записей, использующих объектный класс person. Если они не определены в самом LDAP-сервере, схема, содержащая эти определения, может быть использована для добавления этих определений в словарь сервера.


Формат схемы, по сути, является просто комбинацией вышеперечисленных записей, например:


. . .

objectclass ( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top STRUCTURAL
  MUST ( sn $ cn )
  MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )

attributetype ( 2.5.4.4 NAME ( 'sn' 'surname' )
  DESC 'RFC2256: last (family) name(s) for which the entity is known by' SUP name )

attributetype ( 2.5.4.4 NAME ( 'cn' 'commonName' )
  DESC 'RFC4519: common name(s) for which the entity is known by' SUP name )

. . .

Организация данных


Мы рассмотрели общие элементы, которые используются для построения записей в LDAP системе и поговорили о том, как эти "строительные блоки" определяются в системе. Однако мы еще не много говорили о том, как организована и структурирована сама информация в LDAP DIT.


Размещение записей в DIT


DIT — это просто иерархия, описывающая взаимосвязь существующих записей. После создания, каждая новая запись должна "подключаться" к существующему DIT, помещая себя в качестве дочерней по отношению к какой-либо существующей записи. Это создает древовидную структуру, которая используется для определения отношений и присвоения значения.


Верхний DIT — это самая широкая категория, под которой каждый последующий узел является чьим-то потомком. Обычно самая верхняя часть записи просто используется как метка, называющая организацию, для которой DIT используется. Эти записи могут быть иметь любой объектный класс, но обычно они строятся с использованием доменных компонентов (dc=example,dc=com для управляющей информации LDAP, связанной с example.com), местоположений (l=new_york,c=us для организации или сегмента в Нью-Йорке), или подразделений организации (ou=marketing,o=Example_Co).


Записи, используемые для организации (используемые как папки) часто используют объектный класс organizationalUnit, что позволяет использовать простую описательную метку атрибута с названием ou=. Такого рода записи часто используются для общих категорий в записи DIT верхнего уровня (пример часто используемых — ou=people, ou=groups и ou=inventory). LDAP оптимизирован для поиска информации по дереву в направлении "вправо-влево", а не "вверх-вниз", поэтому зачастую лучше поддерживать иерархию DIT не глубокой, с обобщенными организационными ветвями, и дальнейшим указанием на различия через задание определенных атрибутов.


Именование (Naming) и ссылочные записи (Referencing Entries) в DIT


Мы ссылаемся на записи по их атрибутам. Это означает, что каждая запись должна иметь однозначный атрибут или группу атрибутов на своем уровне в иерархии DIT. Этот атрибут или группа атрибутов называется относительное отличительное имя или RDN (от relative distinguished name), и несет ту же функцию, что и имя файла в каталоге.


Чтобы однозначно ссылаться на запись, вы используете её RDN в сочетании со всеми RDN её родительских записей. Эта цепочка RDN ведет назад, вверх по иерархии DIT и указывает однозначный путь к соответствующему элементу. Мы называем эту цепочку RDN различимым именем или DN (от distinguished name). Вы должны указать DN для записи во время создания, чтобы система LDAP знала, где разместить новую запись, и могла убедиться, что RDN записи уже не используется другой записью.


По аналогии, вы можете считать RDN относительным именем файла или директории, как если бы вы работали с ними в файловой системе. DN, с другой стороны, больше похоже на абсолютный путь. Важным отличием является то, что LDAP DN содержит наиболее уточнящие значение слева, в то время как файловые пути содержат наиболее уточняющую информацию справа. DN-ы разделяют значения RDN запятой.


Например, запись для человека по имени Джон Смит может быть помещена под запись "People" в организации example.com. Так как в организации может быть несколько Джонов Смитов, идентификатор пользователя может быть лучшим выбором для RDN записи. Запись может быть указана вот так:


dn: uid=jsmith1,ou=People,dc=example,dc=com
objectClass: inetOrgPerson
cn: John Smith
sn: Smith
uid: jsmith1

Нам пришлось использовать объектный класс inetOrgPerson, чтобы получить доступ к атрибуту uid в данном случае (кроме того, мы ещё имеем доступ ко всем атрибутам, определенным в объектном классе person, причина этого будет понятна в следующем разделе).


Наследование в LDAP


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


Наследование объектных классов


Каждый objectClass — это класс, который описывает характеристики объектов данного типа.


Однако, в отличие от простого наследования, объекты в LDAP могут быть, и часто являются, экземплярами нескольких классов (некоторые языки программирования предоставляют аналогичную функциональность посредством множественного наследования). Это возможно потому, что LDAP под классом понимает просто набор атрибутов, которые он ДОЛЖЕН (MUST) или МОЖЕТ (MAY) иметь. Это позволяет указать для записи несколько классов (хотя только один структурный объектный класс может и должен присутствовать), в результате чего объект просто имеет доступ к объединенной коллекции атрибутов со строжайшими определениями MUST или MAY, имеющими приоритет.


По своему определению, объектный класс может иметь указывать на родительский объектный класс, от которого он наследует свои атрибуты. Это делается с помощью определения SUP, за которым следует название объектного класса, от которого происходит наследование. Например, определение объектного класса organizationalPerson начинается следующим образом:


objectclass ( 2.5.6.7 NAME 'organizationalPerson' SUP person STRUCTURAL
 . . .

Родительским объектом является объектный класс, следующий за идентификатором SUP. Класс-родитель должен быть того же типа, как и определяемый объектный класс (например, STRUCTURAL или AUXILIARY). Дочерний объектный класс автоматически наследует атрибуты и требования атрибутов родителя.


При назначении объектного класса конкретной записи, Вам нужно только указать самого последнего потомка цепочки наследования, чтобы иметь доступ ко всему набору атрибутов. В предыдущем разделе мы использовали это для указания inetOrgPerson в качестве единственного objectClass для нашей записи John Smith, в то же время получив доступ к атрибутам, определенным в объектных классах person и organizationalPerson. Иерархия наследования inetOrgPerson выглядит следующим образом:


inetOrgPerson -> organizationalPerson -> person -> top

Почти все деревья наследования каждого объектного класса заканчиваются специальным объектным классом, называемым "top". Это абстрактный объектный класс, единственное предназначение которого заключается в том, чтобы можно было выполнить требование задавания объектного класса. Он используется для указания вершины цепочки наследования.


Наследование атрибутов


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


Это часто используется для создания более специфических версий общего атрибута. Например, атрибут фамилия (surname) имеет тот же тип, что и имя, и может использовать все те же методы для сравнения и проверки на равенство. Он может унаследовать эти качества, чтобы получить обобщенную форму атрибута "имя" (name). На деле, конкретное определение фамилии может содержать чуть больше, чем указатель на родительский атрибут.


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


Вариации протокола LDAP


В начале мы упоминали, что LDAP на самом деле является лишь протоколом, определяющим интерфейс связи для работы со службами каталогов. Обычно он известен как LDAP, или протокол ldap.


Стоит упомянуть, что вы можете увидеть некоторые варианты в обычном формате:


  • ldap://: Это основной протокол LDAP, позволяющий получить структурированный доступ к службе каталогов.
  • ldaps://: Этот вариант используется для доступа к LDAP поверх SSL/TLS. Обычно трафик LDAP не шифруется, но большинство реализаций LDAP поддерживают подобный вариант доступа. Такой способ шифрования LDAP-соединений на самом деле устарел, и вместо него рекомендуется использовать шифрование STARTTLS. Если вы работаете с LDAP через незащищенную сеть, настоятельно рекомендуется шифрование.
  • ldapi://: Это используется для указания LDAP через IPC. Это часто используется для безопасного соединения с локальной LDAP-системой в административных целях. Он связывается через внутренние сокеты вместо того, чтобы использовать открытый сетевой порт.

Все три формата используют протокол LDAP, но последние два указывают на дополнительную информацию о том, как он используется.


Заключение


Вы должны достаточно хорошо понимать протокол LDAP и то, как реализации LDAP представляют данные для пользователей. Понимание того, как элементы системы связаны друг с другом, и где они получают свои свойства, делает управление и использование LDAP-систем более простым и предсказуемым.

Комментарии 8

    0
    Такой способ шифрования LDAP-соединений на самом деле устарел, и вместо него рекомендуется использовать шифрование STARTTLS.

    STARTTLS — это расширение основного протокола
    SSL/TLS — это самостоятельный уровень, его можно терминировать не вникая во внутренности протокола

    SSL/TLS можно терминировать на балансировщике и дальше уже распределять трафик на внутренние сервисы, сверяясь с их персональными сертификатами. Так что в современных реалиях скорее устарела экономия на портах, которую решали с помощью STARTTLS.
      0

      А какие у основных реализаций характеристики по read/write latency, throughtput? Например, у меня есть более иерархические данные, которые выглядят как вполне подходящие под LDAP. И я выбираю, класть их в обычную реляционную БД, или в LDAP. Если для РСУБД есть много бенчмарков и понимания как оно "взлетит", то с LDAP это неочевидно. Например, как атрибуты и объекты хранятся на диске, чтобы можно было оценить, насколько быстрыми будут операции с ними?

        0
        Если брать openldap, то используется формат bdb или же lmdb в новых версиях. Ванильный openldap для хайлоад плохо подходит, из форков я знаю только ReOpenLDAP, но он не получил широкой известности.
          –1

          При этом данный форк проверен телекомом (одним из большой тройки), и покрыт тестами, в т.ч. производительности.

            0
            Правильнее было бы сказать — этот форк и пилился Мегафоном под свои нужды, поскольку при их числе запросов в секунду и при их необходимости в master-master репликации они ловили массу граблей в OpenLDAP. Поправили, довели до ума, много что вылизали, и — последние года 2 проект не развивается (вот репа).

            Проект был инициирован в 2014 году для решения проблем, возникших при эксплуатации исходного OpenLDAP в инфраструктуре одного из крупнейших Российских операторов мобильной связи.

            За два года проект выведен «на орбиту» высоконагруженной промышленной эксплуатации. Сейчас ReOpenLDAP успешно работает во всех филиалах ПАО МегаФон по всей России и одновременно доступен как OpenSource.

            (вот)
              –1
              Оригинальная репа здесь. Только мне не очень понятно, что означает в комментарии выше «развитие»: если сервер в полной мере и без ошибок реализует спецификацию, то куда и зачем его «развивать»?
                0
                Оригинальная репа была там, но с некоторых пор переехала на указанный выше сервер. Причина на «бывшей оригинальной» репе указана в конце страницы, посмотрите.

                Что касается «работает — не трогай», то, вероятно, вы правы в разрезе фич. Но даже в самом идеальной коде нет-нет, да найдутся неидальные решения, отчего код все равно правят, и выпускают обновления. Cамые главные из них, даже при «работает — не трогай» подходе — патчи, закрывающие возможные бреши в безопасности. Для Мегафона это, понимаю, не критично, их LDAP явно работает в непубличном сегменте их же собственной сети, так что могли и закрыть вопрос с полировкой и допиливанием, но актуален ли такой подход ли это для других компаний и вариантов внедрения — нужно смотреть в каждом конкретном случае.
                  –1
                  О причинах я в курсе (у меня же форк есть, посмотрите).

                  Об единственном оставшемся из более чем пяти тысячах баге Юрий где-то писал — точно не помню, где, но речь шла о сбое репликации при каком-то дико редком сочетании условий, и то — единственный результат той ошибки был сбой конкретной операции репликации, при этом она штатно перезапускалась без сучка и задоринки, а база оставалась в порядке. Собственно, потребные усилия на ликвидацию этого бага реально превышают отдачу — именно поэтому он был оставлен «как есть».

                  Лично у меня опыт продакшна этого форка с репликацией примерно полтора года. Так вот, по стабильности могу сравнить, пожалуй, только с Nginx.

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

      Самое читаемое