Двухфакторая аутентификация VPN/Mikrotik – просто и масштабируемо

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

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

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

  • Низким уровнем вхождения и простотой кода (для понимания/отладки другим сотрудником)
  • Простые скрипты ROS не создают никакой нагрузки и работают даже на hAP Lite
  • Масштабируемость – возможность подключения большого количества VPN-шлюзов с целью снижения нагрузки или географического распределения
  • Возможность использования Mikrotik CHR в качестве VPN-сервера
  • «1хN» – 1 SMS-шлюз на неограниченное количество роутеров с возможностью расширения при росте нагрузки
  • Возможность привязки отдельного роутера к «конкретному» модему (для чего? – об этом позже)
  • Использование всего одного php скрипта на удаленном сервере
  • Не важно какое устройство инициировало VPN-соединение, авторизация по ссылке из SMS
  • Ведение log'а всех авторизаций на сервере (можно вкл/выкл)
  • Увеличение отказоустойчивости и снижение нагрузки системы путем отправки SMS рандомно с нескольких модемов
  • Возможность отправки SMS через платные SMS-шлюзы (в коде на примере smsc.ru)
  • Функция Firewall – доступ только у роутеров занесенных в список (можно вкл/выкл)
  • Новое: Отправка кодов через Synology Chat и Telegram Bot. Причем различным пользователям уведомления могут доставляться по различным каналам.


Если вас интересует данная тематика, сохраните статью в закладки, я буду периодически её обновлять, по мере расширения функционала скрипта.


UPD 1.0 – Код php обновлен. Теперь он включает логирование всех запросов к скрипту (можно вкл/выкл) и рандомную отправку сообщений через несколько шлюзов в случае указания больше одного.

UPD 1.1 – Проект выложен в репозиторий на GitHub. По отзыву в комментарии добавлена возможность отправки через платные sms-шлюзы (с возможностью указания для каждого роутера откуда отправлять – модем или платно, можно рандомно сочетать несколько путей отправки для одного роутера).

UPD 1.2 – Добавлена функция firewall – доступ к функции генерации и отправки кодов авторизации есть только у роутеров занесенных в список (можно вкл/выкл). Все ответы заменены на базовые коды ответов HTTP

UPD 1.3 – Отправка сообщений с кодом пользователям в Synology Chat и через Telegram Bot. Причем различным пользователям уведомления могут доставляться по различным каналам. Также упрощены и обновлены скрипты для MT

Внимание! Актуальный код скриптов для MikroTik, а также php можно получить на github – ссылка в конце. В статье приведен изначальный код, который не теряет актуальность, т.к описывает общую логику действий.

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


Задача 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, коду и их описанию
Пояснение: указанные скрипты и php-код работоспособны при любом типе VPN-сервера. Выбор типа сервера (PPP/L2TP/SSTP), настройки профиля и туннеля – вы выбираете «на свой вкус», я же приведу общий пример быстрой настройки.

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


Firewall


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


Добавляем правило блокирующее трафик VPN-пользователей из списка ожидания на любой хост кроме разрешенных
/ip firewall raw
add 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 profile
add dns-server=10.10.0.1 idle-timeout=59m local-address=10.10.1.1 name=2F-VPN use-compression=no use-encryption=no use-mpls=no


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

On Up
:global ruid "#ROUTERLOGIN#";
:global pass "#ROUTER_PASSWORD#";
: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=$ruidlogin&pass=$ruidpass&tel=$userphone&remote-ip=$userip" url="http://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
:global ruid "#ROUTERLOGIN#";
:global pass "#ROUTER_PASSWORD#";
:local userip $"remote-address";
# if phone number stored in comment
:local userphone [/ppp secret get [find name=$user] comment];
# if phone number = username
#:local userphone $user;

/tool fetch http-method=post http-data="ruid=$ruidlogin&pass=$ruidpass&tel=$userphone&action=down&remote-ip=$userip" url="http://synome.ru/" mode=http as-value output=user;
/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-адрес пользователя и его номер телефона, отправляем POST-запрос на сервер для сохранения в лог авторизации, находим его в address-list и удаляем. Все выводим в лог.


PPP Secrets


Добавляем пользователя
/ppp secret
add 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 на свои.

Полный проект на GitHub

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
//
//  License: MIT
// ------------------------------------------------------------------------------


require_once('routeros_api.class.php');
// https://github.com/BenMenking/routeros-api

// -------------------------
// Базовые настройки скрипта
// Base settings
// -------------------------
// Firewall - разрешает доступ к отправке SMS только роутерам из массива $ruid_data | Firewall - allow access to send SMS only for router from array $ruid_data
$firewall = true; // Используем firewall? | Use firewall? true | false
$uselog = true; // Используем лог? | Use log? true | false
$log_path = 'mt2Fvpn.log'; // Путь к файлу лога | Log file path
$host = 'https://yourwebsite.com/'; // Адрес по которому доступен данный скрипт | Full address that this script awalible

// Массив данных всех роутеров/vpn-шлюзов | Routers data array used as vpn-servers
$ruid_data = array(
    // пароль в md5, глобальный ip-адрес, логин входа на роутер, пароль, SMS-шлюз через который происходит отправка SMS
    // password in md5, global ip-address, mikrotik login, password, SMS-gateway-key will be use to send sms
    '#ROUTERLOGIN#' => array('mdpass' => '#ROUTER_MD5_PASSWORD#',
                          'ip' => 'XXX.XXX.X.X',
                          'login' => '#ROSAPI_LOGIN#',
                          'password' => '#PASSWORD#',
                          'smsgw' => array(
                                           // Если необходимо отправлять SMS рандомно с нескольких шлюзов (снижение нагрузки на модем)
                                           // If you want send SMS randomly from any SMS-gateways, add one here (low modem load)
                                           0 => 'SMS_gw1')
                          )
    );

// Массив данных роутеров используемых в качестве sms-шлюзов | Routers data array will be used to send autherization sms
$SMS_gateway = array(
    // ip-адрес шлюза (глобальный или локальный если в одной сети с сервером), логин, пароль, порт USB-модема, канал USB-модема
    // ip-address (global or local if used in one local network with server), login, password, USB-modem port, USB-modem channel
    'SMS_gw1' => array('ip' => 'XXX.XXX.X.X', 'login' => '#ROSAPI_LOGIN#', 'password' => '#PASSWORD#', 'port' => '#USB_PORT#', 'channel' => '#USB_CHANNEL#')
    );


// -------------------------
// Входные проверки запросов
// Input data check
// -------------------------
if (!$_REQUEST) die(header('HTTP/1.0 406 Not Acceptable')); // если запроса нет – сброс | if request free - reset
if (!$_REQUEST['ruid']) die(header('HTTP/1.0 406 Not Acceptable')); // если не указан ruid - сбросс | if ruid not isset – reset
if (!array_key_exists($_REQUEST['ruid'], $ruid_data)) die(header('HTTP/1.0 406 Not Acceptable')); // если роутер не существует – сброс | if router does not exist – reset
if ($_REQUEST['auth']) autorize(); // если запрос на авторизацию, то пускаем без пароля и проверяем авторизацию | if auth request allow without password
if (!ruid_auth()) die(header('HTTP/1.0 401 Unauthorized')); // проверяем пароль роутера для отправки SMS | check ruid password
if (@$_REQUEST['action'] == 'down') { // Если vpn-соединение закрыто | if vpn-connection closed
  writelog('CONNECTION CLOSED');
  die(header('HTTP/1.0 200 ОК'));
}
if ($_REQUEST['tel']) send_authcode(); // если задан номер телефона, отправляем SMS | if phone number isset – sending SMS

// ---------------------------------------------------------------------
// Проверка на наличие роутера в списке разрешенных и пароля авторизации
// Check for router (ruid) is in allowed list
// ---------------------------------------------------------------------
function ruid_auth() {
  global $ruid_data;
  if (!$_REQUEST['pass']) return false; // если пароль не задан – сброс | if password not set - reset
  // проверяем md5-хэш пароля | check password md5-hash
  if (md5($_REQUEST['pass']) == $ruid_data[$_REQUEST['ruid']]['mdpass']) return true;
  return false;
}

// ---------------------------------------------------------
// Функция отправки ссылки с кодом авторизации через ros api
// Send autherization sms via ros api function
// ---------------------------------------------------------
function send_authcode() {
  global $ruid_data;
  global $host;
  global $firewall;

  // Если firewall == true, то разрешаем доступ к отправке только роутрам внесенным в массив $ruid_data
  // If firewall == true, allow access only to routers has record in array $ruid_data
  if ($firewall) firewall();
  // Выбираем шлюз рандомно | Get random SMS-gateway
  $sms_gw = $ruid_data[$_REQUEST['ruid']]['smsgw'][array_rand($ruid_data[$_REQUEST['ruid']]['smsgw'], 1)]; // данные sms-шлюза
  // генерируем код авторизации и добавляем его в массив | Generate auth-code and add to REQUEST array
  $_REQUEST['authcode'] = substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ123456789'), 0, 5);
  // Формируем сообщение отправляемое пользователю – только eng или транслит
  // Create message that will be sent to user
  $message = rawurlencode('To authorize user '.$_REQUEST['tel'].' connection open '.$host.'?ruid='.$_REQUEST['ruid'].'&auth='.$_REQUEST['authcode']);

  // Если используем платный sms шлюз | If use paid sms center gateway
  if ($sms_gw == "PAY") UsePAYsmsc($message);

  // подключаем класс | connect class
  $API = new RouterosAPI();
  // если подключились отправляем SMS | if connected successfully - sending message
  if ($API->connect($SMS_gateway[$sms_gw]['ip'], $SMS_gateway[$sms_gw]['login'], $SMS_gateway[$sms_gw]['password'])) {
      // Команда отправки SMS | SMS send command
      $ARRAY = $API->comm("/tool/sms/send", array(
      "port"=>$SMS_gateway[$sms_gw]['port'],
      "channel"=>$SMS_gateway[$sms_gw]['channel'],
      "phone-number"=>$_REQUEST['tel'],
      "message"=>"To autorize user ".$_REQUEST['tel']." connection open – ".$host."?ruid=".$_REQUEST['ruid']."&auth=".$_REQUEST['authcode'],));
      // если отправка не удалась и получили ошибку модема, то выполняем сброс питания usb для перезагрузки модема
      // Checking if send failed and error message return, will make usb power-reset to restart modem
      if($ARRAY['!trap']) {
        $API->comm("/system/routerboard/usb/power-reset");
        // логируем запросы | save log
        // добавляем в массив сообщение об ошибке | Add error to array and save all array to log
        $_REQUEST['ERROR'] = $ARRAY['!trap'][0]['message'];
        writelog('MODEM ERROR');
        die('Stop with error: '.$ARRAY['!trap'][0]['message'].' Making power reset of usb-port');}
  }

  $API->disconnect();
  // логируем запросы | save log
  writelog('SEND AUTH CODE');
  die($_REQUEST['authcode']);
}


// --------------------------------------------------------------------
// Функция авторизации через ros api – удаление из address-list
// Autherization using ros api function – delete list from address-list
// --------------------------------------------------------------------
function autorize() {
  global $ruid_data;
  // подключаем класс | connect class
  $API = new RouterosAPI();
  if ($API->connect($ruid_data[$_REQUEST['ruid']]['ip'], $ruid_data[$_REQUEST['ruid']]['login'], $ruid_data[$_REQUEST['ruid']]['password'])) {
    // если подключились отправляем команду | if connected successfully - sending command
    $API->write('/ip/firewall/address-list/print', false);
    $API->write('?comment='.$_REQUEST['auth'], false);
    $API->write('=.proplist=.id');
    // получаем ответ | get response
    $ARRAYS = $API->read();
    // Если запись не существует в адрес-листе - сброс
    // If no record in firewall address_list – reset
    if (!$ARRAYS[0]) die(header('HTTP/1.0 406 Not Acceptable'));
    // удаляем запись | delete firewall address-list
    $API->write('/ip/firewall/address-list/remove', false);
    $API->write('=.id=' . $ARRAYS[0]['.id']);
    $READ = $API->read();
  }
  $API->disconnect();
  // логируем запросы | save log
  writelog('AUTHERIZATION');

  // Информируем пользователя об успешной авторизации | Show success page to user
  die('
      <!DOCTYPE html>
      <html lang="ru">
      <meta http-equiv="Content-Type" content="charset=utf-8" />
      <body style="font-family: Verdana, Arial, Helvetica, sans-serif; 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: 0 0 10px;">
            VPN-соединение установлено, можете продолжить работу<br />
          </p>
          <p style="font-size: 12pt; color: #aaa;">
            В случае недоступности сервисов обратитесь к вашему системному администратору<br />
          </p>
          <p style="font-size: 24pt; font-weight: bold; margin: 100px 0 10px;">
            VPN connection is established, you can continue to work
          </p>
          <p style="font-size: 12pt; color: #aaa;">
            If any services unavalible you must contact with your system administrator<br />
          </p>
        </div>
      </body>
      </html>
    ');
}

// ---------------------------------------------------------------------------------------------------------------------
// Отправка sms через платный сервис отправки сообщений (значение smsgw в $ruid_data должно быть установлено 0 => «pay»)
// Send sms via paid sms center gateway (smsgw value in $ruid_data should be installed 0 => «pay»)
// ---------------------------------------------------------------------------------------------------------------------
function UsePAYsmsc($message) {
    // для примера использован smsc.ru | for example i use smsc.ru
    $smsc_login = '#SMSCLOGIN#';
    $smsc_pass = '#SMSCPASSWORD#';
    $smsc_sendername = '#SMSCSENDERNAME#'; //  если используется | if need
    // Отправляем SMS | SEND SMS
    $sms_send = file_get_contents('https://smsc.ru/sys/send.php?login='.$smsc_login.'&psw='.$smsc_pass.'&phones='.$_REQUEST['tel'].'&mes='.$message.'&sender='.$smsc_sendername.'&flash=0');
    if (strpos($sms_send, 'OK') !== false) {
      die($_REQUEST['authcode']);
    }
      die($_REQUEST['Send SMS error']);
};

// --------------------------------------------------------------------------------
// Firewall - разрешает доступ к отправке SMS только роутерам из массива $ruid_data
// Firewall - allow access to send SMS only for router from array $ruid_data
// --------------------------------------------------------------------------------
function firewall() {
  global $uselog;
  global $log_path;
  global $ruid_data;

  $result = false;
  // перебираем массив и ищем ip в $ruid_data | serch ip in array $ruid_data
  foreach ($ruid_data as $value) {
    $result = (in_array($_SERVER['REMOTE_ADDR'], $value) === true) ? true : false;
    if($result) break; // если нашли совпадение, прерываем | if found record – break
  }
  // $uselog == true сохраняем лог | save log
  if (!$result) {
    if ($uselog) {
      $fp = fopen($log_path, 'a');
      fputs($fp, "\nTime = " . date("Y-m-d H:i:s")."\n");
      fputs($fp, 'FIREWALL BLOCKED ACCESS'."\n");
      fputs($fp, $_SERVER['REMOTE_ADDR']."\n");
      fclose($fp);
    }
    die(header('HTTP/1.0 403 Forbidden'));
  }
}

// ------------------------
// Функция сохранения логов
// Save log function
// ------------------------
function writelog($type) {
  global $uselog;
  global $log_path;
  // $uselog == false – break
  if (!$uselog) return;
  // удаляем пароль из массива перед сохранением лога | remove ruid password from array before saving log
  unset($_REQUEST['pass']);
  // сохраняем лог | save log
  $fp = fopen($log_path, 'a');
  fputs($fp, "\nTime = " . date("Y-m-d H:i:s")."\n");
  fputs($fp, $type."\n");
  fputs($fp, print_r($_REQUEST, true));
  fclose($fp);
}

?>


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

Благодарю за внимание. Буду рад любым комментариям.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Какие каналы уведомлений добавить?

  • 100,0%Telegram9
  • 33,3%Synology Chat3
  • 22,2%E-mail2
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 34

    +1
    Интересное решение с прослойкой из PHP.
    Только с масштабированием "«1хN» – 1 SMS-шлюз на неограниченное количество роутеров" возникает проблема единой базы логинов и паролей. Она имеет два быстрых решения:
    1. Реплекация /ppp secrets на неограниченное количество роутеров при помощи скриптов/API.
    2. Установка некоего дополнительно стороннего Radius-сервера. Тогда будет логин будет всегда по номеру телефона.

    Для обеспечения принципа "Не важно какое устройство инициировало VPN-соединение, авторизация по ссылке из SMS", сервис авторизации нужно выставить в Интернет. Это может быть нежелательно в ряде случаев.

    p.s. если VPN-соединение сможет провисеть час, то не прошедший проверку клиент всё равно получит доступ к внутренним ресурсам сети.
      0
      1хN подразумевает именно использование одного SMS-шлюза для авторизации на N-vpn, а не сервиса/сервера авторизации (usb-модем в одном роутере, а не в каждом).
      – Репликация /ppp secrets не имеет смысла, т.к пользователю должен быть выдан ip из внутренней адресации роутера.
      – Использование стороннего радиуса не рассматривалось в данной задаче, как усложняющее «уровень входа» (проще уже было использовать usermanager)
      – Данное решение несет функцию защиты внутренней сети компании, а не способы авторизации, а учитывая, что сервис не предназначен для авторизации широкого круга лиц, эти логины добавляются на необходимый MikroTik системным администратором.

      p.s. если VPN-соединение сможет провисеть час, то не прошедший проверку клиент всё равно получит доступ к внутренним ресурсам сети.

      Не сможет. Через 59 минут соединение будет разорвано по тайм-ауту. Если боязно, то можно не удалять его ip из адресного листа по тайм-ауту (удалив скрипт из On Down). В этом случае адресный лист будет действовать даже после отключения пользователя.
      Если же пользователь инициирует новое подключение, то в текущем листе просто обновится код авторизации и все продолжит работать как и запланировано.
        0

        Чтобы соединение было разорвано по таймауту, жто должно быть явно указано в PPP-PROFILE. По-умолчанию, ограничения на длителтность ppp-соединения нет.

          0
          Ну это то как раз и указано и описано подробно какое значение там должно быть
          /ppp profile
          add 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
            0

            Я бы еще "sessino-timeout=00:59:00" добавил, т. к. в сессии будут какое-то время ходить пакетики, idle-timeout обнулится.
            Да, клиенту придется при этом переустанавливать соединение один раз в час. Но это даже полезно от "брошеннных и забытых" пользователями vpn-соединений.

              0
              session-timeout не хорошо использовать по нескольким причинам:
              1. необходимость переподключения раз в час будет бесить сотрудников
              2. ненужная нагрузка на модемы
              3. очень плохо на каналах сервер-mikrotik / mikrotik-mikrotik
      0
      … сервис авторизации нужно выставить в Интернет. Это может быть нежелательно в ряде случаев.


      Да, но мы делаем все возможные проверки на предмет неверного запроса. Во вторых, чтобы взломать защиту на этапе авторизации нужно обладать верным номером ruid и верным кодом авторизации. Вариант подбора очень низок.
      Ну и самое главное – это единственные способ, который пришел в голову для авторизации соединения Сервер -> Mikrotik

      PS в скриптах везде указан http (т.к откатывался в локальной сети), но в жизни нужно https (меняется в 3х местах)

      Если у вас есть какие-то подозрения, где можно что-то перехватить, то пишите, покопаем в этом направлении.
        0

        Перехватывать можно содержимое туннеля при использовании в профиле "use-encryption=no". Со своей стороны склонен использовать "use-encryption=required" или, как минимум "yes".

          0
          В данной статье не говорится про настройку VPN сервера.
          Настройка PPP/L2TP/SSTP – это на ответсвенности администратора сети, также как и настройка firewall, которую каждый делает на свой вкус и потребности, на функционале авторизации и её защищенности это никак не сказывается.
          Мой скрипт работает при любом типе туннеля, а, например, SSTP не работает use-encryption
          Лично я вообще люблю L2TP, вот просто люблю :)

          Но спасибо, добавлю пояснения к статье
            0

            А как l2tp на смарт поднять так чтобы он не оказался основным шлюзом? Правильно, ни как. А микрот не умеет ovpn в udp. Что тоже в минус.

              0
              Не совсем понял как это относится к теме двухфакторной авторизации, но да, на смартфоне не получится и не только l2tp, но и все остальные, а на MacOS не получится PPTP/SSTP.
              Но это решение для рабочего процесса:
              – у меня оно используется для авторизации туннелей сервер-микротик и между микротиками, а дальше поднимаю адресацию по OSPF.
              – достаточно хорошо будет для авторизации сотрудников работающих на удаленке с компьютера. Я полагаю с телефона никто не работает.
              – При соединении офисов в единую сеть данное решение также позволит получать информацию о «падении/подъеме» туннеля. Но это больше вторично, но тоже полезно

              И, что не маловажно, если вы/компания строите защищенную сеть и заморочились с 2FA, то шлюз у вас должен быть один, в противном случае в вашу сеть запросто можно зайти, что называется «со двора» – с того устройства у которого основной шлюз не через vpn
                0
                Вопрос был в любви к l2tp. И сразу камень в огород микрота где ovpn позволяет не задавать основной шлюз, но tcp портит всю малину.
                Без предъяв. Статья понравилась, в избранное затулил не знаю зачем )))
                  0
                  Это не камень в огород МТ, а камень в протокол. Вы же не говорите, что на другом оборудовании l2tp можно сделать так, чтобы работало.
                  Тут «от каждого по желаниям, каждому по потребностям». Лично для меня отсутствие OVPN UDP не является причиной, чтобы отказываться от столь мощного оборудования.
                  Это из разряда извечных споров типа «Android/iOS», «Windows/MacOS», «Huyndai/VW» etc…
                    0
                    Вы же не говорите, что на другом оборудовании l2tp можно сделать так, чтобы работало.

                    Ну начнем с того что в той же винде легко можно снять галочку с основного шлюза и все проблемы исчезают сами по себе. Ну кроме того что надо маршруты теперь дописать. ))) В том же микроте это сделать вообще не проблема. Так что… Но к примеру в том же android этого не завезли и как не крутись после соединения с l2tp сервером весь трафик идет через сервер l2tp. А к примеру у меня канал ограничен. Представь 10 клиентов соединятся с сервером, забудут отключить и будут ютубчик через меня гонять? Или работа у них эпизодическая не клацать же каждый раз «соединится/разъединиться», да и опять же трафик в этот момент либо через меня, либо на маршрутизаторе я его рублю и клиент получается без инетрнета…
                    Это не камень в огород МТ, а камень в протокол.

                    Далее Вы не верно поняли. Камень был не в l2tp, а микрот + ovpn. Ведь там можно из коробки считай для любого устройства сделать как default gateway, так и отказаться от него вовсе, а можно отдавать клиенту конфиги с сервера с скажем правилами маршрутизации которые клиент (приложение) ovpn пропишет в систему. И мне пользователю надо дать только адрес сервера и логин/пароль. Дальше произойдет «чудо»! И по идее даже не надо вдаваться в подробности что за система у клиента, и обойтись одним конфигом. Но отсутствие возможности пользоваться UDP у OVPN полностью отбивает желание пользоваться им. К примеру у меня у многих клиентов не очень широкий интернет канал. И забивать его лишним трафиком TCP, когда внутри и так TCP… сами понимаете )
                    И да по итогу мне пришлось отказаться от микрота именно по этой причине. Домой имея микрот в качестве основного маршрутизатора я поднял l2tp, проблем нет на микроте поиграться с основными шлюзами, но вот клиентам которым надо доступ к нашему ПО и иногда со смартов пришлось поднимать ovpn на отдельном сервере. Когда поднял на микроте, то столкнулся с тем что служебные пакеты очень быстро забили мой канал. Перешел с микрота TCP на отдельный сервере с UDP и прям сильно полегчало.
                      0

                      Я вас правильно понял. На маке тоже можно не ставить галочку "отправлять весь трафик через vpn".
                      Но речь у вас изначально была про невозможность на смартфонах пустить трафик не over vpn, вот я говорю, что проблема локальная для l2tp и его реализации в смартфонах. И проблема тут не в микротах.

                        0
                        Опять же вы не до конца поняли. Проблема глобальная и универсального решения на микроте не заиметь. Ладно, оставим этот разговор. Удачи!
        0

        Один вопрос — почему вместо нестабильного модема, который может отключить оператор (по вашим же словам) не использовать например тот же AWS SNS для рассылки SMS сообщений? Выйдет и дешевле и надежнее.
        Вместо php скрипта можно использовать AWS Cognito и/или Lambda.
        Ну и лично я бы подобную задачу решил путем установки опенсорсного pritunl — vpn сервера на базе openvpn — он поддерживает 2fa на базе OTP кодов из коробки. Ну и займет подобный сетап минут 10. Но это конечно же имхо.

          0
          Вопрос в цене. У того же Тинькофф.мобайл симка с безлимитными sms стоит 49 руб./мес.
          Прикрутить к скрипту отправку через платный сервис, тот же smsc.ru / AWS SNS не проблема, но сразу считайте на одну авторизацию -2,5-3 рубля. Поэтому дешевле точно не выйдет.

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

          По факту решение оказалось достаточно стабильное если модем использовать нормальный. У меня установлена Sierra Wireless на pci-e в BaseBox2 и уже более полутора лет работает безотказно примерно по 1200 авторизаций в месяц. Плюс через него я отправляю уведомления умного дома, synology и пр… эдакий домашний sms-шлюз. Модемы пробовал разные (Alcatel, 3272, 3372, SIM800L, …)

          AWS Cognito / Lambda – другой уровень вхождения и опять таки цена каждой авторизации…
          Pritunl – это облачное решение, для его работы нужно либо VPS в облаке, либо выделять сервер/ВМ + стоимость на каждую авторизацию

          Мое решение подходит тем, у кого в парке есть физические маршрутизаторы MT. Сайт, на который можно кинуть скрипт и так у всех (почти у всех) есть.
            +1

            Ок, оставим вопрос с смс сообщениями. На AWS тарифы для смс сообщений зависят от страны и оператора получателя и колеблятся от 0.001$ до 0.1$ за сообщение — надо считать. Например, 1200 смс в РФ на оператора МТС обойдутся в 69$ примерно. Согласен, значительно дороже 49 рублей) Но если цель используя 2fa авторизовать пользователя, то AWS Cognito это то что доктор прописал.
            1200 авторизаций в месяц даже близко не подходит к месячному бесплатному (Free tier) лимиту (который по моему 50000 в месяц, точно не вспомню, надо смотреть прайсинг).
            С лямбдами тоже никаких вопросов — если смогли написать php скрипт, то и на каком-нибудь nodejs / python наваяете, там сложного ничего нет.
            И без всяких сомнений такое решение будет надежнее чем использовать модем за 49 рублей)
            Просто присмотритесь к такому варианту решения, сервисы AWS крайне хорошо документированы, а API интерфейсы я бы даже сказал user-friendly.
            И облака при правильном использовании не всегда дорого, иногда даже бесплатно)

              0
              С лямбдами тоже никаких вопросов — если смогли написать php скрипт, то и на каком-нибудь nodejs / python наваяете, там сложного ничего нет.

              Уровень вхождения не мой, а того, кто внедряет мое решение у себя :)
              Я изначально делал его не для себя, а для того чтобы отправить ссылку и человек мог скачав два файла все запустить
                0
                Спасибо за обратную связь.
                Я обновил код добавив возможность отправки SMS через платный шлюз, на примере smsc.ru (цена за одно SMS на 0.5 руб. ниже чем у amazon)
                Также выложил код на GitHub
            +1
            Спасибо за статью. Я правильно понимаю что если надо вместо смс имплементировать TOTP то надо менять в функции autorize()?
              0
              В данном случае SMS выступает в виде транспорта для доставки одноразового пароля.
              Соответственно если использовать другой тип пароля (генерации пароля и пр), вам нужно его сначала сгенерировать в send_authcode(), потом передать пользователю и потом проверить, что вернулось от него в autorize().
              Но необходимости в этом особого не вижу, потому что гораздо легче рвать неподтвержденное, например в течении одной/двух минут, VPN-соединение по таймауту (сейчас стоит 59 минут)
                0

                В случае TOTP пользователю ничего отсылать не нужно, у него код уже есть .

                  0
                  А что вы подразумеваете под – «В случае TOTP пользователю ничего отсылать не нужно, у него код уже есть».
                  TOTP в моем понимании – метод шифрования пароля со сроком действия. Сгенерировать его можно на сервере, в клиентском приложении или на токене (например), куда изначально уже внесен ключ-секрет. Если у клиента нет приложения или токена и пароль генерировался на сервере, то пароль ему в любом случае нужно передать.
                  Если написать приложение, то это все меняет, но я увы не пишу их.

                  Буду рад любым разъяснениям. Может это то, что индустрия ждет и я это реализую на благо сообщества :)
                    0

                    TOTP не метод шифрования. Это, проще говоря, алгоритм генерации N-значного цифрового пароля на основе текущего времени и некоего секрета который есть и на сервере и на клиенте. В данном случае надо его просто генерировать и сравнивать с введённым кодом второго фактора.

                      0
                      Ну это то, что я и представлял. Но для генерации у клиента должно быть приложение или токен, правильно? Ну и самое главное на сервере еще должна быть страница куда этот пароль вводить.
                      Ну или приложение, которое генерирует код и отправляет его на сервер «невидимо».
                        0
                        повторюсь, ничего посылать не нужно. одноразовый код вычисляется по формуле:
                        OTP = TOTP* (time+shared secret)

                        * там hmac и т.п. — подробнее в RFC

                        у клиента будет генерироваться такой же код без привязки к коду на микротике — главное чтобы время и shared secret совпадали
                          0

                          Да я это понял.
                          Где он у него будет генерироваться?

                            0
                            Например на аппаратном ключе, или на приложении типа Google Authenticator
                              0

                              Вот про это я и писал, что у пользователя должен быть токен или приложение, а также на сервере должна быть форма в которую будет выводиться этот код. В данном случае код не нужно передавать пользователю, но наоборот сервер его должен получить от пользователя. Экономия на смс, но неудобство для авторизации. На мой взгляд гораздо проще перейти по ссылке из SMS/email и пр.
                              Решение задачи с ТОТР усложняется, когда один пользователь может авторизовать несколько туннелей на разных роутерах (возможность заложена в скрипте).

                                0
                                Дело не в экономии. СМС легко «взломать» заказав дубликат симки
                                  0
                                  TOTP хорошая вещь, но конкретно к данному решению оно увы совсем не подходит.
                                  SMS взломать не получится, т.к отправленный код действует ровно столько сколько действует сессия установленная в idle-timeout. Далее код теряет свою актуальность.
                                  Поэтому если нужна очень высокая защищенность, для критических узлов можно установить timeout vpn-соединения равным 60s.
                                  Заказать дубликат симки даже за час не реально, учитывая то, что еще нужно знать на какой номер отправлялось сообщение и как-то решить вопрос с предоставляемыми документами для заказа дубликата.
                                  Второй момент – для компаний которые используют для защищенного общения внутренние чаты, например тот же Synology Chat, скрипт может отправлять код авторизации через его API и в этом случае система авторизации становится изолированной от внешней среды.
              0
              Обновлен код:
              – Добавлена отправка кодов в Synology Chat и через Telegram Bot
              – Возможность отправки различным пользователям по различным каналам
              – Упрощен код скрипта для MikroTik

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое