Форум проекта Pro-LDAP.ru
Общие вопросы по LDAP => Общий раздел => Тема начата: earyutin от 01 Ноябрь 2022, 00:54:30
-
Всем привет, Коллеги!
Дошли наконец до задачи настройки централизованной авторизации.
Были развернуты freeradius для сетевых устройств и Open LDAP Proxy в связке с Active Directory для порталов.
Возник вопрос: возможно ли выполнять запуск внешних скриптов на Open LDAP, когда есть совпадение пары логина и пароля, до установления привязки?
На freeradius вроде как можно, но пока не вникал. https://networkradius.com/doc/3.0.10/raddb/mods-available/exec.html
Идея в том, чтобы OpenLDAP запускал php скрипт, после получения от AD сообщения о совпадение пары логина и пароля, который в свою очередь отправлял сообщение сотруднику и ждал бы ответ от него.
Предполагаемая схема во вложении.
Нашел обсуждения https://pro-ldap.ru/forum/index.php?topic=1039.msg2149#msg2149 и https://pro-ldap.ru/forum/index.php?topic=433.msg1169#msg1169. Подскажите, возможно ли использовать database shell для реализации моей задачи ?
С php я думаю разберусь. Мне бы понять, можно ли организовать запуск и ожидание выполнение скрипта в средствами OpenLDAP, случае положительного ответа от Active Directory.
Спасибо )!
-
Здравствуйте! Правильно ли я понимаю, что Вы хотите после прохождения двухфакторной аутентификации, первым фактором которой будет совпадение логина и пароля пользователя AD, получить какие-то данные из самого AD?
Если сами данные из AD не нужны, а нужна только аутетификация, то, наверное, нет смысла вообще городить огороды с OpenLDAP, а сразу разбираться с Radius. Я в нём не специалист, но наверняка многофакторную аутентификацию он делать умеет.
В общем, опишите задачу поподробнее, если это деликатная информация, можно в личку.
Егор
-
Добрый вечер, Егор !
Да, верно. Данные из AD необходимо получить.
Идея такова, чтобы перед авторизацией php проверил наличие ID чата, принадлежность к определенной группе и т.д.
Схема, как я вижу следующая:
1. Сотрудник проходит авторизацию на портале
2. Портал отправляет запрос на OpenLDAP Proxy -> далее, идет запрос в Active Directory
3. Active Directory отвечает -> OpenLDAP Proxy
4. OpenLDAP Proxy (если логин/пароль верны)-> Запуск PHP скрипта
5. PHP Ищет атрибут "TelegramID" у пользователя в каталоге AD (Схему изменил в AD, атрибут есть. PHP обрабатывает прекрасно) и отправляет в чат телеграм сообщение-вопрос с кнопками Это вы ? "Это я" и "Нет, не я".
Если пользователь нажимает кнопку "Это я", соответственно выполняется привязка.. Если пользователь не чего не отвечает, то есть срабатывает тайм аут или же отвечает отрицательно - привязка не выполняется, происходит ошибка авторизации.
6. Далее уже работа Webhook telegram bot
На текущий момент, главная задача заставить OpenLDAP запускать скрипт, не выполняя привязку, пока скрипт не отработает.
Radius не подходит для таких порталов, как битрикс.
Рассматриваю Radius для VPN и сетевых устройств и прочего, что не поддерживает LDAP )
-
Пока не могу взять в толк, зачем Вам нужен OpenLDAP в качестве proxy? Если php-скрипт берёт что-то из AD, значит он, как минимум, подключается к AD и проходит там аутентификацию от имени какой-то учётки. А значит могут быть 2 пути:
1. Если атрибут TelegramID находится в LDAP-записи пользователя в AD, то логично в php-скрипте сразу проходить аутентификацию для подключения к AD от имени этого пользователя и извлекать атрибут. Если аутентификация при подключении к AD не выполнилась, значит был введён неверный пароль.
2. Если атрибут TelegramID находится в какой-то другой записи, к которой у пользователя нет прав доступа, и в php-скрипте подключение к AD происходит от некого "привилегированного" пользователя, то можно сделать 2 последовательных подключения к AD: сначала от имени пользователя, проверить, что аутентификацию с введённым паролем он проходит успешно (например, получить свою учётку из AD), а затем уже второй раз подключиться от "привилегированного" пользователя и выполнять то, что Вам нужно.
Надеюсь, не сильно запутанно объяснил. Не то, чтобы я против OpenLDAP-proxy, просто в любом случае это будет костыльно, скорее всего с дырами в безопасности, а в случае с database shell ещё и медленно.
К тому же, без установления привязки выяснить совпадение пары логина и пароля не получится в принципе: SimpleBind-аутентификация в LDAP основана как раз на привязке, то есть смог пользователь аутентифицироваться в каталоге, значит пароль был верный. В открытом виде пароли в каталоге не хранятся, в AD вообще невозможно прочитать атрибут с паролем.
-
Доброй ночи, Егор !
Под php я имею ввиду отдельный скрипт.
К порталам, на которых должна проходить авторизация, этот скрипт отношения не имеет
Скрипт php изначально написан для отправки сообщений в телеграм чат и расположен он на машинке с Open ldap proxy
Я понимаю, о чем Вы, но ковыряться в исходном коде порталов мы не можем.
Open ldap Proxy я рассматриваю в качестве узла в DMZ и в качестве сервиса второго фактора,
который бы смог после установления привязки, не авторизуя пользователя запустить еще один процесс проверки за счет запуска php скрипта.
Скрипту же нужно передать логин, под которым сотрудник пытается войти.
Был еще вариант заюзать модуль pw-radius и настроить запуск скрипта модулем exec на freeradius, но вот команда gcc -shared -I ../../../include -Wall -g -o pw-radius.so radius.c -lradius не срабатывает, информации по модулю pw-radius найти не удалось.
Отправил письмо в поддержку, пока молчат.
Ребята с чата подсказали, что можно рассмотреть slapd-sock.
Не могу пока понять, как я могу использовать slapd-sock или slapd-shell.
-
Здравствуйте! В рабочие дни нереально заниматься посторонними делами, попробую найти время на выходных.
Вопросы:
1. У Вас уже есть настроенный Openldap-proxy? Если да, пришлите конфигурацию.
2. php-скрипт принимает логин пользователя просто в качестве первого аргумента командной строки при запуске?
3. Что должен выдать php-скрипт при успешном и неуспешном завершении проверки?
Ребята с чата подсказали, что можно рассмотреть slapd-sock.
У ребят из чата есть рабочий пример настройки slapd на взаимодействие с сокетом и исходники сокета? Любопытно было бы посмотреть.
Егор
-
Добрый день, Егор !
Вопросы:
1. У Вас уже есть настроенный Openldap-proxy? Если да, пришлите конфигурацию.
cat /etc/ldap/ldap.conf
### Schema includes ###########################################################
include /etc/ldap/schema/core.schema
include /etc/ldap/schema/cosine.schema
include /etc/ldap/schema/nis.schema
include /etc/ldap/schema/inetorgperson.schema
include /etc/ldap/schema/microsoftattributetype.schema
include /etc/ldap/schema/microsoftattributetypestd.schema
#include /etc/ldap/schema/microsoftobjectclass.schema
## Module paths ##############################################################
modulepath /usr/lib/ldap
moduleload back_ldap.la
moduleload back_ldap
moduleload back_hdb.la
moduleload back_meta
moduleload rwm
moduleload pcache.la
moduleload memberof.la
moduleload rwm.la
moduleload dynlist.la
# Main settings ###############################################################
pidfile /var/run/slapd/slapd.pid
argsfile /var/run/slapd/slapd.args
### Database definition (Proxy to AD) #########################################
database ldap
readonly yes
protocol-version 3
rebind-as-user yes
uri "ldap://ldap.example.ru"
suffix "dc=example,dc=ru"
overlay rwm
rwm-map attribute uid sAMAccountName
rwm-map attribute mail proxyAddresses
rebind-as-user yes
### Logging ###################################################################
loglevel 9
2. php-скрипт принимает логин пользователя просто в качестве первого аргумента командной строки при запуске?
Ну да, идея была такова, что то вроде
php /var/www/html/2fa.php username или /var/www/html/2fa.sh username
<?php ?> или #!/bin/php
$username = $1;
...
3. Что должен выдать php-скрипт при успешном и неуспешном завершении проверки?
При успешном выполнении пользователь должен пройти авторизацию на портале, при неуспешном портал должен сообщить об ошибке авторизации
У ребят из чата есть рабочий пример настройки slapd на взаимодействие с сокетом и исходники сокета? Любопытно было бы посмотреть.
Задал им вопрос, пока молчат
В данный момент имею два тестовых скрипта, пока не нашел возможность запуска скрипта для дополнительной проверки авторизации писать оконченный вариант не стал.
Возможно кустарно, так как тему изучать начал относительно не давно.
Большинство кода взял из интернет
Скрипт проверки поиска в LDAP
<?php
header('Content-type: text/html; charset=utf-8');
extension_loaded('ldap');
//$ldap_user = "cn=ldap_view,ou=it отдел,ou=users,ou=general,dc=example,dc=ru";
$ldap_user = "ldap_view@example.ru";
$ldap_pass = "********";
$dn = "ou=general,dc=example,dc=ru";
$attrs = array("displayname", "telegramid", "mobile", "mail");
$filter = "samaccountname=ivan.petrov";
$ldapuri = "ldap://ХХХ.ХХХ.ХХХ.ХХХ";
$ldap_con=ldap_connect($ldapuri);
//ldap_set_option($ldap_con, LDAP_OPT_PROTOCOL_VERSION, 3)
//or die ("Could not set ldap protocol");
if ($ldap_con) {
$bind = ldap_bind($ldap_con, $ldap_user, $ldap_pass);
if ($bind) {
echo "LDAP-OK!\n$ldap_user";
//$search = ldap_search($ldap_con, $dn, $filter, $attrs)
$search = ldap_search($ldap_con, $dn, $filter)
or die ("ldap search failed");
$entries = ldap_get_entries($ldap_con, $search);
//print_r($entries);
if ($entries["count"] > 0) {
for ($i=0; $i<$entries["count"]; $i++) {
echo "\n\n";
echo "Сотрудник: ".$entries[$i]["displayname"][0]."\n";
echo "ID чата телеграмм: ".$entries[$i]["telegramid"][0]."\n";
echo "Phone: +".$entries[$i]["mobile"][0]."\n";
echo "Email: ".$entries[$i]["mail"][0]."\n";
}
} else {
echo "<p>No results found!</p>";
}
//ldap_unbind($ad);
} else {
echo "LDAP-NO!\n$ldap_user";
}
}
?>
Скрипт отправки сообщения
<?php
header('Content-Type: text/html; charset=utf-8');
// подключаемся к API
require_once("vendor/autoload.php");
$site_dir = dirname(dirname(__FILE__)).'/'; // корень сайта
$bot_token = 'ТОКЕН БОТА'; // токен вашего бота
$data = file_get_contents('php://input'); // весь ввод перенаправляем в $data
$data = json_decode($data, true); // декодируем json-закодированные-текстовые данные в PHP-массив
$date = date('d.m.Y H:i:s ');
$inline_button1 = array("text"=>"Да, это я","callback_data"=>"1");
$inline_button2 = array("text"=>"Нет, это не я","callback_data"=>'0');
$inline_keyboard = [[$inline_button1,$inline_button2]];
$keyboard=array("inline_keyboard"=>$inline_keyboard);
$replyMarkup = json_encode($keyboard);
file_put_contents(__DIR__ . '/telegram_users/users.txt', $date . print_r($data, true) . PHP_EOL, FILE_APPEND | LOCK_EX);
// Для отладки, добавим запись полученных декодированных данных в файл message.txt,
// который можно смотреть и понимать, что происходит при запросе к боту
// Позже, когда все будет работать закомментируйте эту строку:
// Основной код: получаем сообщение, что юзер отправил боту и
// заполняем переменные для дальнейшего использования
if (!empty($data['message']['text'])) {
$chat_id = $data['message']['from']['id'];
$user_name = $data['message']['from']['username'];
$first_name = $data['message']['from']['first_name'];
$last_name = $data['message']['from']['last_name'];
$text = trim($data['message']['text']);
$text_array = explode(" ", $text);
//file_put_contents($path.'/'.$data['message']['from']['id'].'.txt', $date . print_r($data, true) . PHP_EOL, FILE_APPEND | LOCK_EX);
//if ($text == '/help') {
$text_return = "Привет, $first_name $last_name. \nСегодня, $date была выполнена попытка подключения к ВПН. Это Вы ?";
message_to_telegram($bot_token, $chat_id, $text_return, $replyMarkup);
//}
// elseif ($text == '/about') {
// $text_return = "verysimple_bot:Я пример самого простого бота для телеграм, написанного на простом PHP";
// message_to_telegram($bot_token, $chat_id, $text_return, $replyMarkup);
//}
}
// функция отправки сообщени от бота в диалог с юзером
function message_to_telegram($bot_token, $chat_id, $text, $reply_markup = '')
{
$ch = curl_init();
$ch_post = [
CURLOPT_URL => 'https://api.telegram.org/bot' . $bot_token . '/sendMessage',
CURLOPT_POST => TRUE,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_TIMEOUT => 10,
CURLOPT_POSTFIELDS => [
'chat_id' => $chat_id,
'parse_mode' => 'HTML',
'text' => $text,
'reply_markup' => $reply_markup,
]
];
curl_setopt_array($ch, $ch_post);
curl_exec($ch);
}
?>
-
Здравствуйте! Опишу результаты проведённых на выходных экспериментов.
Давно не занимался OpenLDAP, так много нового =). Во первых, в современных версиях (2.5, 2.6) отказались от механизма slapd-shell, поэтому пришлось собрать OpenLDAP 2.4.59. Думаю, всё то же самое можно сделать и на slapd-sock, но, повторюсь, никто не знает как =) .
Во-вторых, пути к slapd, ldapadd, ldapsearch, файлам конфигурации, файлам схем, хранилищу БД, а также ip-адреса и порты в LDAP-URI я использовал нестандартные (для экспериментов от имени непривилегированного пользователя), будьте внимательны, не забудьте всё поменять и проверить. Если что, все файлы продублированы в архиве во вложении.
Собственно, что сделано. Поскольку у меня нет AD, то я запустил первый процесс slapd для его эмуляции на порту 9000. Файл конфигурации slapd_fake_ad.conf:
include /opt/openldap-2.4-lastest/etc/openldap/schema/core.schema
include /opt/openldap-2.4-lastest/etc/openldap/schema/cosine.schema
include /opt/openldap-2.4-lastest/etc/openldap/schema/nis.schema
include /opt/openldap-2.4-lastest/etc/openldap/schema/inetorgperson.schema
include /data/openldap-experiments/2022-11-05_slapd-shell/additional_attrs.schema
modulepath /opt/openldap-2.4-lastest/libexec/openldap/
moduleload back_mdb.so
database mdb
directory /data/openldap-experiments/2022-11-05_slapd-shell/db/
suffix cn=users,dc=mydomain,dc=com
rootDN cn=Administrator,cn=users,dc=mydomain,dc=com
rootPW secret123
access to attrs=userPassword
by self write
by anonymous auth
by * none
access to *
by self write
by * none
Пришлось добавить несколько атрибутов в отдельном файле схемы additional_attrs.schema:
attributetype ( 1.2.840.113556.1.4.221
NAME 'sAMAccountName'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
SINGLE-VALUE )
attributetype ( 1.2.840.113556.1.2.210
NAME 'proxyAddresses'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
attributetype ( 1.2.840.113556.1.4.284
NAME 'telegramId'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX '1.3.6.1.4.1.1466.115.121.1.15'
SINGLE-VALUE )
Команда для запуска slapd:
/opt/openldap-2.4-lastest/libexec/slapd -u egor -g egor -h ldap://127.1:9000 -f /data/openldap-experiments/2022-11-05_slapd-shell/slapd_fake_ad.conf -d 256
Проинициализировал каталог таким файлом init.ldif:
dn: cn=Users,dc=mydomain,dc=com
objectClass: applicationProcess
cn: Users
dn: cn=Иванов Иван,cn=Users,dc=mydomain,dc=com
objectClass: inetOrgPerson
objectClass: extensibleObject
cn: Иванов Иван
sn: Иванов
displayName: Иванов Иван
userPassword: ivanovPassword
mobile: +79111111111
mail: ivan.ivanov@mydomain.com
saMAccountName: ivan.ivanov
telegramId: ivan.ivanov.telegramId
dn: cn=Петров Петр,cn=Users,dc=mydomain,dc=com
objectClass: inetOrgPerson
objectClass: extensibleObject
cn: Петров Петр
sn: Петров
displayName: Петров Петр
userPassword: petrovPassword
mobile: +79222222222
mail: petr.petrov@mydomain.com
saMAccountName: petr.petrov
telegramId: petr.petrov.telegramId
dn: cn=Сидоров Сидор,cn=Users,dc=mydomain,dc=com
objectClass: inetOrgPerson
objectClass: extensibleObject
cn: Сидоров Сидор
sn: Сидоров
displayName: Сидоров Сидор
userPassword: sidorovPassword
mobile: +79333333333
mail: sidor.sidorov@mydomain.com
saMAccountName: sidor.sidorov
telegramId: sidor.sidorov.telegramId
Команда для инициализации каталога:
/opt/openldap-2.4-lastest/bin/ldapadd -H ldap://127.1:9000 -xD cn=Administrator,cn=users,dc=mydomain,dc=com -w secret123 -f /data/openldap-experiments/2022-11-05_slapd-shell/init.ldif
В Вашем случае это всё не нужно, поскольку у Вас есть AD.
Далее идут файлы настройки slapd в режиме прокси на основе slapd-shell slapd_shell_proxy.conf:
include /opt/openldap-2.4-lastest/etc/openldap/schema/core.schema
include /opt/openldap-2.4-lastest/etc/openldap/schema/cosine.schema
include /opt/openldap-2.4-lastest/etc/openldap/schema/nis.schema
include /opt/openldap-2.4-lastest/etc/openldap/schema/inetorgperson.schema
include /data/openldap-experiments/2022-11-05_slapd-shell/additional_attrs.schema
modulepath /opt/openldap-2.4-lastest/libexec/openldap/
moduleload back_shell.so
database shell
suffix cn=users,dc=mydomain,dc=com
bind /data/openldap-experiments/2022-11-05_slapd-shell/bind.sh
search /data/openldap-experiments/2022-11-05_slapd-shell/search.sh
Не забудьте скорректировать суффикс под свой AD. Скрипт для операции bind:
#!/bin/bash
while [ 1 ]; do
read TAG VALUE
if [ $? -ne 0 ]; then
break
fi
case "$TAG" in
dn:)
FIRST_QUERY_FILTER=`echo $VALUE | cut -d ',' -f 1`
USER_ACCOUNT_NAME=`echo $FIRST_QUERY_FILTER | cut -d '=' -f 2`
;;
cred:)
CRED=$VALUE
;;
# include other parameters here
esac
done
# Получение реального DN пользователя с аутентификацией от RootDN
FIRST_QUERY_RESULT=`/opt/openldap-2.4-lastest/bin/ldapsearch -x -LLL -H ldap://127.0.0.1:9000 -b cn=users,dc=mydomain,dc=com -D cn=Administrator,cn=users,dc=mydomain,dc=com -w secret123 $FIRST_QUERY_FILTER 1.1`
if [[ -z $FIRST_QUERY_RESULT ]]; then
echo "RESULT"
echo "code: 49"
echo "info: Unknown user: $USER_ACCOUNT_NAME"
exit 49
fi
# Получение любого атрибута от DN пользователя
USER_DN=`echo $FIRST_QUERY_RESULT | head -n 1 | perl -MMIME::Base64 -wpe 's/^([^:]+):: (.+)$/"$1: " . decode_base64($2)/e'`
USER_DN=${USER_DN:3}
SECOND_QUERY_RESULT=`/opt/openldap-2.4-lastest/bin/ldapsearch -x -LLL -H ldap://127.0.0.1:9000 -b "$USER_DN" -D "$USER_DN" -w $CRED cn`
if [[ -z $SECOND_QUERY_RESULT ]]; then
echo "RESULT"
echo "code: 49"
echo "info: Wrong password for $USER_ACCOUNT_NAME"
exit 49
fi
# Второй фактор
SECOND_FACTOR_TEST=`php /data/openldap-experiments/2022-11-05_slapd-shell/2fa.php $USER_ACCOUNT_NAME`
if [[ $SECOND_FACTOR_TEST != "OK" ]]; then
echo "RESULT"
echo "code: 49"
echo "info: Second factor auth failed for $USER_ACCOUNT_NAME"
exit 49
fi
# Успешное завершение
echo "RESULT"
echo "code: 0"
exit 0
Как Вы понимаете, это главная часть. Поскольку в AD мудрёная схема именования записей, приходится делать два запроса: один для извлечения DN записи пользователя (от имени администратора), а второй для собственно прохождения аутентификации от имени самого пользователя. Если SimpleBIND-аутентификация пройдена, выполняется проверка второго фактора вызовом php-скрипта.
php-скрипт-заглушка для эмуляции второго фактора 2fa.php:
<?php
$username = $argv[1];
if($username === 'ivan.ivanov') {
echo 'OK';
} else if($username === 'petr.petrov') {
echo 'ER';
} else {
sleep(30);
echo 'ER';
}
Для ivan.ivanov всегда возвращается OK, для petr.petrov -- ER, для остальных эмулируется таймаут. Надеюсь, принцип понятен.
Скрипт-заглушка для операции search:
#!/bin/bash
echo "RESULT"
echo "code: 0"
exit 0
Он просто всегда возвращает успех.
Команда для запуска slapd в режиме shell-прокси на порту 9001:
/opt/openldap-2.4-lastest/libexec/slapd -u egor -g egor -h ldap://127.1:9001 -f /data/openldap-experiments/2022-11-05_slapd-shell/slapd_shell_proxy.conf -d 256
Ну а дальше проверяем аутентификацию. Тест 1, неверное имя пользователя:
$ /opt/openldap-2.4-lastest/bin/ldapsearch -H ldap://127.1:9001 -LLLxb cn=Users,dc=mydomain,dc=com -D sAMAccountName=ivan.ivanov1,cn=Users,dc=mydomain,dc=com -w ivanovPassword
ldap_bind: Invalid credentials (49)
additional info: Unknown user: ivan.ivanov1
Тест 2, неверный пароль пользователя:
$ /opt/openldap-2.4-lastest/bin/ldapsearch -H ldap://127.1:9001 -LLLxb cn=Users,dc=mydomain,dc=com -D sAMAccountName=ivan.ivanov,cn=Users,dc=mydomain,dc=com -w ivanovPassword1
ldap_bind: Invalid credentials (49)
additional info: Wrong password for ivan.ivanov
Тест 3, правильные имя пользователя и пароль, успешно пройденный второй фактор:
$ /opt/openldap-2.4-lastest/bin/ldapsearch -H ldap://127.1:9001 -LLLxb cn=Users,dc=mydomain,dc=com -D sAMAccountName=ivan.ivanov,cn=Users,dc=mydomain,dc=com -w ivanovPassword
Тест 4, пользователь не прошёл второй фактор:
$ /opt/openldap-2.4-lastest/bin/ldapsearch -H ldap://127.1:9001 -LLLxb cn=Users,dc=mydomain,dc=com -D sAMAccountName=petr.petrov,cn=Users,dc=mydomain,dc=com -w petrovPassword
ldap_bind: Invalid credentials (49)
additional info: Second factor auth failed for petr.petrov
Тест 5, пользователь не прошёл второй фактор по таймауту:
$ LDAPTIMEOUT=10 /opt/openldap-2.4-lastest/bin/ldapsearch -H ldap://127.1:9001 -LLLxb cn=Users,dc=mydomain,dc=com -D sAMAccountName=sidor.sidorov,cn=Users,dc=mydomain,dc=com -w sidorovPassword
ldap_result: Timed out (-5)
Надеюсь, идея понятна, сможете подкорректировать и развить. Прокси, который Вы настраивали (slapd-ldap), не понадобился.
Егор
-
Да, поскольку slapd я запускаю в режиме дебага (-d 256), то использую 3 терминала: в одном крутится slapd эмуляции AD, во втором slapd-shell-proxy, а в третьем, собственно, выполняются пользовательские команды ldapadd, ldapsearch. Надеюсь, понятно.
Егор
-
Всем привет !
Егор, спасибо Вам большое, очень помогли ) !
В общем то, авторизация работает как и задумывалось, но не без проблем конечно )
В данный момент имею проблему авторизации учетных записей порталов Bitrix и pfsense.
При тестировании выходила ошибка "ldap_search_ext: Bad search filter (-7)".
Если я подключаю портал на порт 389 openldap proxy, минуя 9000 порт с backend Shell, то авторизация проходит нормально
Для работы авторизации на портале Bitrix был дописан файл search.sh и добавлены условия, при которых удаляются лишние символы из поисковой строки, так как в строке filter было замечены лишние символы, которые генерирует портал. С pfsense пока что не удалось решить. На первый взгляд, данные в строке filter указаны корректно.
#!/bin/bash
while [ 1 ]; do
read TAG VALUE
if [ $? -ne 0 ]; then
break
fi
case "$TAG" in
base:)
BASE=$VALUE
;;
filter:)
FILTER=$VALUE
;;
#include other parameters here
esac
done
if [[ "$FILTER " == *"sAMAccountName"* ]]; then
FILTER_NEW=`echo $FILTER | sed 's/(&(?/(/g' | sed 's/user))/user)/g' | sed 's/?//g'`
echo "It's there - sAMAccountName !!!" $FILTER_NEW >> "/home/test/FILTER.txt"
elif [[ "$FILTER " == *"(|(ou=*)(cn=users))"* ]]; then
FILTER_NEW=$FILTER
echo "It's there - (|(ou=*)(cn=users)) !!!" $FILTER_NEW >> "/home/test/FILTER.txt"
else
FILTER_NEW=`echo $FILTER | sed 's/(&(?/(/g' | sed 's/))/)/g' | sed 's/?//g'`
echo "It's NOT there - sAMAccountName !!!" $FILTER_NEW >> "/home/test/FILTER.txt"
fi
# Search
/usr/bin/ldapsearch -x -LLL -H ldap://127.0.0.1 -b dc=example,dc=ru -D "cn=ldap_view,ou=IT отдел,ou=Users,ou=General,dc=example,dc=ru" -w "Secret123" $FILTER_NEW
echo "RESULT"
echo "code: 0"
exit 0
Файл bind.sh почти не трогал, единственное сделал вывод в файл всех переменных, чтобы понимать, что внутри. Переменную $FIRST_QUERY_FILTER заключил в кавычки при задании переменной FIRST_QUERY_RESULT, так как в случае портала Bitrix передаются данные с пробелом. В моем случае учетная запись "cn=Тест Тестов 1" и выполнение скрипта рушится.
#!/bin/bash
while [ 1 ]; do
read TAG VALUE
if [ $? -ne 0 ]; then
break
fi
case "$TAG" in
dn:)
FIRST_QUERY_FILTER=`echo $VALUE | cut -d ',' -f 1`
USER_ACCOUNT_NAME=`echo $FIRST_QUERY_FILTER | cut -d '=' -f 2`
;;
cred:)
CRED=$VALUE
;;
# include other parameters here
esac
done
# START Тест - вывод значения переменных в файл
echo "FIRST_QUERY_FILTER - $FIRST_QUERY_FILTER" | perl -MMIME::Base64 -MEncode=decode -n -00 -e 's/\n +//g;s/(?<=:: )(\S+)/decode("UTF-8",decode_base64($1))/eg;print' >> /home/test/2fa.php.txt
echo "USER_ACCOUNT_NAME - $USER_ACCOUNT_NAME" | perl -MMIME::Base64 -MEncode=decode -n -00 -e 's/\n +//g;s/(?<=:: )(\S+)/decode("UTF-8",decode_base64($1))/eg;print' >> /home/test/2fa.php.txt
echo "FIRST_QUERY_FILTER_NEW_NEW - $FIRST_QUERY_FILTER_NEW_NEW" | perl -MMIME::Base64 -MEncode=decode -n -00 -e 's/\n +//g;s/(?<=:: )(\S+)/decode("UTF-8",decode_base64($1))/eg;print' >> /home/test/2fa.php.txt
# END Тест - вывод значения переменных в файл
# Получение реального DN пользователя с аутентификацией от RootDN
FIRST_QUERY_RESULT=`/usr/bin/ldapsearch -x -LLL -H ldap://127.0.0.1 -b ou=General,dc=example,dc=ru -D "cn=ldap_view,ou=IT отдел,ou=Users,ou=General,dc=example,dc=ru" -w Secret123 "$FIRST_QUERY_FILTER" 1.1`
# START Тест - вывод значения переменных в файл
echo "FIRST_QUERY_RESULT - $FIRST_QUERY_RESULT" | perl -MMIME::Base64 -MEncode=decode -n -00 -e 's/\n +//g;s/(?<=:: )(\S+)/decode("UTF-8",decode_base64($1))/eg;print' >> /home/test/2fa.php.txt
# END Тест - вывод значения переменных в файл
if [[ -z $FIRST_QUERY_RESULT ]]; then
echo "RESULT"
echo "code: 49"
echo "info: Unknown user: $USER_ACCOUNT_NAME"
exit 49
fi
# Получение любого атрибута от DN пользователя
USER_DN=`echo $FIRST_QUERY_RESULT | head -n 1 | perl -MMIME::Base64 -wpe 's/^([^:]+):: (.+)$/"$1: " . decode_base64($2)/e'`
USER_DN=${USER_DN:3}
SECOND_QUERY_RESULT=`/usr/bin/ldapsearch -x -LLL -H ldap://127.0.0.1 -b "$USER_DN" -D "$USER_DN" -w $CRED cn`
#SECOND_QUERY_RESULT=`/usr/bin/ldapsearch -x -LLL -H ldap://127.0.0.1 -b ou=General,dc=example,dc=ru -D "$USER_DN" -w $CRED cn`
# START Тест - вывод значения переменных в файл
echo "USER_DN - $USER_DN\n" | perl -MMIME::Base64 -MEncode=decode -n -00 -e 's/\n +//g;s/(?<=:: )(\S+)/decode("UTF-8",decode_base64($1))/eg;print' >> /home/test/2fa.php.txt
echo "SECOND_QUERY_RESULT - $SECOND_QUERY_RESULT" | perl -MMIME::Base64 -MEncode=decode -n -00 -e 's/\n +//g;s/(?<=:: )(\S+)/decode("UTF-8",decode_base64($1))/eg;print' >> /home/test/2fa.php.txt
# END Тест - вывод значения переменных в файл
if [[ -z $SECOND_QUERY_RESULT ]]; then
echo "RESULT"
echo "code: 49"
echo "info: Wrong password for $USER_ACCOUNT_NAME"
exit 49
fi
VARUSER_DN=`echo $USER_DN | perl -MMIME::Base64 -MEncode=decode -n -00 -e 's/\n +//g;s/(?<=:: )(\S+)/decode("UTF-8",decode_base64($1))/eg;print'`
# Второй фактор
SECOND_FACTOR_TEST=`/usr/bin/php /var/www/html/php_ldap_test_2.php "$USER_ACCOUNT_NAME" "$VARUSER_DN"`
# START Тест - вывод значения переменных в файл
echo "SECOND_FACTOR_TEST - $SECOND_FACTOR_TEST" | perl -MMIME::Base64 -MEncode=decode -n -00 -e 's/\n +//g;s/(?<=:: )(\S+)/decode("UTF-8",decode_base64($1))/eg;print' >> /home/test/2fa.php.txt
# END Тест - вывод значения переменных в файл
echo "*****************************************************" >> /home/test/2fa.php.txt
if [[ $SECOND_FACTOR_TEST != "OK" ]]; then
echo "RESULT"
echo "code: 49"
echo "info: Second factor auth failed for $USER_ACCOUNT_NAME"
exit 49
fi
# Успешное завершение
echo "RESULT"
echo "code: 0"
exit 0
Вот в таком виде пока что собран файл php
<?php
header('Content-type: text/html; charset=utf-8');
require("/var/www/html/inc/ldap_functions.php");
$ldap_user = "cn=ldap_view,ou=it отдел,ou=users,ou=general,dc=example,dc=ru";
$user = $argv[1];
$userdn = $argv[2];
$password = 'Secret123';
$ldapuri = 'ldap://192.168.210.254';
$basedn = 'ou=General,dc=example,dc=ru';
//$filter = "sAMAccountName={$user}";
$filter = "cn=".$user;
$group_2fa = 'a-s-2fa_telegram';
$group_2fa_service = 'a-s-2fa_services';
$startTime = time();
$timeout = 61;
file_put_contents("/home/test/var_user.txt", $user, FILE_APPEND | LOCK_EX);
file_put_contents("/home/test/var_filter.txt", $filter, FILE_APPEND | LOCK_EX);
// Подключение к LDAP-серверу
$ad = ldap_connect($ldapuri) or die('Could not connect to LDAP server.');
// Выбор протокола подключения
ldap_set_option($ad, LDAP_OPT_PROTOCOL_VERSION, 3) or die ("Could not set ldap protocol");
//ldap_set_option($ad, LDAP_OPT_REFERRALS, 0);
// Прохождение авторизации в каталоге от сервисной учетной записи
@ldap_bind($ad,$ldap_user,$password) or die('Could not bind to AD.');
// Поиск значение TelegramID у пользователя
$search = ldap_search($ad, $basedn, $filter) or die ("ldap search failed");
// Создание массива с найденными данными
$entr = ldap_get_entries($ad, $search);
// Определение полного пути для пользователя
//$userdn = getDN($ad, $user, $basedn);
file_put_contents("/home/test/var_userdn.txt", $userdn, FILE_APPEND | LOCK_EX);
// Авторизовать сервисные учетные записи без требования второго фактора
if (checkGroupEx($ad, $userdn, getDN($ad, $group_2fa_service, $basedn)))
{
echo "OK";
// Проверка вхождения пользователя в группу проверки второго фактора
} else if (checkGroupEx($ad, $userdn, getDN($ad, $group_2fa, $basedn))) {
if ($entr["count"] > 0) {
$out_array = print_r($entr,true);
file_put_contents("/home/test/var_entr.txt", $out_array, FILE_APPEND | LOCK_EX);
// Задание переменной со значением TelegramID
$user_chat_id = $entr[0]["telegramid"][0];
$FirstName = $entr[0]["givenname"][0];
$LastName = $entr[0]["sn"][0];
//$teluser = "test";
file_put_contents("/home/test/var_FirstName.txt", $FirstName, FILE_APPEND | LOCK_EX);
file_put_contents("/home/test/var_LastName.txt", $LastName, FILE_APPEND | LOCK_EX);
file_put_contents("/home/test/var_user_chat_id.txt", $user_chat_id, FILE_APPEND | LOCK_EX);
if ($user_chat_id != NULL) {
$callback_data_file = "/home/test/{$user_chat_id}.txt";
if (file_exists($callback_data_file)) {unlink($callback_data_file);}
// Отправка сообщения в телеграм чат
exec("/usr/bin/php -f /var/www/html/SendMessage_authbot.php {$FirstName} {$LastName} {$user_chat_id} > /home/test/exec_php.txt 2>&1 &");
while (true) {
// Обработка ответа в чате
$callback_data = file_get_contents($callback_data_file);
if (($callback_data != NULL) or (time() > $startTime + $timeout)) {
break;
}
}
if ($callback_data != "YES") {echo "ERR";} else {echo "OK";}
}
}
} else {echo "ER";}
//unlink("/home/test/{$user_chat_id}.txt");
ldap_unbind($ad);
?>
Позднее, как приведу скрипты в нормальный вид, могу скинуть сюда. Может кому то пригодится.
Конфиг ldap.conf, работает на порту 389. На нем настроен Proxy в MS AD
cat /etc/ldap/ldap.conf
### Schema includes ###########################################################
include /etc/ldap/schema/core.schema
include /etc/ldap/schema/cosine.schema
include /etc/ldap/schema/nis.schema
include /etc/ldap/schema/inetorgperson.schema
include /etc/ldap/schema/microsoftattributetype.schema
include /etc/ldap/schema/microsoftattributetypestd.schema
#include /etc/ldap/schema/microsoftobjectclass.schema
#include /etc/ldap/schema/additional_attrs.schema
## Module paths ##############################################################
modulepath /usr/lib/ldap
moduleload back_ldap.la
moduleload rwm.la
# Main settings ###############################################################
pidfile /var/run/slapd/slapd.pid
argsfile /var/run/slapd/slapd.args
### Database definition (Proxy to AD) #########################################
database ldap
readonly yes
protocol-version 3
rebind-as-user yes
uri "ldap://ldap.example.ru"
suffix "dc=example,dc=ru"
overlay rwm
rwm-map attribute uid sAMAccountName
rwm-map attribute mail proxyAddresses
rwm-map objectClass posixGroup group
rwm-map objectClass posixAccount person
rwm-map objectClass memberUid member
rebind-as-user yes
#rootDN cn=ldap_view,ou=IT отдел,ou=Users,ou=General,dc=example,dc=ru
#rootPW Secret123
access to attrs=userPassword
by self write
by anonymous auth
by * none
access to *
by self write
by * none
### Logging ###################################################################
loglevel 256
Конфиг ldap_2fa.cong, работает на порту 9000
cat /etc/ldap/ldap_2fa.conf
include /etc/ldap/schema/core.schema
include /etc/ldap/schema/cosine.schema
include /etc/ldap/schema/nis.schema
include /etc/ldap/schema/inetorgperson.schema
include /etc/ldap/schema/additional_attrs.schema
modulepath /usr/lib/ldap
moduleload back_shell.so
database shell
suffix "dc=example,dc=ru"
bind /etc/ldap/shell/bind.sh
search /etc/ldap/shell/search.sh
Егор, атрибуты написанные Вами внес в файлик /etc/ldap/schema/microsoftattributetype.schema.
Используемые схемы прикрепляю во вложении.
Пока не понял/не дошли руки понять, для чего используется следующее:
rootDN cn=ldap_view,ou=IT отдел,ou=Users,ou=General,dc=example,dc=ru
rootPW Secret123
Егор, возможно ли дописать схемы или правила, чтобы запросы прилетающие на порт :9000 корректно и одинаково обрабатывались в файлике search.sh для всех используемых порталов ?
Следующим сообщением выложу debug сообщения
Спасибо !
-
Егор, во вложении debug сообщения для двух порталов
В случае с pfsense я могу из админки просмотреть каталог Active Directory, но вот пройти тест авторизации учетки не могу. Вероятно, не срабатывает filter на этапе проверки принадлежности учетной записи группе a-s-ovpn_users
-
Здравствуйте!
Наличие вопросительных знаков в фильтрах -- признак того, что OpenLDAP не может найти что-то (атрибут, объектный класс) в своей схеме данных. Нужно прописать в схему данных все недостающие атрибуты и объектные классы, тогда не нужно будет городить костыли с sed.
Наличие в логах кучи мусорных строк типа
UNKNOWN attributeDescription "PRIMARYGROUPID" inserted.
говорит о том же. Если Вам для аутентификации не нужны все эти атрибуты (подозреваю, что это так), то нужно перестроить LDAP-запрос, чтобы он их не просил у LDAP-сервера.
Я никак не могу взять в толк, зачем Вы упорно используете прокси на slapd-ldap? У Вас же и так есть AD. Получается, что между клиентом (Bitrix) и источником данных (AD) у Вас две прослойки, и обе -- сплошные костыли, а значит в два раза больше источников потенциальных ошибок. Предлагаю совсем отказать от прокси на slapd-ldap, отладить аутентификацию Bitrix сначала без второго фактора непосредственно в AD, а потом уже цеплять к AD прокси на slapd-shell для реализации второго фактора.
Настраивать Bitrix на LDAP-аутентификацию мне не приходилось. Если пришлёте настройки Bitrix в части, касающейся LDAP, посмотрю, постараюсь что-то подсказать.
По поводу rootDN и rootPw -- это учётка с правами суперпользователя на уровне каталога (то есть того фрагмента настроек, которые следуют за директивой database и корень которого определяется директивой suffix). В этом каталоге у суперпользователя полные права, все накладываемые ограничения (явные и дефолтные), в том числе ACL, лимиты и прочее, игнорируются.
Егор