Русский
Русский
English
Статистика
Реклама

Из песочницы Двухфакторая аутентификация VPNMikrotik просто и масштабируемо

Здравствуйте!

На написание данной статьи меня побудило прочтение аналогичного содержания статьи пользователя nkusnetsov. По количеству просмотров видно, что сообществу интересна данная тема.

Поэтому я решил поделиться с вами собственным решением, которое было ранее реализовано мной и в базовом виде обладает:

  • Низким уровнем вхождения и простотой кода (для понимания/отладки другим сотрудником)
  • Простые скрипты ROS не создают никакой нагрузки и работают даже на hAP Lite
  • Масштабируемость возможность подключения большого количества VPN-шлюзов с целью снижения нагрузки или географического распределения
  • Возможность использования Mikrotik CHR в качестве VPN-сервера
  • 1хN 1 SMS-шлюз на неограниченное количество роутеров с возможностью расширения при росте нагрузки
  • Возможность привязки отдельного роутера к конкретному модему (для чего? об этом позже)
  • Использование всего одного php скрипта на удаленном сервере
  • Не важно какое устройство инициировало VPN-соединение, авторизация по ссылке из SMS

При несложной доработке кода:

  • Возможность вести log авторизаций сотрудников на сервере (возможность реализована в расширенной версии, если есть интерес выложу)
  • Увеличить отказоустойчивость и снижение нагрузки системы путем отправки SMS рандомно с нескольких модемов


Постановка задачи и решение


Задача 1


В процессе разработки данного решения была поставлена задача минимизировать количество usb-модемов уменьшив стоимость владения, упростить администрирование, локализовать модемы в одном месте и тем самым улучшить ремонтопригодность системы.

Было решено использовать один роутер установленный на надежном канале как базовый SMS-шлюз. Единственное я не знал какова будет нагрузка на систему, справятся модемы и не заблокирует ли оператор. Поэтому я заложил возможность подключения нескольких модемов и дальнейшего разбиения системы на группы. Причем дополнительные модемы могут быть подключены к базовому шлюзу через простой usb-hub. Но для полноценного функционирования, в минимальном функционале, нужен один модем в не зависимости от количества роутеров.

Задача 2


Предусмотреть возможность использования локальных сим-карт в зоне установки роутера.
Пример: широкая филиальная сеть с несколькими магазинами в Казахстане. Отправка sms-сообщения из РФ будет стоить достаточно дорого. Данное решение позволяет сотрудникам из РК получать sms с локального номера.

Но в процессе размышлений оказалось, что решение уже было найдено и реализовано в Задаче 1.

Задача 3


Авторизация туннеля с мобильного устройства без необходимости физически находиться на авторизуемом устройстве.

Цель возможность авторизовывать не только пользовательские туннели, но и любые vpn-соединения: Mikrotik->Mikrotik, Сервер->Mikrotik и т.д При этом пользователю, ответственному за данные туннели, необходимо просто перейти по ссылке из SMS сообщения, в которой также отображается какой туннель хочет авторизоваться.

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

Решение данной задачи уже однозначно подразумевало использование RouterOS API (или SSH). Победу одержало API, как наиболее простой вариант.

Логика


  1. Пользователь авторизуется с заранее добавленным логином и паролем
  2. VPN-шлюз заносит его ip в адресный лист ожидания и отправляет POST запрос на сервер
  3. Сервер получает данные, проверяет, формирует код-авторизации и отправляет на контактный номер SMS вида: To autorize user 79001112233 connection open http_://synome.ru/?ruid=vrG7yYMbZ6&auth=YU6zc
  4. Пользователь переходит по ссылке с любого устройства
  5. Сервер делает запрос на роутер, проверяет наличие в адрес-листе записи с кодом авторизации в комментарии. Если запись найдена, удаляет ее, а пользователю отображает страницу удачной авторизации

Во всех остальных случаях, если что-то не прошло проверку пользователь получит Request error.

Настройка и код


Теперь перейдем от идейной части к настройке Mikrotik, коду и их описанию

Добавляем на Mikrotik:


Firewall


Обязательно создаем список разрешенных ресурсов, среди которых: собственный адрес MT, любой публичный DNS, адрес сервера на котором будет происходить авторизация
/ip firewall address-listadd address=10.10.0.1 list=Allow-listadd address=8.8.8.8 list=Allow-listadd address=synome.ru list=Allow-list


Добавляем правило блокирующее трафик VPN-пользователей из списка ожидания на любой хост кроме разрешенных
/ip firewall rawadd action=drop chain=prerouting dst-address-list=!Allow-list src-address-list=VPN-blocked disabled=no


PPP Profile


Создаем профиль в котором устанавливаем idle-таймаут соединения. Время должно быть меньше чем указанное в следующем скрипте On Up, в противном случае, если авторизация не была произведена, то после удаления списка из address-list пользователь получит доступ на время равное разнице (idle-таймаут минус address-list timeout)

Добавляем профиль
/ppp profileadd dns-server=10.10.0.1 idle-timeout=59m local-address=10.10.1.100 name=2F-VPN use-compression=no use-encryption=no use-mpls=no


Далее на последней вкладке Script добавляем:

On Up
:global pass "19RuOU89";:global ruid "vrG7yYMbZ6";:local userip [/ppp active get [find name=$user] address];# if phone number stored in comment#:local userphone [/ppp secret get [find name=$user] comment];# if phone number = username:local userphone $user;:local authkey [/tool fetch http-method=post http-data="ruid=$ruid&pass=$pass&tel=$userphone" url="http://personeltest.ru/away/synome.ru/" mode=http as-value output=user];/ip firewall address-list remove [find address=$userip];/ip firewall address-list add address=$userip list=VPN-blocked timeout=1h comment=($authkey->"data");:log info message="User connect:";:log info message=$userphone;:log info message=$userip;:log info message=($authkey->"data");


On Down
:local userip [/ppp secret get [find name=$user] remote-address];/ip firewall address-list remove [find address=$userip];:log info message="User disconnect:";:log info message=$user;:log info message=$userip;


Смысл скриптов

При подключении: в самом начале мы задаем логин и пароль роутера, которые будут проверяться на сервере. При авторизации пользователя получаем его номер телефона (может быть в имени или в комментарии) и локальный ip-адрес. Отправляем POST-запрос на сервер и получаем в ответе код авторизации. Добавляем ip-адрес в address-list VPN-blocked с кодом авторизации в комментарии и тайм-аутом на 1 минуту больше чем в профиле. Выводим все в лог.

При отключении: получаем ip-адрес пользователя, находим его в address-list и удаляем. Все выводим в лог.


PPP Secrets


Добавляем пользователя
/ppp secretadd comment="70001112233" name=70001112233 password=testuser profile=2F-VPN remote-address=10.10.1.100


Номер телефона можно указать прямо в name, но если хотим иметь возможность задавать один номер на несколько аккаунтов (для авторизации нескольких туннелей), то номер указываем в комментарии, при этом в скрипте On Up нужно изменить закомментированность строк

Изменение (вторую открываем, четвертую закрываем)
# if phone number stored in comment:local userphone [/ppp secret get [find name=$user] comment];# if phone number = username#:local userphone $user;


Ну и самое главное включаем PPTP или L2TP сервер.

На этом с Mikrotik работа закончена.

Серверная часть на PHP


Ниже приведен код. Он достаточно подробно комментирован, поэтому не буду писать лишний текст. Самое главное заменить данные в $host и $ruid_data на свои.

index.php
<?php// ------------------------------------------------------------------------------//  Copyright (с) 2020//  Author: Dmitri Agababaev, d.agababaev@duncat.net////  Copyright by authors for used RouterOS PHP API class in the source code files////  Redistributions and use of source code, with or without modification, are//  permitted that retain the above copyright notice// ------------------------------------------------------------------------------require_once('routeros_api.class.php');// Адрес по которому доступен данный скрипт$host = 'http://synome.ru/';// МАССИВ ДАННХ ВСЕХ РОУТЕРОВ, А ТАКЖЕ РОУТЕРА ЯВЛЯЮЩЕГОСЯ SMS-ШЛЮЗОМ$ruid_data = array(    // роутеры учавствующие в авторизации    // пароль в md5 , глобальный ip-адрес, логин входа на роутер, пароль, SMS-шлюз через который происходит отправка SMS    'vrG7yYMbZ6' => array('mdpass' => '5568ba82f332494d9ff8754b51e7b28a', 'ip' => '10.10.0.1', 'login' => 'user_vpn', 'password' => 'kji&@11az', 'smsgw' => 'SMS_gw1'),    // SMS-шлюзы    // ip-адрес шлюза (глобальный или локальный если в одной сети с сервером), логин, пароль, порт USB-модема, канал USB-модема    'SMS_gw1' => array('ip' => '172.16.1.3', 'login' => 'sms2F', 'password' => 'skIU8w!0', 'port' => 'usb1', 'channel' => '0'));// ВХОДНЕ ПРОВЕРКИ ЗАПРОСОВif (!$_REQUEST) die('Request error'); // если запроса нет  сбросif (!$_REQUEST['ruid']) die('Request error'); // если не указан ruid - сбросif (!array_key_exists($_REQUEST['ruid'], $ruid_data)) die('Request error'); // если роутер не существует  сбросif ($_REQUEST['auth']) autorize(); // если запрос на авторизацию, то пускаем без пароля и проверяем авторизациюif (!ruid_auth()) die('Request error'); // проверяем пароль роутера для отправки SMSif ($_REQUEST['tel']) send_authcode(); // если задан номер телефона, отправляем SMS// ПРОВЕРКА НА НАЛИЧИЕ РОУТЕРА В СПИСКЕ РАЗРЕШЕННХ и пароля авторизацииfunction ruid_auth() {  global $ruid_data;  if (!$_REQUEST['pass']) return false; // если пароль не задан  сброс  // проверяем md5-хэш пароля  if (md5($_REQUEST['pass']) == $ruid_data[$_REQUEST['ruid']]['mdpass']) return true;  return false;}// ФУНКЦИЯ ОТПРАВКИ ССЛКИ С КОДОМ АВТОРИЗАЦИИ ЧЕРЕЗ ROS APIfunction send_authcode() {  global $ruid_data;  global $host;  $sms_gw = $ruid_data[$_REQUEST['ruid']]['smsgw']; // данные sms-шлюза  // генерируем код авторизации  $auth_code = substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ123456789'), 0, 5);  // подключаем класс  $API = new RouterosAPI();  // Формируем сообщение отправляемое пользователю  только eng или транслит  $message = 'To autorize user '.$_REQUEST['tel'].' connection open  '.$host.'?ruid='.$_REQUEST['ruid'].'&auth='.$auth_code;  // если подключились отправляем SMS  if ($API->connect($ruid_data[$sms_gw]['ip'], $ruid_data[$sms_gw]['login'], $ruid_data[$sms_gw]['password'])) {      // Команда отправки SMS      $ARRAY = $API->comm("/tool/sms/send", array(      "port"=>$ruid_data[$sms_gw]['port'],      "channel"=>$ruid_data[$sms_gw]['channel'],      "phone-number"=>$_REQUEST['tel'],      "message"=>"To autorize user ".$_REQUEST['tel']." connection open  ".$host."?ruid=".$_REQUEST['ruid']."&auth=".$auth_code,));      // если отправка не удалась и получили ошибку модема, то выполняем сброс питания usb для перезагрузки модема      if($ARRAY['!trap']) {        $API->comm("/system/routerboard/usb/power-reset");        die('Stop with error: '.$ARRAY['!trap'][0]['message'].' Making power reset of usb-port');}  }  $API->disconnect();  die($auth_code);}function autorize() {  global $ruid_data;  // подключаем класс  $API = new RouterosAPI();  if ($API->connect($ruid_data[$_REQUEST['ruid']]['ip'], $ruid_data[$_REQUEST['ruid']]['login'], $ruid_data[$_REQUEST['ruid']]['password'])) {    // если подключились отправляем команду    $API->write('/ip/firewall/address-list/print', false);    $API->write('?comment='.$_REQUEST['auth'], false);    $API->write('=.proplist=.id');    // получаем ответ    $ARRAYS = $API->read();    // ЕСЛИ ЗАПИСЬ НЕ СУЩЕСТВУЕТ В АДРЕС-ЛИСТЕ - СБРОС    if (!$ARRAYS[0]) die('Request error');    // удаляем запись    $API->write('/ip/firewall/address-list/remove', false);    $API->write('=.id=' . $ARRAYS[0]['.id']);    $READ = $API->read();  }  $API->disconnect();  // ИНФОРМИРУЕМ ПОЛЬЗОВАТЕЛЯ ОБ УСПЕШНОЙ АВТОРИЗАЦИИ  die('      <html>      <body style="background-color: #282c34; color: #fff; height: 100vh; display: flex;">        <div style="margin: auto; max-width: 50%;">          <p style="font-size: 24pt; font-weight: bold; margin: -300px 0 50px;">            VPN-соединение установлено и авторизовано, можете продолжить работу          </p>          <p style="font-size: 14pt; color: #aaa;">             В случае недоступности сервисов обратитесь к вашему системному администратору<br/>          </p>        </div>      </body>      </html>');}?>


RouterOS API class PHP используемый в коде можно взять на GitHub.

Благодарю за внимание. Буду рад любым комментариям.
Источник: habr.com
К списку статей
Опубликовано: 15.06.2020 12:08:41
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Php

Сетевые технологии

Mikrotik

Vpn

Удалённый доступ

Двухфакторная аутентификация

Sms-шлюз

Категории

Последние комментарии

© 2006-2020, personeltest.ru