Серверы и 1С
Секретная ссылка вместо белого списка IP
Медицинская организация работает в облачной медицинской информационной системе. Страница входа в систему открыта в интернет, как у любого сайта.
Медицинская организация работает в облачной медицинской информационной системе. Страница входа в систему открыта в интернет, как у любого сайта. Защищает её только логин и пароль, а среди пользователей есть регистраторы, врачи на подработке и сотрудники, которые заходят из дома.
Разработчик системы предлагает стандартную защиту: белый список IP-адресов. Но у клиники несколько площадок, сотрудники работают из дома и с телефонов, а домашние провайдеры раздают адреса как хотят. Белый список пришлось бы обновлять каждую неделю, и каждое обновление — это звонок «у меня не открывается».
Идея
Поставить между людьми и системой свой шлюз на домене организации. Разработчику даём в белый список ровно один адрес — адрес шлюза. А сам шлюз пускает только тех, кто хотя бы раз открыл «секретную ссылку» текущего месяца. Ссылка оставляет в браузере cookie, и дальше человек работает как обычно: та же закладка, тот же логин. Первого числа ссылка меняется, старая перестаёт работать.
Для случайного сканера интернета на месте формы входа теперь пустая страница 404. Для своих — одно сообщение в месяц с новой ссылкой.
Из чего собрано
Небольшой сервер на Ubuntu, Docker, Nginx Proxy Manager (далее NPM) для обратного прокси и бесплатных сертификатов Let's Encrypt. Плюс два кусочка нашей конфигурации nginx и скрипт на bash. Никаких отдельных сервисов авторизации, баз данных и перезапусков по расписанию.
Как nginx сам узнаёт, какой сейчас месяц
У nginx есть переменная $time_iso8601 с текущим временем. Директива map сопоставляет её с шаблоном «год-месяц» и выдаёт токен этого месяца. Таблица токенов лежит в отдельном файле:
# /data/nginx/custom/http_top.conf (подключается NPM на уровне http)
map $time_iso8601 $gw_token {
default "";
include /data/nginx/custom/gw_tokens.map;
}
# совпадает ли cookie с токеном текущего месяца
map "$cookie_gw:$gw_token" $gw_cookie_ok {
"~^([^:]+):\1$" 1;
default 0;
}
# пришёл ли человек по свежей ссылке
map "$arg_gw:$gw_token" $gw_arg_ok {
"~^([^:]+):\1$" 1;
default 0;
}
Сравнение через "значение:токен" и обратную ссылку \1 — небольшой трюк: в if у nginx нельзя сравнить две переменные между собой, а регулярное выражение в map это умеет.
Файл с токенами — просто строки «месяц → токен»:
"~^2026-10-" "3f9c1e0a7b2d4c5e8a6b1d2c3e4f5a6b";
"~^2026-11-" "9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d";
Дальше в поле Advanced у прокси-хоста в NPM:
location / {
# пришёл по ссылке месяца — ставим cookie и убираем токен из адреса
if ($gw_arg_ok) {
add_header Set-Cookie "gw=$arg_gw; Path=/; Max-Age=2678400; Secure; HttpOnly; SameSite=Lax" always;
return 302 /;
}
# нет cookie текущего месяца — делаем вид, что здесь ничего нет
if ($gw_cookie_ok = 0) {
return 404;
}
proxy_set_header X-Forwarded-For "";
proxy_set_header X-Real-IP "";
proxy_set_header X-Forwarded-Proto "";
proxy_set_header X-Forwarded-Host "";
proxy_pass https://адрес-медицинской-системы;
}
Скрипт, который раз в год делает всю работу
Токены на следующий год генерируются заранее, в декабре, одной командой. Скрипт дописывает строки в таблицу, сохраняет готовые ссылки для рассылки, проверяет конфигурацию и только потом перечитывает её:
#!/usr/bin/env bash
# gw-tokens.sh 2027 — сгенерировать токены и ссылки на 12 месяцев
set -euo pipefail
YEAR="${1:?укажите год}"
MAP=/opt/npm/data/nginx/custom/gw_tokens.map
LINKS=/root/gw-links-$YEAR.txt
DOMAIN=mis.example.ru
touch "$MAP"
for m in $(seq -w 1 12); do
t=$(openssl rand -hex 16)
echo "\"~^${YEAR}-${m}-\" \"$t\";" >> "$MAP"
echo "$YEAR-$m https://$DOMAIN/?gw=$t" >> "$LINKS"
done
chmod 600 "$LINKS"
docker exec npm nginx -t && docker exec npm nginx -s reload
echo "Готово. Ссылки для рассылки: $LINKS"
И одна строка в cron, чтобы не забыть:
0 9 15 12 * /root/gw-tokens.sh $(( $(date +\%Y) + 1 )) >> /var/log/gw-tokens.log 2>&1
На чём мы споткнулись
Эта часть, пожалуй, самая полезная для тех, кто будет повторять.
Несуществующий файл в include ломает всё. Если gw_tokens.map ещё не создан, nginx не проходит проверку конфигурации, и NPM отказывается сохранять хост. Поэтому в скрипте стоит touch — файл существует всегда, даже пустой.
NPM «теряет» поле Advanced. После неудачного сохранения (например, из-за ошибки выше) содержимое поля может не примениться. Сертификат при этом молча не выпускается, правила не работают, а в интерфейсе всё выглядит нормально. После любой ошибки открывайте Advanced и проверяйте глазами, что ваш конфиг на месте.
Медицинская система говорила «система недоступна». Через прокси она отвечала отказом, хотя адрес шлюза уже был в белом списке. Причина — заголовки X-Forwarded-For и X-Real-IP: прокси честно передавал адрес настоящего пользователя, система его видела и отказывала, потому что этого адреса в её списке нет. Очистка четырёх заголовков в конфиге выше — это и есть лечение.
Какой адрес давать разработчику. У сервера за NAT входящий и исходящий адреса могут различаться. В белый список нужен исходящий, и проверять его надо с самого сервера:
curl -s https://ifconfig.me; echo
Проверка
curl -I https://mis.example.ru/ # 404 — без ссылки ничего нет
curl -I "https://mis.example.ru/?gw=ТОКЕН" # 302 и Set-Cookie
После запуска мы сразу перегенерировали токены, которые светились в отладке, и закрыли из интернета административные порты NPM и Portainer, оставив только 80 и 443.
Что из этого вынести
Форма входа в медицинскую систему исчезла из общего интернета, и при этом никому не пришлось менять привычки. Схема не зависит от разработчика системы и подходит для любого веб-сервиса, который нельзя спрятать за VPN: бухгалтерских порталов, личных кабинетов, внутренних панелей. Администрирование серверов и такие решения по безопасности — отдельное направление нашей работы.
Ещё проекты
Похожие объекты
Серверы и 1С
Свой ИИ в закрытом контуре: когда облако под запретом
ИИ-помощник по внутренним документам без облака: собрали стенд на своих серверах — видеокарты в ESXi, Ollama и поиск по базе знаний на PostgreSQL.
Серверы и 1С
Zabbix тревожит сервер 1С: ложная тревога и настоящая настройка памяти SQL
Серверы клиентов у нас под мониторингом Zabbix: он пишет в Telegram, когда что-то идёт не так. Один из серверов 1С стал регулярно присылать предупреждение «Memory Pages/sec is too high (over 1000 for 5m)» со значениями около 4000.
Серверы и 1С
Медицинская система только по секретной ссылке: обратный прокси с ежемесячной ротацией токена
Медицинская организация пользуется облачной медицинской информационной системой (МИС). Вендор системы умеет ограничивать доступ по IP-адресу, но у клиники несколько площадок, сотрудники работают и из дома, и с мобильных устройств, а провайдеры меняют адреса.