Лаборатория: собираем TOTP + heartbeat вокруг MikroTik
Цикл «Не светим лишнего». Выпуск 15.

Теоретическая схема у нас уже есть. Теперь полезно собрать маленький стенд, который можно без жалости ломать. Не надо сразу подключать боевой WinBox, IPMI и камеры. Сначала докажем, что доступ правильно открывается и, что гораздо важнее, правильно закрывается.
Что понадобится
На границе ставим MikroTik с RouterOS 7. У него есть WAN и отдельная защищаемая сеть, например VLAN protected-lab.
В защищаемой сети размещаем три разных ресурса:
- простой HTTP-сервис на 443;
- Linux-хост или контейнер с SSH;
- Windows VM или тестовый RDP-сервис.
Снаружи доступен только HTTPS-портал. Его административная часть слушает внутренний management-интерфейс или дополнительно закрыта firewall. RouterOS API доступен лишь worker с фиксированного управленческого адреса.
Сам портал удобно разделить на компоненты:
- backend проверяет TOTP и создаёт сессии;
- PostgreSQL хранит токены, профили, владельцев и историю;
- Redis хранит живые WebSocket-сессии и короткое оперативное состояние;
- worker синхронизирует активные разрешения с MikroTik;
- reverse proxy завершает TLS и проксирует WebSocket.
Для первого запуска всё это можно поднять на одной Linux VM в контейнерах. Разделение здесь логическое: позже компоненты получится разнести, не переписывая модель доступа.
Профили лаборатории
Не создаём универсальный список trusted. Заводим три роли.
lab-web разрешает только HTTPS к тестовому веб-сервису.
lab-ssh разрешает только TCP/22 к одному Linux-хосту.
lab-support разрешает HTTPS, SSH и RDP к конкретным тестовым адресам, но не ко всей VLAN.
Каждый TOTP-токен связан с одной или несколькими ролями. Пользователь не вводит destination вручную. На MikroTik заранее лежат правила, которые ссылаются на эти address-list.
Жизненный цикл сессии
Пользователь открывает портал и вводит TOTP. Backend проверяет код, срок действия токена, лимит попыток и разрешённые роли. Затем создаёт сессию с абсолютным максимумом, например два часа.
Браузер открывает WebSocket. Пока он жив, Redis содержит активную ссылку на пару внешний IP + роль. Worker раз в несколько секунд вычисляет нужное состояние и добавляет на MikroTik динамическую запись с timeout 60–90 секунд.
При штатном завершении worker удаляет запись сразу. Если любой компонент пропал, MikroTik сам удалит её по timeout. Это и есть fail closed.
Что проверять по шагам
1. Нулевое состояние. Без TOTP все три ресурса недоступны. Проверяем не только браузером, но и nmap, SSH и RDP-клиентом. Публичный портал при этом доступен.
2. Разные роли. Токен lab-web не должен открыть SSH. lab-ssh не должен дать RDP. Проверяем реальные правила, а не только подпись на странице.
3. Истечение времени. Не закрываем вкладку и ждём абсолютный максимум. Сессия обязана завершиться, даже если heartbeat продолжает идти.
4. Закрытие вкладки. При штатном WebSocket close доступ снимается быстро. Затем повторяем жёстко: убиваем браузер, отключаем сеть, усыпляем ноутбук. В этих случаях ждём окончания короткого lease.
5. Два пользователя за одним NAT. Открываем две сессии с одного публичного IP. Закрываем первую. Доступ второй роли обязан сохраниться. Закрываем последнюю — соответствующая запись исчезает.
6. Одинаковая роль за одним NAT. Две сессии требуют lab-ssh. После закрытия одной счётчик остаётся равным единице, и SSH не пропадает у второй.
7. Уже установленный SSH. Открываем соединение, затем отзываем сессию. Если терминал продолжает работать, значит порядок established,related, FastTrack или очистка connection tracking настроены неправильно.
8. Падение портала. Останавливаем backend и Redis. Новые доступы не выдаются, существующие исчезают после timeout. Никаких постоянных записей на MikroTik остаться не должно.
9. Потеря RouterOS API. Worker должен показать ошибку и не считать сессию успешно применённой. Старые leases естественно истекают.
10. Перезапуск worker. После запуска он восстанавливает правильное состояние из активных сессий, а не доверяет случайно оставшимся firewall-записям.
11. Смена внешнего IP. Переключаем сеть или внешний VPN. Старый адрес закрывается по lease. Новый не получает права автоматически без повторного подтверждения.
12. Рассинхронизация времени. Уводим часы портала и убеждаемся, что мониторинг NTP ловит проблему. TOTP очень не любит творческое отношение ко времени.
Плюсы такого стенда
- можно проверить спорные места до боевого внедрения;
- правила ресурсов отделены от логики токенов;
- легко сравнить фиксированный timeout и heartbeat;
- видны нагрузки на RouterOS API и worker;
- получается готовый набор интеграционных тестов;
- стенд потом можно превратить в dev-контур решения.
Минусы
- лаборатория не воспроизводит все виды CGNAT и мобильных сетей;
- Docker на одной VM скрывает проблемы высокой доступности;
- тестовые сервисы проще реальных старых панелей;
- права RouterOS API всё равно придётся отдельно минимизировать и изолировать;
- для промышленной эксплуатации нужны резервирование, мониторинг и резервные копии.
Где можно больно ошибиться
Тестировать только успешный вход. Самые важные сценарии — падение, отзыв и рассинхронизация.
Использовать боевые TOTP-секреты. Лаборатория должна иметь отдельные токены, ключи API, адреса и сертификаты.
Дать worker полный RouterOS admin. Даже в стенде стоит сразу строить отдельную учётку и management-доступ. Иначе опасная привычка доедет до production.
Считать состояние Redis единственной истиной. На точке применения всегда есть собственный короткий timeout. Потеряли оперативную базу — доступ закрывается.
Не собирать журнал. Для каждой записи нужны ID сессии, токен, роль, внешний IP, время создания, продления и причина закрытия.
Забыть IPv6. Если тестовый ресурс имеет глобальный IPv6 в обход MikroTik-политики, идеальный IPv4 heartbeat ничего не защищает.
Что получится в финале
После этих проверок у нас будет не просто страница с шестью цифрами, а понятный механизм: заранее проверенные группы ресурсов, временные роли, короткие firewall leases, живые браузерные сессии и гарантированный fail closed.
Дальше уже можно делать второй, практический цикл: конкретная схема БД, API портала, конфигурация RouterOS, Docker Compose и автоматические тесты. Но это будет отдельная инженерная работа, а не ещё одна статья в духе «добавьте правило accept и всё готово».
Ранее в цикле
- 11. TOTP-портал: ввёл код — firewall открыл нужную группу
- 12. Heartbeat: firewall открыт, пока жива вкладка
- 14. Собираем вместе: временный доступ без единственной волшебной двери