Jabber (XMPP) + Windows + SSO, часть 2

Jun 16, 2017 18:51


Продолжение предыдущего поста. Под катом чисто технический текст про формирование списка контактов и групп (ростера) для Jabber-сервера Prosody на основании информации, содержащейся в Active Directory LDAP.

За так называемый "shared roster" в Prosody отвечает специальный модуль "mod_groups", который по умолчанию выключен. Он имеет единственную настройку: путь к текстовому файлу, где содержится список пользователей (JID-ов и их имён), разбитый на секции. Кроме того, после перезаписи этого файла нам понадобится каким-то образом объяснить этому модулю, что неплохо бы перечитать конфиги. К сожалению, это можно сделать только посредством management-интерфейса, слушающего TCP-сокет. И этот интерфейс также представлен отдельным модулем "mod_admin_telnet" и по умолчанию выключен.

Мой заказчик сформировал следующее техническое задание. Все пользователи Jabber-а будут созданы в отдельном OU (Organization Unit) под названием "CLIENTS". Группироваться пользователи будут путём добавления в соответствующие Security Groups. Такие группы носят имена, начинающиеся с символов "IM_" (например, "IM_IT_Dept") и также находятся в отельном OU под названием "SECURITY_GROUPS". JID пользователя будем брать из атрибута "userPrincipalName", его имя - из атрибута "displayName". Название группы будем брать из атрибута "name".

Итак, поехали. Прежде всего создадим в AD какого-нибудь пользователя (всё равно какого) с минимальными привилегиями, лишь бы имел доступ на чтение к LDAP-каталогу. Для примера назовём его "ldap_agent" и поместим его в OU с именем "AGENTS". От его имени мы будем обращаться к контроллеру домену за необходимой информацией.

А дальше придётся накатать скрипт на любимом скриптовом языке. В интернете есть примеры на PHP. Но по мне так, для решения данной задачи и Bash-а будет вполне достаточно. У меня получилось что-то вроде того.
#!/bin/bash

HOST="192.168.1.1"
DOMAIN="DC=supercompany,DC=com"
AGENT="cn=ldap_agent,ou=AGENTS,$DOMAIN"
PASS="superlongpassword"
CONFTMP="/etc/prosody/newgroups.cfg"
OLDCONF="/etc/prosody/groups.cfg"

echo -n > "$CONFTMP"

# Тута мы ищем список групп
/usr/bin/ldapsearch -x -h $HOST -D "$AGENT" -b "OU=SECURITY_GROUPS,$DOMAIN" \
-w "$PASS" 'cn=IM_*' name distinguishedName | \
/bin/grep 'distinguishedName: \|name: ' | \
/bin/sed -e 's/^[a-zA-Z]\+: //g' \
| \
while read line ; do
read groupname

# А тут мы в каждую группу добавляем принципалов
echo '['"$groupname"']' >> "$CONFTMP"
/usr/bin/ldapsearch -x -h $HOST -D "$AGENT" -b "ou=CLIENTS,$DOMAIN" \
-w "$PASS" "memberOf=$line" userPrincipalName displayName | \
/bin/grep 'displayName: \|userPrincipalName: ' | \
/bin/sed -e 's/^[a-zA-Z]\+: //g' \
| \
while read user ; do
read principal
echo -n "$principal"'=' >> "$CONFTMP"
echo "$user" >> "$CONFTMP"
done
done

if [ `cat "$CONFTMP" | wc -l` -lt 2 ] ; then
echo "Fail retreiving structure from LDAP"
/bin/rm -f "$CONFTMP"
exit 1
fi

# Переписываем конфиг prosody и даем команду перезагрузить

if [ ! -f "$OLDCONF" ] ; then
/usr/bin/touch "$OLDCONF"
/bin/chown root:prosody "$OLDCONF"
/bin/chmod 640 "$OLDCONF"
fi

# Если новый конфиг не отличается от старого, то ничего не делаем
/usr/bin/cmp -s "$OLDCONF" "$CONFTMP" && \
{ rm -f "$CONFTMP"
echo "Nothing to do"
exit 0
}

# Перезаписываем конфиг и заставляем prosody перечитать модули
cat "$CONFTMP" > "$OLDCONF"
rm -f "$CONFTMP"

exec 3<>/dev/tcp/127.0.0.1/5582

sed -n '/^\r$/Q' <&3
echo -e "module:reload(\"groups\")\r" >&3
sed -n '/OK:.*\r$/Q' <&3
echo -e "module:reload(\"roster\")\r" >&3
sed -n '/OK:.*\r$/Q' <&3
echo -e "quit" >&3
cat <&3 > /dev/null

echo "Group information reloaded"

exit 0

Немного комментариев. В константах "HOST", "DOMAIN" указываем где искать контроллер домена и LDAP-корень этого домена. "AGENT", "PASS" - Distinguished Name и пароль предварительно созданного нами в AD пользователя-агента для чтения каталога (см. выше). "CONFTMP", "OLDCONF" - путь к временному файлу листинга пользователей (придумываем произвольно) и "боевому" аналогичному файлу. Последний должен совпадать с директивой "groups_file" в конфиге "prosody.cfg.lua".

Вся магия совершается утилитой "ldapsearch" из пакета "ldap-utils". Сперва находим список групп с подходящими нам именами, потом в цикле для каждой группы ищем принадлежащих ей пользователей и заполняем таким образом строчка за строчкой временный файл с листингом. Потом проверяем что у нас получилось и получилось ли. Если всё хорошо, то сравниваем побайтно старый и новый файл. Если они ничем не отличаются, то ничего и не делаем. Если же отличаются, то прицепляемся к TCP-сокету на localhost:5582 и произносим туда заклинания module:reload("groups"), module:reload("roster"), quit. Это заставит сервер перечитать сгенерированный нами список пользователей.

Этот скрипт подвешиваем на выполнение по cron-у, например, раз в час. А для особо нетерпеливых можно рядышком поднять какой-нибудь Apache и наколдовать простейший CGI-скрипт для принудительного отвешивания пинка со стимуляцией через веб-морду.
#!/bin/bash

echo "Content-type: text/html"
echo

echo ''
echo ''
echo ''
echo 'Обновление ростера'
echo ''
echo ''
/usr/bin/sudo /etc/prosody/fetch.sh
echo ''
echo ''

exit 0

Не забываем, что данный скрипт будет выполнятся от имени "www-data", а посему для успешного повышения привилегий придётся настроить также sudoers типа того:
www-data ALL=NOPASSWD:/etc/prosody/fetch.sh
Ага, вот такой я извращенец.

администрирование, xmpp, sso, kerberos, linux, windows, bash

Previous post Next post
Up