Рубрика:
Администрирование /
Продукты и решения
|
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|
Алексей Барабанов
Размещаем пользовательские бюджеты в LDAP
Часть 2
Настроив подсистему аутентификации и авторизации на использование LDAP, попробуем работать с ней прозрачным образом, так же как с файловой базой учетных записей. Проверим, насколько код стандартных утилит приспособлен для взаимодействия с LDAP.
В первой части статьи [1] вы можете найти всё необходимое для настройки сервера OpenLDAP, указания его в качестве хранилища информации о пользовательских бюджетах и соответствующей инициализации. Быть может, кто-то из вас даже попробовал предложенное в действии и «открыл» свой счет удобств и потерь от такого усовершенствования. Если же нет, то предлагаю поверить «на слово» и продолжить рассмотрение вопроса так, будто бы система аутентификации и авторизации с LDAP построена в среде openSUSE версии 10.1 или 10.2 (кому что удобнее) в полном соответствии с рекомендациями из части 1.
Использование LDAP
Попробуем создать пользовательский бюджет так, чтобы запись о нем была произведена в LDAP. Воспользуемся стандартным системным средством useradd. Для сравнения создадим обычный бюджет, зарегистрированный в passwd и shadow:
# useradd -m -c "shadow user" suser1
# getent passwd | grep suser
suser1:x:1001:100:shadow user:/home/suser1:/bin/bash
|
Установим для него некоторый пароль (здесь и далее пароль совпадает с именем пользователя):
# passwd suser1
Changing password for suser1.
New Password:*****
Bad password: too simple
Reenter New Password:*****
Password changed.
# getent shadow | grep suser
suser1:$2a$10$1qqIvf4KcxLU/1RzxYPIiuKhjnohIlZa68fSHGjwiShgcvheuNFli:13495:0:99999:7:::
|
Также проверим, как работает авторизация такого бюджета:
> su - suser1 -c "LC_ALL=C id"
Пароль:*****
uid=1001(suser1) gid=100(users) groups=16(dialout),33(video),100(users)
|
А теперь попробуем проделать то же самое, но для бюджета, регистрируемого в LDAP:
# useradd -D cn=ldapadmin,dc=office,dc=localnet --service ldap -m -c "ldap user" luser1
Enter LDAP Password:*****
Base DN for user account `luser1' is "ou=People,dc=office,dc=localnet".
LDAP information update failed: Object class violation
useradd: User not added to LDAP group `video'.
LDAP information update failed: Object class violation
useradd: User not added to LDAP group `dialout'.
# getent passwd | grep luser
luser1:x:1002:100:ldap user:/home/luser1:/bin/bash
|
Хотя пользователь был зарегистрирован, но не удалось сделать записи о его членстве в дополнительных группах:
# getent group | grep "^\(video\|dialout\)"
dialout:x:16:alekseybb,suser1
video:x:33:alekseybb,suser1
dialout:*:16:
video:*:33:
|
И, увы, эта проблема не поддается простому «лечению». Секрет в том, что исходные тексты pwdutils, куда входит утилита useradd, модифицированы для использования схемы rfc2307bis, которая заменила схему nis и по идее должна обеспечить POSIX-совместимые атрибуты пользовательских бюджетов. Но вот вместо применяемых в файловых базах индексов в виде номеров GID/UID, автор модификаций, все тот же неутомимый Thorsten Kukuk, решил заменить их на отличительные имена LDAP (то есть поменять индексы на LDAP-ссылки), что делает такую схему несовместимой с ранее принятыми алгоритмами работы с учетными данными пользователей. Можно, конечно, подождать, пока все участники процесса разработки договорятся и придут к выводу о том, что каждая схема регистрации должна пользоваться собственными атрибутами, поддерживая определенный уровень совместимости. Но они не договорились ни к выходу openSUSE 10.1, ни к выходу openSUSE 10.2, и я делаю вывод, что рациональнее пересобрать пакет pwdutils, применяя регрессивную заплатку следующего содержания:
# cat pwdutils-3.1.2-memberUid-member.diff
--- lib/libldap.c.orig 2006-12-13 07:37:45.000000000 +0300
+++ lib/libldap.c 2006-12-13 07:46:09.000000000 +0300
@@ -1535,12 +1535,12 @@
return 1;
rc = ldap_update_group (session, group, binddn, password, LDAP_MOD_ADD,
- "member", dn );
+ "memberUid", member );
free (dn);
if ( first )
{
ldap_update_group (session, group, binddn, password, LDAP_MOD_DELETE,
- "member", "" );
+ "memberUid", "" );
}
}
else
@@ -1569,11 +1569,11 @@
if ( last )
{
ldap_update_group (session, group, binddn, password, LDAP_MOD_ADD,
- "member", "" );
+ "memberUid", "" );
}
rc = ldap_update_group (session, group, binddn, password, LDAP_MOD_DELETE,
- "member", userdn );
+ "memberUid", member );
free (userdn);
}
else
@@ -2039,13 +2039,13 @@
strvals[0][1] = "groupOfNames";
strvals[0][2] = NULL;
- /* groupOfNames requires at least one "member"
* attribute
+ /* groupOfNames requires at least one "memberUid"
* attribute use an empty value for groups with
* no members */
strvals[3][0] = "";
strvals[3][1] = NULL;
mod[3].mod_values = strvals[3];
- mod[3].mod_type = "member";
+ mod[3].mod_type = "memberUid";
mod[3].mod_op = LDAP_MOD_ADD;
mods[3] = &mod[3];
mods[4] = NULL;
#
Этот патч предназначен для исходных текстов к openSUSE 10.2, но должен подойти и к pwdutils-3.0.7.1-17 из openSUSE 10.1, хотя там проще изменить патч, накладывающий модификацию согласно rfc2703bis. Бинарные сборки модифицированных pwdutils соответствующих версий доступны в [2]. Итак, исправим и пробуем снова:
# userdel -D cn=ldapadmin,dc=office,dc=localnet --service ldap -r luser1
Enter LDAP Password:*****
no crontab for luser1
# useradd -D cn=ldapadmin,dc=office,dc=localnet --service ldap -m -c "ldap user" luser1
Enter LDAP Password:*****
Base DN for user account `luser1' is "ou=People,dc=office,dc=localnet".
# getent group | grep "^\(video\|dialout\)"
dialout:x:16:alekseybb,suser1
video:x:33:alekseybb,suser1
dialout:*:16:luser1
video:*:33:luser1
|
Все хорошо. Но не совсем. Возникают трудности с назначением пароля. Поскольку в утилите passwd нет возможности указать, с каким отличительным именем обращаться к LDAP (кстати, вероятно, это временное упущение), а в настройках клиента /etc/ldap.conf не заданы параметры принудительной линковки к LDAP, то если пароль не инициализирован, установить его от суперпользователя никак не получится. Обойти проблему можно несколькими путями. Во-первых, у нас в запасе есть smbldap-passwd, и если /etc/smbldap-tools/smbldap_bind.conf настроен правильно, то с помощью этой утилиты можно установить пароль любого пользователя, смирившись с тем, что в конфигурационном файле хранится в открытом виде пароль администратора LDAP. Поэтому так делать не будем. Во-вторых, можно установить пароль-заглушку сразу при создании бюджета:
# useradd -D cn=ldapadmin,dc=office,dc=localnet --service ldap -p $(slappasswd -s luser2 -h {SSHA} ) -m -c "ldap user" luser2
Enter LDAP Password:*****
Base DN for user account `luser2' is "ou=People,dc=office,dc=localnet".
|
И в третьих, можно вспомнить про утилиту usermod (кстати, авторы [1] об этом не упоминают, потому что в ALT Linux используется иная версия управляющих утилит), которая позволяет указывать отличительное имя для связи с LDAP:
# usermod -D cn=ldapadmin,dc=office,dc=localnet --service ldap -p $(slappasswd -s luser1 -h {SSHA} ) luser1
Enter LDAP Password:*****
|
Проверяем, что получилось, и обнаруживаем, что бюджет, где пароль установлен через usermod, недоступен:
alekseybb@suse102:~> su - luser1 -c "LC_ALL=C id"
Password:*****
Пароль:*****
Права доступа на базу данных паролей может быть слишком ограниченная.
su: неправильный пароль
|
А вот тот, что получил пароль при инициализации, прекрасно работает:
alekseybb@suse102:~> su - luser2 -c "LC_ALL=C id"
Password:*****
uid=1003(luser2) gid=100(users) groups=16(dialout),33(video),100(users)
|
Причина скрывается в формате парольных хешей:
# slapcat | grep "^\(dn\|userPassword\)" | tail -n 4
dn: uid=luser1,ou=People,dc=office,dc=localnet
userPassword:: e2NyeXB0fXtTU0hBfS96d0tsREFDak8xa1MxMktrNENKSCsvSTNURVFTSkhZ
dn: uid=luser2,ou=People,dc=office,dc=localnet
userPassword:: e1NTSEF9eGNtSFRNZC9zTS9WRDd2V2JqWk1tL2phd0lEdFJPc1k=
|
Здесь и «простым глазом» видно, что с хешами что-то не так, а приглядевшись внимательнее, понимаем, что хеш из бюджета luser1 имеет неверный формат.
# slapcat | grep "^userPassword" | tail -n 2 | awk '{print $2}' | mimencode -b -u
{crypt}{SSHA}/zwKlDACjO1kS12Kk4CJH+/I3TEQSJHY{SSHA}xcmHTMd/sM/VD7vWbjZMm/jawIDtROsY
|
Таким образом, обнаруживается вторая ошибка, исправляемая следующим патчем:
# cat pwdutils-3.1.2-newpassword.diff
--- lib/user.c.orig 2006-12-15 02:16:30.000000000 +0300
+++ lib/user.c 2006-12-15 02:18:42.000000000 +0300
@@ -1036,16 +1036,9 @@
}
if (data->newpassword)
- {
- const char *cryptstr = "{crypt}";
- char buffer[strlen (data->newpassword) +
- strlen (cryptstr) + 1];
- snprintf (buffer, sizeof (buffer), "%s%s", cryptstr,
- data->newpassword);
retval = ldap_update_user (session, data->pw.pw_name,
- data->binddn, data->oldclearpwd,
- "userPassword", buffer);
- }
+ data->binddn, data->oldclearpwd,
+ "userPassword", data->newpassword);
}
if (retval != 0)
fprintf (stderr,
Видно, что в оригинальном коде полученная строка обрабатывалась не как хеш, а как текстовый пароль. Это противоречит документации на утилиту usermod, и вообще непонятно, как попало в код. В архиве [2] содержатся полностью исправленные версии pwdutils.
После их установки получаем адекватные записи в LDAP:
# usermod -D cn=ldapadmin,dc=office,dc=localnet --service ldap -e -1 -p $(slappasswd -s luser1 -h {SSHA} ) luser1
Enter LDAP Password:*****
# slapcat | grep "^\(dn\|userPassword\)" | tail -n 4
dn: uid=luser1,ou=People,dc=office,dc=localnet
userPassword:: e1NTSEF9bTY3cGNYYnR3N0xRdEhZNjNlMWpRQmZZakZNSXBNSUQ=
dn: uid=luser2,ou=People,dc=office,dc=localnet
userPassword:: e1NTSEF9eGNtSFRNZC9zTS9WRDd2V2JqWk1tL2phd0lEdFJPc1k=
|
И, естественно, удачную проверку авторизации:
alekseybb@suse102:~> su - luser1 -c "LC_ALL=C id"
Password:*****
uid=1002(luser1) gid=100(users) groups=16(dialout),33(video),100(users)
|
Тем самым ранее поставленную цель – создать полностью совместимую с традиционной схему размещения пользователей в LDAP – можно считать достигнутой. В завершение можно порекомендовать сменить ранее установленный простенький пароль для ldapadmin на что-нибудь более существенное, например, так можно поменять его на my.new.pass:
# cat <<EOT | ldapmodify -v -a -D "cn=ldapadmin,dc=office,dc=localnet" -H ldap://localhost -x -w admin
dn: cn=ldapadmin,dc=office,dc=localnet
changetype: modify
replace: userPassword
userPassword: $(slappasswd -s my.new.pass -h {SSHA})
EOT
ldap_initialize( ldap://localhost )
replace userPassword:
{SSHA}lZyR2iLrXzijblIE5gBF5UEMMlzMQabk
modifying entry "cn=ldapadmin,dc=office,dc=localnet"
modify complete
|
Кстати, после этого smbldap-tools перестанут работать, то есть станут не опасными.
Штатный способ настройки в openSUSE
Можно задаться вопросом, зачем там много настраивать и «подкручивать», есть же штатные для openSUSE инструменты, составляющие YaST и позволяющие выполнить все те задачи, что выше были перечислены. Попробуем! Работать будем в openSUSE 10.2, чтобы не натыкаться на давно исправленные ошибки. Запущенное после уже сделанных настроек средство для установки LDAP-сервера приняло установленную конфигурацию, но потребовало вернуть суперпользователя rootdn (см. рис. 1).
Рисунок 1. Настройка сервера LDAP
Здесь важно отметить, что никакой другой уровень доступа, кроме rootdn, автоматического настройщика не устраивает! Предполагаю, что разработчики openSUSE очень не уверены в том, что без rootdn их системы будут работать. Остается лишь гадать, в какой степени это относится к SLES. Кроме того, обратите внимание, что система правильно восприняла тип парольных хешей – SSHA.
После завершения в конфигурационных файлах LDAP ничего не изменилось, кроме того, что был добавлен rootdn:
# grep ^root /etc/openldap/slapd.conf
rootdn "cn=ldaproot,dc=office,dc=localnet"
rootpw "{ssha}DHToDgxUuvxDwua/SB5Em5zzlABQWVpGVA=="
В процессе настройки клиента совсем не удивило, что снова было указано на невозможность работы без задания суперпользователя LDAP (см. рис. 2).
Рисунок 2. Настройка клиента LDAP
Дальнейшая настройка выполняется если не просто, то очень просто. Не буду ее даже и комментировать. Уже здесь можно отметить, насколько все легко. Хотя, как я уже писал, все перечисленные в статье команды легко связываются в единый скрипт и выполняются вообще без каких-либо дополнительных манипуляций.
Будем считать, что все уже настроено. Через средство управления пользовательскими учетными записями создадим дополнительного пользователя luser3, зарегистрированного в LDAP. И проверим, как все работает.
Сначала проверим, что не «сломался» старый бюджет:
alekseybb@suse102:~> su - luser1 -c "LC_ALL=C id"
Пароль:
Права доступа на базу данных паролей может быть слишком ограниченная.
uid=1002(luser1) gid=100(users) groups=16(dialout),33(video),100(users)
|
Да, все работает, но забавно. Если интересно, можете взглянуть, как нелепо расписаны приоритеты поиска для pam в /etc/pam.d. Но раз работает, то и не будем придираться.
Хуже то, что проверка «свеженького» бюджета не проходит:
alekseybb@suse102:~> su - luser3 -c "LC_ALL=C id"
su: пользователь luser3 не существует
|
Не верим своим глазам, проверяем внимательнее:
# getent passwd | tail -n 2
luser1:x:1002:100:ldap user:/home/luser1:/bin/bash
luser2:x:1003:100:ldap user:/home/luser2:/bin/bash
|
Точно, нет такого пользователя!
Может быть в shadow:
# getent shadow | tail -n 2
suser1:$2a$10$1qqIvf4KcxLU/1RzxYPIiuKhjnohIlZa68fSHGjwiShgcvheuNFli:13495:0:99999:7:::
+::0:0:0::::
|
А там вообще какой-то неадекватный мусор. Хотя в конфигурации nss, как и прежде:
# grep shadow /etc/nsswitch.conf
shadow: files
|
Посмотрим, что в самой базе LDAP:
# slapcat | grep "^\(dn: u\|userP\)" | tail -n 6
dn: uid=luser1,ou=People,dc=office,dc=localnet
userPassword:: e1NTSEF9bTY3cGNYYnR3N0xRdEhZNjNlMWpRQmZZakZNSXBNSUQ=
dn: uid=luser2,ou=People,dc=office,dc=localnet
userPassword:: e1NTSEF9eGNtSFRNZC9zTS9WRDd2V2JqWk1tL2phd0lEdFJPc1k=
dn: uid=luser3,dc=office,dc=localnet
userPassword:: e2V4b3B9bHVzZXIz
|
Нет слов! Смех переходит в гомерический. Кроме того, что учетная запись luser3 не размещена в нужном контейнере, так у нее еще и какой-то странный хеш. Смотрим, что внутри:
# slapcat | grep "^userP" | tail -n 1 | awk '{print $2}' | mimencode -b -u
{exop}luser3
|
Ну что тут сказать? Создается впечатление, что разработчики OpenLDAP и YaST проживают на разных планетах, и им, как «лунатикам» с «марсианами», никак не договориться. Хотя, быть может, это моя вина, и я что-то пропустил в настройке? Снова вернемся к настройке клиента (см. рис. 3).
Рисунок 3. Параметры клиента LDAP
Видим, что контейнер, в котором ищутся, а значит, и размещаются учетные записи пользователей YaST, понят правильно. Даже exop правильно интерпретирован. Но в результате получаем нелепицу. Попробуем починить, исправим путь поиска в nss:
# grep passwd /etc/ldap.conf
nss_base_passwd dc=office,dc=localnet?sub
|
И сразу же начинаем «видеть» новичка:
# getent passwd | tail -n 3
luser1:x:1002:100:ldap user:/home/luser1:/bin/bash
luser2:x:1003:100:ldap user:/home/luser2:/bin/bash
luser3:x:1004:100:Yast ldap user:/home/luser3:/bin/bash
|
Пробуем авторизоваться:
alekseybb@suse102:~> su - luser3 -c "LC_ALL=C id"
Пароль:
Права доступа на базу данных паролей может быть слишком ограниченная.
su: неправильный пароль
|
Опять те же «грабли»! Здесь лично я делаю вывод, что в команде openSUSE не только Thorsten Kukuk любит пошутить. Юмористов там очень много, и шутки их с увеличением индекса дистрибутива становятся все разнообразнее.
Выводы
Статья может быть использована как практическое руководство в настройке общего пользовательского LDAP-каталога. Но описанное здесь позволяет сделать выводы как насчет надежности используемых в Linux настроек общесетевых каталогов LDAP, понять, чем озабочены разработчики подобных систем или, напротив, чем они пренебрегают, так и установить недостатки предложенного в силу свойств, присущих LDAP.
Во-первых, описанная схема работает только в pwdutils, используемых в openSUSE и еще в PLD. Во многих других, например RHEL, FC, Mandriva, ALT Linux, применяются shadow-utils и их модификации. Отличие в том, что pwdutils, которые создал Thorsten Kukuk, позволяют указывать отличительное имя администратора в командной строке, то есть дают работать с административными правами даже в отсутствие параметра rootbinddn в конфигурации клиента LDAP. Это позволяет избежать столь уязвимого указания пароля администратора в текстовой форме в openSUSE и PLD и приводит к тому, что все остальные дистрибутивные ветки при настройке общесетевого репозитория LDAP вынуждены хранить пароль администратора системы в открытом виде, как минимум, на тех станциях, с которых допускается административное управление каталогом. Что делает использование LDAP менее надежным, чем традиционная схема размещения бюджетных данных, обходящаяся хранением парольных хешей в файлах.
Во-вторых, тот факт, что данная схема реализуется лишь с помощью заплаток, отсутствующих в дистрибутивных версиях pwdutils, свидетельствует о том, что никто и в openSUSE не смущается данными проблемами и, как и ранее, работает через rootbinddn, и даже более, используя в качестве оного rootdn, как, например в [3]. Другими словами, хранение пароля администратора в нешифрованном виде является повсеместной практикой в Linux. Для сравнения попробуйте найти нешифрованный пароль администратора на сервере или на рабочей станции MS Windows. И опять приходится признать, что Linux-каталог LDAP существенно менее безопасен, чем аналогичный в MS Windows.
В-третьих, даже использование pwdutils не является практичным решением, так как требует введения пароля администратора для каждой операции, что не всегда возможно в случае, если эти утилиты используются как основа в обертывающих скриптах. То есть это проблема «by design». Аналогичная проблема возникает при использовании smbldap-tools. Этот инструментарий предназначен для подключения его в качестве внутренних утилит в samba3. И такое их использование исключает интерактивную фазу, в ходе которой можно задать вопрос администратору о парольной фразе. Что естественным образом приводит к необходимости хранить пароль в месте, доступном для автоматического считывания (в данном конкретном случае пароль записан в конфигурационный файл). То есть снова приходим к выводу о слабой защищенности практических решений на основе LDAP в Linux.
И вот теперь сделаем самый главный вывод о том, как можно исправить ситуацию. Фактически вся слабость использования LDAP по сравнению с традиционной формой хранения учетных данных следует из-за невозможности аутентифицировать удаленного привилегированного пользователя на основе сеансовых данных его локальной регистрации. При использовании локального доступа к учетным данным UID легко проверяется и является надежным критерием для оценки прав доступа. Так как взаимодействие с LDAP происходит по клиент-серверной схеме, то удаленная сторона обязана потребовать аутентификации клиентской части прежде, чем разрешить нужный уровень доступа. И единственное, на мой взгляд, средство, которое позволяет избежать постоянного интерактивного взаимодействия с механизмами аутентификации, это Kerberos. Лишь в случае построения аутентификации на основе Kerberos можно заранее и на определенный срок получить для некоторого локального администратора достаточный уровень прав на удаленный каталог LDAP и использовать данную возможность в неинтерактивных скриптах, запускаемых от упомянутого пользователя.
Итак, LDAP обречен на взаимодействие с Kerberos. Именно по этому пути пошли разработчики Microsoft и точно также поступили в Samba Team. Точно так же надо делать и в случае любого применения LDAP как хранилища учетных данных.
Большинство схем, описывающих иную настройку (источники [3] и [4] не исключение), можно применять лишь в локальных вариантах, и то, закрывая глаза на вопросы безопасности. Но симбиоз OpenLDAP и Kerberos заслуживает отдельного рассмотрения. Это значит, продолжение следует...
- Барабанов А. Размещение пользовательских бюджетов в LDAP. Часть 1. //«Системный администратор», №1, 2007 г. – C. 36-43.
- Исходные тексты и собранные пакеты для настоящей статьи – http://www.barabanov.ru/arts/ldap-start.
- Как настроить SAMBA в SUSE как PDC с OpenLDAP, DYNDNS и CLAM – http://ru.opensuse.org/Howto_setup_SUSE_as_SAMBA_PDC_with_OpenLDAP,_DYNDNS_and_CLAM.
- Настройка OpenLDAP и его клиентов – http://www.freesource.info/wiki/ALTLinux/Dokumentacija/OpenLDAP?v=1845.
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|