На связи удалённо · офис завтра с 09:00

Серверы и 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С

Zabbix тревожит сервер 1С: ложная тревога и настоящая настройка памяти SQL

Серверы клиентов у нас под мониторингом Zabbix: он пишет в Telegram, когда что-то идёт не так. Один из серверов 1С стал регулярно присылать предупреждение «Memory Pages/sec is too high (over 1000 for 5m)» со значениями около 4000.

Серверы и 1С

Медицинская система только по секретной ссылке: обратный прокси с ежемесячной ротацией токена

Медицинская организация пользуется облачной медицинской информационной системой (МИС). Вендор системы умеет ограничивать доступ по IP-адресу, но у клиники несколько площадок, сотрудники работают и из дома, и с мобильных устройств, а провайдеры меняют адреса.

Позвонить

Сайт использует cookie: для работы форм, отзывов с Яндекс.Карт и Яндекс.Метрики — чтобы понимать, что улучшить. Подробнее