Как использовать OpenLDAP в ОС Debian GNU/Linux




Сайт создан в системе uCoz

Вернуться к Оглавлению

1.Вместо введения

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

1.2. Что такое LDAP и какие ошибки следует избегать при его использовании

Служба Директорией является специализированной базой данных, предназначенной для поиска и отображения, в дополнение к уже имеющимся базовым функциям просмотра и обновления. Самой яркой одной из таких базовых функций является Служба Переключения Имен(Name Named Switch — NSS), которая используется для просмотра и модификации учётных записей пользователей NSS баз данных: group, passwd и их теневых паролей shadow. Поэтому используя протокол LDAP можно осуществлять расширение этой службы за счёт централизованной аутентификации пользователей в локальной вычислительной сети(ЛВС) или в объеме корпоративной вычислительной сети(КВС), являющейся порой совокупностью нескольких ЛВС.

Сегодня, вокруг использования этого протокола началось полное сумасшествие. Начинают хранить все что можно вплоть до службы DHСP, осуществляющей ретрансляцию MAC-адреса и IP-адреса, службы имен DNS и т.д., что не столь востребовано клиентом локальной сети, потому что служба LDAP обращается к ним локально, только на сервере. Конечно же, если не идет речь о большой КВС, где такой подход может быть востребован из-за большего количества серверов. А для небольшой организации имеющий несколько десятков ПЭВМ(и менее), образующих ЛВС, где порой в качестве сервера такой сети может выступать одна из них, это будет весьма громоздкое решение.

Аббревиатура LDAP происходит от Lightweight Directory Access Protocol, который переводится с английского как Легковесный протокол доступа к директории. Как следует из имени, он является облегчённым протоколом доступа к директориям сервисов на основе директорий протокола X.500. LDAP выполняется через TCP/IP протокол или другие соединения ориентированные на передачу данных. Этот протокол описывается в RFC 4510 в "Lightweight Directory Access Protocol (LDAP) Technical Specification Road Map". Протокол в ОС Debian GNU/Linux реализуется сервером slapd, являющийся автономно работающим LDAP демоном, который производит доступ к каталогу через любой порт стека TCP/IP протоколов, по-умолчанию номер которого установлен равным 389. Количество баз данных(каталогов) не лимитируется. Для того, чтобы сервер slapd знал об этих каталогах необходимо их указать в файле /etc/ldap/slapd.conf.

При использовании openldap версии 2.3 и выше помните, что в нем теперь существует возможность хранить конфигурацию сервера в формате ldif-записи. Поэтому не удивляйтесь, если в директории /etc/ldap/ не найдете slapd.conf, потому что по умолчанию установлен метод динамической конфигурации данных(dynamic config backend). Если Вы ещё не успели освоить его, можно использовать старый способ конфигурации через файл slapd.conf.

Библиотека LDAP является клиентом сервера slapd и используется приложениями для выполнения запросов связанных с созданием и изменением данных в LDAP каталогах через одноимённый протокол.

2. Установка требуемого программного обеспечения

В ОС Debian GNU/Linux для нормальной работы сервиса LDAP нужно установить следующие пакеты, реализующие поддержку OpenLDAP:

  • пакет slapd, содержит службу OpenLDAP, обеспечивающий поддержку службы директорий автономно;

  • пакет ldap-utils, содержит утилиты из проекта OpenLDAP (облегчённый протокол доступа к каталогам). С их помощью можно обращаться к локальному и удалённому серверу LDAP; сюда входят все клиентские программы, необходимые для доступа к серверам LDAP;

  • пакет libldap, содержит динамические библиотеки для серверов и клиентов OpenLDAP;

  • пакет libldap-dev, содержит средства разработчика пользовательских приложений для обеспечения доступа клиентов OpenLDAP к серверу. Этот пакет содержит заголовочные файлы, библиотеки и линки для осуществления статической и динамической линковки.

Для установки этих пакетов в ОС Debian GNU/Linux, версии 6.0.5 и выше с кодовым обозначением "Squeeze", необходимо выполнить,

root@engine:~# apt-get install slapd ldap-utils libldap-2.4 libldap2-dev

Рекомендую пропустить настройку slapd во время установки, потому что настройку этого пакета мы проведем позднее. На все задаваемые вопросы отвечайте просто нажатием клавиши enter.

3. Обработка ошибок в OpenLdap

Помните статью Как использовать библиотеку XML в ОС Debian GNU/Linux, в ней мы создавали файл ldapsi-msg.xml, а потом в нем же искали код ошибки LDAP, чтобы вывести в диалоге ldap-error. Это код ошибки автор будет использовать в приложении ldap-users-admin, чтобы получать расширенную информацию о случившихся ошибках во время использования API-функций библиотеки libldap и/или протокола LDAP.

Перечисленные коды ошибок можно условно разделить на две группы: результат выполнение API-функций и самого протокола LDAP. У автора нет желания переписывать LD_ERRNO(3) и он надеется, что читатель сам сможет разобраться с помощью этой справочной страницы какие коды к какой из двух групп относятся и что они обозначают. Единственно, что хочу сказать, что ситуации во время работы приложения бывают разные и поэтому необходимо учитывать такие вещи как коды ошибок, позволяющие уберечь свою программу от неправильной интерпретации событий.

Коды ошибок берутся из переменной errno библиотеки С(GLIBC), которая объявлена в <errno.h> и устанавливается немедленно во время использования семейств функций ldap_init(3), ldap_bind(3) и т.д. Разве, что кроме асинхронных функций, таких как ldap_search_ext(3) и ldap_modify_ext(3), которые используют ldap_result(), для получения кода ошибки LDAP. Дело в том, что члены маркера клиентского соединения с LDAP сервером закрыты для публичного доступа. Он декларируется структурой struct ldap как пользовательский тип LDAP, содержащей поле ld_errno, значение которого потом с помощью макроса LDAP_SET_ERRNO присваивается переменной errno библиотеки С. Поэтому всегда в коде программы нужно включать через дерективу#include заголовочный файл <errno.h> . Единственное, что могу посоветовать, когда будете использовать переменную errno, то это использовать LDAP_API_RESULT(n), который определяет относится данная ошибка к API или протоколу, а так же, если выполнение API-функций завершилось успешно.

Обычно в коде используют функцию ldap_perror(), особо не заморачиваясь на счёт написания каких-то там XML-файлов, содержащих коды ошибок LDAP, когда эта функция сама все вернет, остаётся только ей передать маркер LDAP соединения и дело в шляпе. Автор использовал файл, как пример ldapsi-msg.xml для наглядности работы библиотеки XML и будет он использовать эту часть в проекте ldap-users-admin, не смотря на громоздкость решения. Вам же рекомендую обходиться этой функцией для экономии времени и сил.

4. Настраиваем сервер slapd

Когда вы устанавливали пакет slapd, автор этой статьи вас просил пропустить настройку slapd, которую мы с вами произведем сейчас. Для этого, давайте вызовем dpkg-reconfigure, который перенастраивает уже установленные пакеты. В качестве единственного параметра мы укажем пакет slapd, вследствии чего, будут заданы вопросы по настройке пакета. Их, как вы увидете, будет больше чем во время инсталляции этого пакета apt-get, поэтому автор и просил вас оставить его настройку на потом.

root@engine:~# dpkg-reconfigure slapd

После чего будет осуществлена настройка slapd и нам останеться лишь ответить на следующие вопросы:

DNS Domain nameengine.local
Name of Organizationengine
Admin passwordВвести пароль
Database backend to use:HDB
Do you want your database to be removed when slapd is purged?Yes
Move old DatabaseNo
Allow LDAPv2 protocolNo

Учтите, что в таблице для DNS Domain name и Name of Organization указаны настройки для моей ПЭВМ, у вас же должно быть что-то другое, но если вам лень что-то придумывать самим можете использовать их. Как показала практика расхождение имени реального домена DNS и указанного в DNS Domain name роли не играет, впрочем как и Name of Organization. Но, лучше избегать таких расхождений, чтобы избегать неприятностей в будущем, в общем вам решать самим.

Теперь переходим к настройке нашего с вами каталога "dc=engine,dc=local", который соответствует тому, что вы вводили в DNS Domain name. Эти настройки храняться в файле /etc/ldap/slapd.conf и относятся к базе данных #1. Поэтому открывайте этот файл в текстовом редакторе, которому вы отдаете свои предпочтения, и спускайтесь на первую строку парметров базы данных #1. Ниже приводится дамп фрагмента файле /etc/ldap/slapd.conf, а жирным цветом на что вам нужно обратить внимание.

# Specific Directives for database #1, of type bdb:
# Database specific directives apply to this databasse until another
# 'database' directive occurs
database hdb

# The base of your directory in database #1
suffix "dc=engine,dc=local"

# rootdn directive for specifying a superuser on the database. This is needed
# for syncrepl.
# rootdn "cn=admin,dc=engine,dc=local"

# Where the database file are physically stored for database #1
directory "/srv/ldap"

# The dbconfig settings are used to generate a DB_CONFIG file the first
# time slapd starts. They do NOT override existing an existing DB_CONFIG
# file. You should therefore change these settings in DB_CONFIG directly
# or remove DB_CONFIG and restart slapd for changes to take effect.

# For the Debian package we use 2MB as default but be sure to update this
# value if you have plenty of RAM
dbconfig set_cachesize 0 2097152 0

# Sven Hartge reported that he had to set this value incredibly high
# to get slapd running at all. See http://bugs.debian.org/303057 for more
# information.

# Number of objects that can be locked at the same time.
dbconfig set_lk_max_objects 1500
# Number of locks (both requested and granted)
dbconfig set_lk_max_locks 1500
# Number of lockers
dbconfig set_lk_max_lockers 1500

# Indexing options for database #1
index objectClass eq

# Save the time that the entry gets modified, for database #1
lastmod on

# Checkpoint the BerkeleyDB database periodically in case of system
# failure and to speed slapd shutdown.
checkpoint 512 30

# Where to store the replica logs for database #1
# replogfile /var/lib/ldap/replog

# The userPassword by default can be changed
# by the entry owning it if they are authenticated.
# Others should not be able to see it, except the
# admin entry below
# These access lines apply to database #1 only
access to attrs=userPassword,shadowLastChange
by dn="cn=admin,dc=engine,dc=local" write
by anonymous auth
by self write
by * none


# Ensure read access to the base for things like
# supportedSASLMechanisms. Without this you may
# have problems with SASL not knowing what
# mechanisms are available and the like.
# Note that this is covered by the 'access to *'
# ACL below too but if you change that as people
# are wont to do you'll still need this if you
# want SASL (and possible other things) to work
# happily.
access to dn.base="" by * read

# The admin dn has full write access, everyone else
# can read everything.
access to *
      by dn="cn=admin,dc=engine,dc=local" write
      by * read


# For Netscape Roaming support, each user gets a roaming
# profile for which they have write access to
#access to dn=".*,ou=Roaming,o=morsnet"
# by dn="cn=admin,dc=engine,dc=local" write
# by dnattr=owner write

Как видно из дампа файла /etc/ldap/slapd.conf, который можно было бы и не открывать, все устанавливается автоматически, но чтобы не было в будущем проблем, когда мы будем устанавливать libpam-ldap и libnss-ldap вы должны обратить внимание на следующие дерективы.

  • Деректива suffix — все запросы будут направляться к этой базе данных. Узел DN suffix является по-сути дело корневым от него берут начало все остальные узлы в этой базе данных. Её значение должно соответствовать тому, что вводили в ответ на вопрос:"DNS Domain name"
  • Деректива directory — устанавливает базовую директорию файловой системы, где будет хранится база данных. По умолчанию, эта директория располагается /var/lib/ldap, но автор предпочитает хранить её в директории /srv/ldap, которая находится на отдельном локальном диске, смонтированнго в точке монтирования /srv.
  • Деректива rootdn — должна быть всегда закомментирована.
  • Деректива access — устанавливает правила доступа для администратора сервера чтение/запись, а для других только режим чтения. Администратором является пользователь, указанный в rootdn, что в нашем случае это будет admin.engine.local.

Не забудте после изменения переменной directory копировать содержимое из /var/lib/ldap в /srv/ldap .

5.Инициализация соединения с сервером LDAP

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

Эта функция создает маркер LDAP, но не откроет само соединение. Это событие случится позднее, когда будет сделана первая операция. Ниже представлен протип функции

#include <ldap.h>

LDAP *ldap_init ( char *host, int port );

Функции в первом аргументе host пердается имя узла, на котором запущен сервер. В втором аргументе port — номер порта. По умолчанию, мы всегда должны назначать номер порта равным 389, который выделила "Администрация адресного пространства Интернет"(IANA).

Функция ldap_init() возвращает указатель на маркер LDAP или вместо него нулевой указатель. В этом случае рекомендую завершить процедуру инициализации соединения с сервером LDAP.

Откладывая соединение с сервером LDAP на потом, мы с вами пользуемся этим моментом и устанавливаем версию протокола LDAP через ldap_set_option(). Помните, когда нас при повторной настройке сервера спрашивали "Allow LDAPv2 protocol?", мы c вами отвечали "No".Так вот, поэтому мы должны передать опцию LDAP_VERSION3. Если же вы ответили "Yes" то, тогда вы должны передать опцию LDAP_VERSION2, которая указывает использовать вторую версию протокола LDAP. Взглянем на прототип функции.

#include <ldap.h>

int ldap_set_option ( LDAP *ld, int option, const void *invalue );

А первом аргументе ld мы передаем маркер LDAP, который ранее создали функцией ldap_init(). В втором аргументе option мы передаем код опции, а в третьем — её значение. Третий аргумент является указателем разыменованного типа, который использует тип void. Такой тип указателя используется потому, что значение передаваемые опции может иметь различную размерность. Поэтому, здесь используется такой трюк с обобщенным указателем, которым является invalue . Сам часто использую такие указатели, когда имеется неоднозначность в размерности передавемых по нему переменных. В нашем случаем, мы будем передавать в втором аргументе option код опции LDAP_OPT_PROTOCOL_VERSION, а для передачи значения, типа int, равное LDAP_VERSION3 или LDAP_VERSION2 в зависимости от того какую версию протокола LDAP мы выбрали.

Функция ldap_set_option() возвращает значение LDAP_OPT_SUCCESS в случае успешного завершения. Иначе, она вернет LDAP_OPT_ERROR, которая означает, что произошла ошибка.

 1 #include <ldap.h>
 2
 3 LDAP*
 4 ldap_connect_server( const char* hostname, int port, int proto_ver )
 5 {
 6  LDAP *ld;
 7  if( proto_ver != LDAP_VERSION2 && proto_ver != LDAP_VERSION3 ) {
 8      fprintf( stderr, "ldap_connect_server: was passed a invalid proto version!" );
 9      return ((LDAP*)NULL);
10  }
11  
12  if( ( ld == ldap_init ( hostname,port ) ) == NULL ){
13      perror( "ldap_init failed" );
14      return ((LDAP*)NULL);
15   }
16 
17   if( ( ldap_set_option ( ld, LDAP_OPT_PROTOCOL_VERSION, proto_ver ) != LDAP_VERSION3 ) {
18      perror( "ldap_set_option" );
19      return ((LDAP*)NULL);
20   }
21 
22   return ld;
23 }
24 
25 /*eof*/

Все, что мы сделали с помощью ldap_init(), так это просто зафрахтовали соединение с сервером LDAP и выделили память под маркер LDAP. Соединение будет произведено тогда, когда мы введем rootdn от лица которого оно будет создано. Потому что в текущей конфигурации LDAP сервера мы имеем одну такую персону, указанную в rootdn как admin.engine.local . Это производится на стадии связывания через ldap_bind() или ldap_bind_s(). Так же можно использовать ldap_simple_bind() или ldap_simple_bind_s(), если вы не планируете использовать соединения с использованием SASL (Simple Authentication and Security Layer) без криптографической поддержки.

SASL — простая аутентификация и слой безопасности, являщиесятся методом для добавления поддержки аутентификации в протоколы соeдинения, в нашем случае, таким протоколом является LDAP. Тут SASL вставляется между протоколом и соединением, что позволяет обеспечить безопасную передачу данных, к числу которых относится пароль администратора LDAP, хранящийся в rootdn. Если убрать этот пароль, то любой желающий сможет изменять учетные записи пользователей и групп, которыми управляет программа ldap-users-admin. В версии этой программы будет использован простая аутентификация(Simple authentication) SASL, которая не защищает передаваемый пароль, поэтому он имеют риск быть разоблаченным. Следовательно использоваться API-функции ldap_sasl_bind() и ldap_sasl_bind_s(), использующие криптографическую пакет Cyrus-SASL, не будут использованы в настоящей версии программы ldap-users-admin.

#include <ldap.h>

int ldap_bind ( LDAP *ld, const char *who, const char *cred, int method );
int ldap_bind_s ( LDAP *ld, const char *who, const char *cred, int method);
int ldap_simple_bind(LDAP *ld, const char *who, const char *passwd);
int ldap_simple_bind_s(LDAP *ld, const char *who, const char *passwd);

Первым аргументом во всех четырех функциях является маркер LDAP, который передается по указателю ld. Вторым аргументом является указатель who, по-которому будем передовать rootdn в принятой нотации, как cn=admin,dc=engine,dc=local. В третьем аргументе — cred или passwd передаем пароль, который присваивали учетной записи администратору во время настройки сервера LDAP. В четвертом параметре в ldap_bind()/ldap_bind_s() передаем код метода аутентификации механизма SASL.

Задачей этих функций является связывать пароли переданного в password или cred c паролем, который находится в userPassword узла DN, которым в нашем случае является rootdn.

Функции оканчивающиеся на _s в случае ошибки возвращают код ошибки errno, описанный в ldap_error(3), в том числе и LDAP_SUCCESS, который соответствует успешному завершению API-функции.

Теперь мы можем внести в наш предыдущий код правки, чтобы создать соединение с сервером LDAP.

 1 #include <ldap.h>
 2
 3 LDAP*
 4 ldap_connect_server( hostname, port, proto_ver, who, password )
 5 const char* hostname;
 6 int port;
 7 int proto_ver;
 8 const char* who;
 9 const char* password;
10 {
11
12  LDAP *ld;
13  gint rc;
14
15  if( proto_ver != LDAP_VERSION2 && proto_ver != LDAP_VERSION3 ) {
16      fprintf( stderr, "ldap_connect_server: was passed a invalid proto version!" );
17      return ((LDAP*)NULL);
18  }
19  
20  if( ( ld == ldap_init ( hostname,port ) ) == NULL ){
21      perror( "ldap_init failed" );
22      return ((LDAP*)NULL);
23   }
24 
25   if( ( ldap_set_option ( ld, LDAP_OPT_PROTOCOL_VERSION, proto_ver ) != LDAP_OPT_SUCCESS ) {
26      perror( "ldap_set_option" );
27      return ((LDAP*)NULL);
28   }
29 
30  if ( (rc=ldap_simple_bind_s( ld, rootdn, password )) != LDAP_SUCCESS ) {
31      ldap_perror( ld, "ldap_bind" );
32      return ((LDAP*)NULL);
33  }
34 
35   return ld;
36 }
37 
38 void
39 ldap_disconnect_server( LDAP *ld )
40 {
41   
42   if( ldap_unbind_s(ld) != LDAP_SUCCESS ) {
43       ldap_perror( ld, "ldap_unbind" );
44   }
45  
46 }
47
48 /*eof*/

Как вы можете увидеть, в коде произошли значительные изменения, которые связанные с добавлением ldap_simple_bind_s(), ldap_unbind_s() и обертки-функции ldap_disconnect_server(). Эту функцию необходимо будет вызывать всякий раз, когда нужно завершить соединение с сервером LDAP. Если вы этого не сделаете, то не будет освобождена память, выделяемая под маркер LDAP, который описывает соединения с LDAP-сервером. Это всегда нужно делать, т.к. если сервер при завершении процесса разорвет соединение по сигналу BROKEN_PIPE, а выделенную память, кроме как ldap_unbind() или ldap_unbind_s(), никто не освободит, вы просто рискуете столкнуться с банальной утечкой памяти.

Функции ldap_disconnect_server() не производит обработку ошибок. Если функция ldap_unbind_s() не смогла завершить соединение с LDAP-сервером, что делать в этом случае, оставляю на ваш умотрение. Поэтому эта функция ничего не возвращает, а только выводит ошибку на stderr через ldap_perror().

Как вы увидели, у нас изменилось число аргументов функции ldap_connect_server() и её прототип в целом. К ранее уже используемым аргументам hostname, port и proto_ver добавились who и password. В предыдущей записи аргументов в определении функции использовалась "новая запись". В ней аргументы функции перечисляются вместе с типом в одной последовательности через запятую ','. Такой способ записи принято называть "старая запись". В ней в скобках после имени функции следуют только имена параметров, а после скобок объявление типов аргументов функции.

В аргументе who мы передаем узел DN, по которому будет производится связывание с LDAP-сервером, а в аргументе password — пароль, который находится в userPassword узла DN. Если у вас возникают проблемы со связыванием и ldap_bind(), к примеру, возвращает Invalid credentials (49), проверте пару узел DN и его пароль с помощью утилиты ldapsearch из пакета ldap-utils. Где в дополнительном аргументе опции "-b" должен быть указан узел DN, согласно принятой нотации записи DN в протоколе LDAP.

my_user@engine:~$ ldapsearch -x -W -Hldap://127.0.0.1 -b "cn=admin,dc=engine,dc=local"
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <cn=admin,dc=engine,dc=local> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# admin, engine.local
dn: cn=admin,dc=engine,dc=local
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

Что будет соответствовать использованию функции ldap_connect_server() следующим образом

 1 #include <ldap.h>
 2
 3 int main ( int argc, char** argv )
 4 {
 5   LDAP* ld;
 6
 7     · · ·
 8
 9  ld=ldap_connect_server ( "localhost", LDAP_PORT, NULL,NULL );
10  if( ld == NULL ) {
11    return EXIT_FAILURE;
12  }
13     · · ·
14  
15  return EXIT_FAILURE;
16 }
17
18 /*eof*/

Соответственно, "localhost" является именем узла на котором запущен LDAP сервер, а LDAP_PORT — определённой в <ldap.h> по умолчанию номер порта, который равен 389. Два последние указателя являются нулевыми.

Таким способом через ldapsearch можно проверить не только возможность связывания с узлом DN без пароля, но и по паролю, как показано ниже.

root@engine:/etc/ldap# ldapsearch -x -W -D "cn=admin,dc=engine,dc=local" -b "cn=admin,dc=engine,dc=local"
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# admin, engine.local
dn: cn=admin,dc=engine,dc=local
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
userPassword:: e2NyeXB0fVBmVnBLTGVQcm9JL3c=

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

Что будет соответствовать использованию функции ldap_connect_server() следующим образом

 1 #include <ldap.h>
 2
 3 int main ( int argc, char** argv )
 4 {
 5   LDAP* ld;
 6
 7     · · ·
 8
 9  ld=ldap_connect_server ( "localhost", LDAP_PORT,
 9               "cn=admin,dc=engine,dc=local","key word" );
10  if( ld == NULL ) {
11    return EXIT_FAILURE;
12  }
13     · · ·
14  
15  return EXIT_FAILURE;
16 }
17
18 /*eof*/

Дополнительно к имени узла и номеру порта, на котором весит сервер LDAP, добавился узел DN и пароль, по которому произойдет связывание с LDAP-сервером.

6. Использование LDAP вместе с службой переключения имен

В своих двух статьях про учетные записи пользователей, группы и теневые пароли рассказывал, как используется Служба переключения имён, Name Switch Service(NSS), и как производятся операции с этими учётными записями, которые используются во время авторизации пользователя, разграничения прав доступа или запуска программы(процесса) от его лица. Мы помним, что все эти записи доступны только локально, что исключает возможности организации групп пользователей в ЛВС, что не совсем удобно, если планируется использовать сетевую распределенную программу в локальной сети, где потребуется удаленный доступ к NSS, такая как программа Автоматизированного учёта приёма пациентов в Медицинском центре

Тем самым средством удаленного доступа является NSS-модуль для использования LDAP как сервера имен, который предоставляет пакет libnss-ldap. Он позволяет соединить несоединимое, а именно LDAP-сервер и Службу переключению имен, известную как NSS(Name Service Switch). С помощью этого модуля можно подсоединить учётные записи пользователей и групп хранящиеся в базе данных LDAP-сервера, как будто они храняться в локакальных базах данных group, passwd и shadow службы NSS. Поэтому не раздумывая устанавливаем пакет libnss-ldap.

root@engine:~# apt-get install libnss-ldap

По-мимо пакета libnss-ldap, будут установлены пакеты libpam-ldap и nscd, речь о которых пойдет ниже. Кроме того, во время установки этих пакетов будут заданы вопросы по их конфигурации, которые рекомендую пока пропустить, потому что эти пакеты все равно прийдется настраивать позднее с помощью dpkg-reconfigure.

При конфигурировании пакета libnss-ldap нужно запретить специальные привелегии LDAP для root. Если они были заданы, то нужно произвести конфигурацию этих пакетов снова.

root@engine:~# dpkg-reconfigure libnss-ldap

Во время настройки пакета libnss-ldap нужно ответить на следующие вопросы.

Универсальный Идентификатор Ресурса сервера LDAPldap://engine
Индивидуальное имя базы поискаdc=engine,dc=local
Использовать версию3
База данных LDAP требует учетное имя? Нет
Специальные привилегии LDAP для root?Нет
Разрешить чтение/запись в файл настроек только его владельцу?Да

Теперь нужно изменить порядок просмотра баp данных в службой имен. Для чего необходимо отредактировать файл /etc/nsswitch.conf .

passwd:ldap compat
group:ldap compat
shadow:ldap compat

На этом с настройкой пакета libnss-ldap все, а теперь мы можем перейти к созданию учетных записей пользователей и групп.

Как писал об этом уже ранее, пользователи и их теневых паролей храняться в базах данных passwd, а на нашем сервере они будут хранится в ou=People,dc=engine,dc=local, а группы пользователей в ou=Group,dc=engine,dc=local . Теперь отвлечемся на теорию о представлении данных в LDAP раз мы их уже коснулись.

LDAP-сервер содержит данные составляющие дерево каталагов(или директорий) информации, directory information tree (DIT). Такая модель представления информации базируется на записях, которые содержат наборы атрибутов, различаемые по Отличительному имени, Distinguished Name(DN). Такие DN используются для обеспечения однозначности при обращении к записи. Каждый из атрибутов записи имеет тип и один или несколько значений. Типы представляются обычно мнемоническими строками. У нас в нашей модели DIT будет использоваться два типа записей ou и cn. В libnss-ldap дерево разбивается на две ветви DN ou=Group и DN ou=People, которые свою очередь имеют дочерние DN c тип cn и содержащие атрибутами учетных записей групп пользователей, самих пользователей и теневых паролей. Давайте взглянем на само дерево.

Как видно из дерева, которое использует libnss-ldap для хранения учётных записей пользователей, их групп и их теневых паролей, оно разветвляется на узле dc=engine на два подграфа:

  • Подграф пользователей и теневых паролей, вершиной которого является узел ou=People
  • Подграф группы пользователей, вершиной которого является узел ou=People.
Все эти подграфы заканчиваются узлами DN c типом CN, которые содержит атрибуты, соответствующие, для пользователей — NSS базам данных passwd и shadow, а группам — NSS базе данных group.

Атрибуты узлов DN, тип CN, для пользователей и их теневых паролей являются:

  • cn — расширенная информация о пользователе, соответствует полю gecos в базе данных passwd; он же задает имя узла DN
  • uid — пользователя, соответствует полю login в базе данных passwd;
  • uidNumber — уникальный идентификатор пользователя, соответствует полю uid в базе данных passwd;
  • gidNumber — уникальный идентификатор группы куда входит пользователь, соответствует полю gid в базе данных passwd;
  • homeDirctory — домашняя директория задается абсолютным путем, соответствует полю home в базе данных passwd;
  • loginShell — командная оболочка задается абсолютным путем, соответствует полю shell в базе данных passwd;

Для узлов DN, тип CN, групп пользователей, указан только два атрибута gidNumber уникальный номер группы и cn — символьного имени группы. Он же задает имя узла DN. Автору этой статьи известно, как минимум ещё об одном атрибуте, который задает члены этой группы. Думаю, что как его использовать, вы изучите самостоятельно.

Теперь перейдем от теории к практики и создадим с начала группы root и ldap_users. Для чего будем пользоваться утилитой ldapadd(1).

Эта утилита, на сегодня, требует, чтобы ей на стандартный вход через опцию -f передавался ldif-файл, содержащие в себе узлы DN. В нашем случае, это будет ou=Group,dc=engine,dc=local, cn=root,ou=Group,dc=engine,dc=local(группа суперпользователя) и cn=user,ou=Group,dc=engine,dc=local(группа обычных пользователей) c соответствующими атрибутами, которые мы рассматривали только что.

Предвижу такую ситуацию, когда уникальный номер 1000, уже занят другой группой. Поэтому предлагаю вашему вниманию скрипт get_groupsgid.sh, который рассчитает вам уникальный номер для группы ldap_users.

#!/bin/sh

  for i in `cat /etc/group | awk 'BEGIN { FS = ":" }; { if ( $1 != "nogroup" ) print $3 }' | sort -n`;
    do
        let "gid = $i + 1"
    done

    echo "gidNumber: $gid"

#eof

Ниже привожу результат работы скрипта, который показал, что максимальный номер группы равен 1022. Это значит, как минимум, начиная с номера 1022, и как максимум до номера 65534, который занимает nogroup, мы можем использовать для группы ldap_users.

my_user@engine:~/Documents/rjaan$ ./get_groupsgid.sh
gidNumber: 1022

Теперь после, того как нам стал известен gidNumber для группы пользователей ldap_users, мы можем смело написать ldif-файл nss-groups.ldif.

#Group,engine.local
dn: ou=Group,dc=engine,dc=local
ou: Group
objectClass: organizationalUnit
objectClass: top

#root,Group,engine.local
dn: cn=root,ou=Group,dc=engine,dc=local
cn: root
objectClass: posixGroup
objectClass: top
gidNumber: 0
memberUid: 0

#ldap_users,Group,engine.local
dn: cn=ldap_users,ou=Group,dc=engine,dc=local
cn: ldap_users
objectClass: posixGroup
objectClass: top
gidNumber: 1022
memberUid: my_user
memberUid: other_user

#eof

Только что написанный файл nss-groups.ldif мы добавляем к директории dc=engine,dc=local дочерние, которые являются её узлами, а именно подграф с вершиной ou=Group,dc=engine,dc=local.

Корневой директорией, как мы помним, у нас является dc=engine,dc=local. Остальные узлы у нас относятся к структурной части организации или подразделения, Organizational Unit(ou), которая обычно состоит из общепринятых имён,Common names(cn).

А вот какого типа записей содержит DN задается через objectClass, или иначе говоря, каждый DN представляет собой объект или набор атрибутов, которые ему соответствуют. Мало того, атрибут objectClass определяет какие другие атрибуты должен иметь объект. Мало того, если мы указываем в атрибуте objectClass, то мы указываем к какому классу объекту принадлежит DN, мы должны не забывать, что ему на LDAP-сервере должна быть определена соответствующая схема. Так к классу объекту posixGroup,posixAccount, или shadowAccount будет соответствовать схема nis.schema, которая определяется на LDAP-сервере через директиву include в его конфигурационном файле /etc/ldap/slapd .

По-мимо отношений объекта к службе NSS, наш объект имеет обязательный класс объекта top, который указывает на то, что этот объект может иметь дочерние объекты и являться им родительским.

my_user@engine ~$ ldapadd -x -W -Hldap://engine -D "cn=admin,dc=engine,dc=local" -f nss-groups.ldif

Теперь, точно так же как и для ou=Group,dc=engine,dc=local создаем файл nss-users.ldif добавления узлов, а именно подграф с вершиной ou=People,dc=engine,dc=local, как мы помним эта вершина будет содержать DN c учётными записями пользователей. Поэтому, сначала мы получим максимальный номер идентификатора пользователя, чтобы уникальные идентификаторы локальных пользователей не вступали в противоречие с раздаваемыми с LDAP сервера.

Поэтому предлагаю вашему вниманию скрипт get_usersuid.sh, который рассчитает вам уникальный номер для группы users.

#!/bin/sh

  for i in `cat /etc/passwd | awk 'BEGIN { FS = ":" }; { if ( $1 != "nobody" ) print $3 }' | sort -n`;
    do
        let "uid = $i + 1"
    done

    echo "uidNumber: $uid"

#eof

Ниже привожу результат работы скрипта, который показал, что максимальный номер пользователя равен 1009. Это значит, как минимум, начиная с номера 1009, и как максимум до номера 65534, который занимает пользователь nobody, мы можем использовать для наших пользователей my_user и other_user

my_user@engine:~/Documents/rjaan$ ./get_usersuid.sh
uidNumber: 1009

Теперь напишем ldif-файл для того, чтобы добавить выше означанных пользователей на LDAP-сервер. Мы напишем ldif-файл nss-users.ldif, который создаст подграф с вершинной ou=People,dc=engine,dc=local и DN c пользователями Super User, My user и Other user.

#People,engine.local
dn: ou=People,dc=engine,dc=local
ou: People
objectClass: organizationalUnit
objectClass: top

dn: cn=Super user,ou=People,dc=engine,dc=local
cn: Super user
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
sn: Super user's root
uid: root
uidNumber: 0
gidNumber: 0
homeDirectory:/root
loginShell: /bin/bash

dn: cn=My user,ou=People,dc=engine,dc=local
cn: My user
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
sn: My user's my_user
uid: my_user
uidNumber: 1009
gidNumber: 1022
homeDirectory:/home/my_user
loginShell: /bin/bash

dn: cn=Other user,ou=People,dc=engine,dc=local
cn: Other user
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
sn: Other user's other_user
uid: other_user
uidNumber: 1010
gidNumber: 1022
homeDirectory:/home/other_user
loginShell: /bin/bash

Завершаем возню с DN пользователями и заливаем файл nss-users.ldif на LDAP-сервер.

my_user@engine ~$ ldapadd -x -W -Hldap://engine -D "cn=admin,dc=engine,dc=local" -f nss-users.ldif

Остается внести изменения в /etc/nsswitch.conf и попробывать выполнить getent group и getent passwd, только не забудте перезапустить сеанс после внесения изменений в файл /etc/nsswitch.conf.

my_user@engine ~$ gksu 'gedit /etc/nsswitch.conf'

В редакторе gedit вносим изменения в файл /etc/nsswitch.conf базы данных passwd,group,shadow службы NSS.


# /etc/nsswitch.conf
#
# Example configuration of GNU Name Service Switch functionality.
# If you have the `glibc-doc-reference' and `info' packages installed, try:
# `info libc "Name Service Switch"' for information about this file.

passwd:        compat ldap
group:         compat ldap
shadow:        compat ldap

hosts:         files mdns4_minimal [NOTFOUND=return] dns mdns4
networks:      files

protocols:     db files
services:      db files
ethers:        db files
rpc:           db files

netgroup:      nis

# eof

После того, как настроили libnss-ldap мы можем приступить к настройки libpam-ldap, но перед этим рекомендую создать загрузочную флешку, а уже, затем мучить PAM-модули. Это потребуется, в том случае, если вы сделаете ошибку и не сможете войти в систему из-за ошибок в настройках PAM-модулей.

Сначало настраиваем пакет libpam-ldap через dpkg-reconfigure, который попросит ответить вас на следующие вопросы, которые привожу ниже с рекомендуемыми ответами

Универсальный идентификатор ресурсаldap://engine
Индивидуальное имя базы поискаdc=engine,dc=locale
Иcпользовать версию3
Make local root Database adminНет
База данных требует учетное имя?Нет
Local crypt to use when changing passwordCrypt

После чего, вносим изменения в общие файлы PAM-цепочек, имеющие префикс common и находящиеся в /etc/pam.d.

common-auth

auth      sufficient    pam_ldap.so
auth      required      pam_unix.so nullok_secure try_first_pass

common-account

account      sufficient     pam_ldap.so
account      required       pam_unix.so

common-session

session      sufficient     pam_ldap.so
session      required       pam_unix.so

common-password

password      sufficient     pam_ldap.so
password      required       pam_unix.so obscure md5 use_first_pass

После чего, должны изменить пароль учетной записи root через passwd(1).

root@engine:~# passwd
New password:
Re-enter new password:
LDAP password information changed for root
passwd: пароль успешно обновлён

Теперь мы должны перегрузить наш компьютер на котором настраивали libpam-ldap. Иначе, вы не сможете изменить пароль у пользователей my_user, other_user.

6. Модель единого пользователя для ЛВС

Эта модель оперирует двумя пространствами учетных записей — глобальным и локальным. Глобальное пространство учетных записей пользователей применимо на всех узлах ЛВС, которыми являются сервер и его клиенты.

Почему клиенты? Потому что учетные записи пользователей находятся на сервере и распределяются по узлам ЛВС через клиент-серверное соединение, которое образуется между клиентом(libnss-ldap) и LDAP-сервером(демон slapd). Потому глобальное пространство состоит из учетных записей, которые хранятся на LDAP-сервере в директории "ou=People,dc=engine,dc=local" и раздаются по клиентам ЛВС.

К локальному же пространству относятся все учетные записи, которые находяться в базе passwd службы NSS. Данный пример использования LDAP-сервера, которым является демон slapd, и связки LDAP-NSS-PAM, которую реализуют libnss-ldap и libpam-ldap, позволяет сосуществовать двум пространствам и производить в них необходимые изменения для обеспечения жизнедеятельности ЛВС и самих узлов в ней, которыми являются компьютеры рабочих станций и сервер с установленным на них ОС Debian GNU/Linux. В этих учетных записях обычно храняться пользователи от лица которых запускаются демоны(службы) для устранения возможности несанкционированного доступа и пользователи с суперпользователем root, которых вы, ранее, создавали сами или во время инсталляции ОС Debian GNU/Linux.

В используемом мной варианте настройки связки LDAP-NSS-PAM лежит именно модель единого пространства пользователя, которое является глобальное пространством учётных записей пользователей. Оно это пространство сосуществует вместе с локальным пространством учетных записей пользователей. Поэтому было бы разумно назначить разные диапазоны уникальных идентификаторов пользователей и их групп, что в принципе мы и сделали, когда находили с помощью скриптов get_groupsgid.sh и get_usersuid.sh минимальные уникальные идентификаторы для учётной записи пользователя(uid) и группы(gid) в базе passwd службы NSS. Потом, мы их увеличивали на единицу, тем самым делая эти идентификаторы минимальным числом для создания учётных записей пользователей и групп на LDAP-сервере.

Локальное и глобальное пространство пользователя должно пересекаться только в двух точках или в двух учетных записях пользователей root и nobody, но последнюю не обязательного указывать на сервере LDAP, потому что она есть на каждом узле ЛВС, в том числе и сервере. Так, что же нужно дублировать? Только учетную запись пользователя root, т.к. он имеет вход в систему, а первым куда за учетными записями пользователя будет обращаться механизм аутентфикации PAM, так это к учетным записям на сервере LDAP, а не к локальной базе passwd службы NSS. Даже не смотря на то, что в NSS обращение к LDAP серверу стоит на последнем месте.

7. Операции с учетными записями групп и пользователей на LDAP-сервере

Когда мы создавали соединение с сервером LDAP в аргументе rootdn, к функции ldap_simple_bind_s(), мы указывали cn=admin,dc=engine,dc=local, а следом за ним передавали пароль или вместо него нулевой указатель. Тем самым, нам разрешалось читать и/или вносить изменения в записи корневого каталога dc=engine,dc=local, которым является первая база LDAP-сервера с которым мы установили соединение.

В используемых, здесь настройках безопасности доступа к информационным директориям LDAP-сервер, использован тот же принцип, что и в NSS службе для баз данных passwd, group и shadow. Соответственно, без пароля, или передачей нулевого указателя соответствующему аргументу функции ldap_simple_bind_s(), мы имеем только право чтения, а с паролем чтения и записи.

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

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

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

7.2. Синхронные операции

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

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

#include <ldap.h>

int ldap_modify_ext_s(
      LDAP *ld,
      char *dn,
      LDAPMod *mods[],
      LDAPControl **sctrls,
      LDAPControl **cctrls );

void ldap_mods_free(
      LDAPMod **mods,
      int freemods );

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

  typedef struct ldapmod {
      int mod_op;
      char *mod_type;
      union {
         char **modv_strvals;
         struct berval **modv_bvals;
      } mod_vals;
      struct ldapmod *mod_next;
  } LDAPMod;
  #define mod_values mod_vals.modv_strvals
  #define mod_bvalues mod_vals.modv_bvals

Поле структуры mod_op используется для указания типа операции, на которые должен указывать один из определяющих её макросов LDAP_MOD_ADD, LDAP_MOD_DELETE или LDAP_MOD_REPLACE. Поля mod_type и mod_values(modv_strvals или modv_bvals) указывают модифицируемые типы аттрибутов и вмести с ним null-завершаемый двухмерный массив значений для добавления, удаления или изменения соответсвенно. Поле mod_next используется только LDAP-сервером и должен быть игнорирован клиентом, в роли которого выступает ваша программа.

Если вам необходимо указать не значение, состоящее из строки символов, к примеру имя пользователя или его имя входа в систему, а добавить его фотографию или аудиозапись с его голосом, вы должны установить LDAP_MOD_BVALUES в mod_op через логическое ИЛИ вместе одной из операций, указанных ранее(например LDAP_MOD_REPLACE). В этом случае, вы должны использовать указатель на духмерный массив mod_bvalues(modv_bvals) взамен указателя на двухмерный массива mod_values(modv_strvals), которые оба содержат NULL-завершаемые массивы, состоящих из структур bervals так, как это определено в <lber.h>.

Для LDAP_MOD_ADD модификаций, данные значения добавляются к записи и при необходимости с созданием атрибута. Для LDAP_MOD_DELETE модификаций, данные значения удаляются из записи и удалается атрибут, если в нем не осталось значений. Если весь атрибут был удален, то поле mod_values должно быть обнулено NULL-указателем. Для LDAP_MOD_REPLACE-модификаций, атрибут будет иметь перечисленные значения после модификации, которые могут быть созданы, если это потребуется. Все модификации исполняются в том порядке, в каком они были указаны.

Два последних аргумента sctrls и cctrls функции ldap_modify_ext_s() предназначаются для контроля за обменом между клиентом и сервером, но пока не ясно как их использовать мы будем их обнулять.

Функция в случае ошибки возвращает код ошибки errno, описанный в ldap_error(3), в том числе и LDAP_SUCCESS, который соответствует успешному завершению этой API-функции.

Функция ldap_mods_free() может использоваться для освобожения выделенной памяти в общей куче, а не стеке, нуль-завершаемых массивов, которые мы передовали по указателю mod_values(modv_strvals или modv_bvals) в структуре LDAPMod, соответственно передаем в первом аргументе mod. Если второй аргумент freemods содержит не нулевое значение, то функция ldap_mods_free() будет пытаться освобободить память, выделенную под двойной указатель.

 

Вернуться к Оглавлению

 

Copyright © 2010 rjaan as Andrey Rjavskov(Rzhavskov) <rjaan@yandex.ru> <arjavskov@gmail.com>