Fail2ban для VPN: защита от брутфорса
Как только VPN-сервер поднят и порт открыт наружу, его тут же находят сканеры — сначала просто щупают порт, потом пробуют ключи tls-auth наугад, а если у вас EAP-авторизация по логину и паролю, начинают перебирать учётки. SSH обычно закрывают fail2ban в первую очередь, а VPN-порт часто остаётся без присмотра, хотя ловить в нём атаки даже проще: трафик узнаваемый, а неудачные попытки хорошо видно в логах OpenVPN и strongSwan. Разберём, как настроить fail2ban под логи OpenVPN и IKEv2, чтобы банить адреса, которые долбят порт подбором.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Почему VPN нуждается в отдельной защите
Fail2ban не привязан к SSH — это универсальный движок, который читает лог по шаблону и банит источник через фаервол. Для VPN логика та же, но применяется к другому трафику. У OpenVPN на UDP-порту 1194 фоновый шум — это в основном пакеты без правильного ключа tls-auth или с недействительным сертификатом: боты и сканеры интернета простукивают открытые UDP-порты пачками, и без tls-crypt каждый такой пакет доходит до демона и оставляет след в логе. У IKEv2 картина похожая: попытки установить IKE_SA без валидной конфигурации пира или с неверным PSK, а если включена EAP-авторизация по логину и паролю — прямой перебор учётных данных.
Разница с SSH в том, что готового рецепта на все случаи меньше: fail2ban исторически силён именно для SSH и веб-серверов, а VPN-фильтры чаще приходится писать или адаптировать самостоятельно под свой формат логов. OpenVPN здесь повезло больше — фильтр идёт в комплекте с fail2ban. Для strongSwan официального фильтра нет, поэтому под IKEv2 фильтр collectively пишут администраторы, и он требует более внимательной проверки перед тем, как доверять ему банить реальных клиентов.
Где искать неудачные попытки в логах
Прежде чем писать фильтр, нужно понять, куда демон вообще пишет события. Если OpenVPN установлен как systemd-инстанс (например, через скрипт openvpn-install, который поднимает сервис openvpn-server@server), лог идёт в journald, а не в отдельный файл:
journalctl -u openvpn-server@server --no-pager -n 50
journalctl -u openvpn-server@server -f
Если в server.conf явно прописана директива log-append /var/log/openvpn/openvpn.log, события дублируются и в файл — тогда фильтр можно натравить на путь вместо journald. Для strongSwan в классической схеме с ipsec.conf и демоном strongswan-starter лог тоже смотрят через journalctl:
journalctl -u strongswan-starter --no-pager -n 50
journalctl -u strongswan-starter -f
Точное имя unit'а стоит перепроверить командой systemctl list-units | grep -i strong — на разных версиях strongSwan это может быть strongswan-starter.service, strongswan.service или, при переходе на swanctl/VICI, strongswan-swanctl.service. Прежде чем писать фильтр, полезно вручную поймать несколько неудачных подключений (например, подключиться с заведомо неверным паролем) и посмотреть, как именно это выглядит в логе именно на вашей системе — форматы отличаются между версиями пакетов.
Арендуйте сервер под свои задачи!
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверФильтр fail2ban для OpenVPN
Fail2ban поставляется с готовым фильтром openvpn, который ищет ошибки TLS-хендшейка — неверный tls-auth/tls-crypt ключ, битый сертификат, обрыв соединения по SIGUSR1. Файл /etc/fail2ban/filter.d/openvpn.conf выглядит так:
[INCLUDES]
before = common.conf
[Definition]
_daemon = ovpn-server\d*
failregex = ^%(__prefix_line)s<HOST>:\d{4,5} (?:TLS Auth Error:|VERIFY ERROR:|TLS Error: TLS handshake failed\b|SIGUSR1\[soft,connection-reset\] received\b)
^%(__prefix_line)sTLS Error: cannot locate HMAC in incoming packet from \[AF_INET\]\s*<HOST>:\d{4,5}
ignoreregex =
Менять его не нужно — достаточно включить джейл в /etc/fail2ban/jail.local, указав, откуда читать лог. Для journald-варианта:
[openvpn]
enabled = true
port = 1194
protocol = udp
filter = openvpn
backend = systemd
journalmatch = _SYSTEMD_UNIT=openvpn-server@server.service
maxretry = 3
findtime = 10m
bantime = 1h
Если вместо journald используете файл лога, замените backend/journalmatch на обычный logpath = /var/log/openvpn/openvpn.log. Обратите внимание на port и protocol — для UDP-сервисов их нужно указывать явно, иначе действие бана применится некорректно. Большинство фильтров и джейлов работают с TCP по умолчанию, и это одна из типичных причин, почему бан для UDP-сервиса на бумаге настроен, а по факту не режет трафик.
Фильтр fail2ban для IKEv2/strongSwan
Готового фильтра под strongSwan в поставке fail2ban нет, поэтому фильтр приходится писать самостоятельно и обязательно проверять на своих логах перед тем, как включать бан в боевом режиме. Ниже — рабочая отправная точка, собранная из типичных строк charon про неудачную авторизацию: AUTHENTICATION_FAILED, отсутствие конфигурации пира и неизвестный сертификат. Создайте /etc/fail2ban/filter.d/strongswan-ikev2.conf:
[Definition]
maxlines = 10
failregex = ^.*charon:.*AUTH_FAILED.*\n.*to <HOST>
^.*charon:.*no peer config found.*\n.*\n.*sending packet.*to <HOST>
^.*charon:.*certificate unknown.*\n.*\n.*to <HOST>
ignoreregex =
Это многострочный фильтр: событие в логе charon размазано по нескольким строкам, а адрес атакующего часто оказывается на строке sending packet ... to <HOST>, а не там, где написано про саму ошибку. Параметр maxlines задаёт, сколько строк fail2ban держит в буфере для сборки одного совпадения — если у вас в логе события расположены дальше друг от друга, увеличьте значение. На результат сильно влияет verbosity демона: charondebug в /etc/ipsec.conf или strongswan.conf управляет тем, сколько подробностей charon вообще пишет в лог. Если фильтр не даёт совпадений, а неудачные попытки в логе видно глазами, для начала поднимите уровень отдельных групп, например charondebug="cfg 1, ike 2, net 1", и посмотрите, не станет ли нужной строки больше.
Джейл для IKEv2 указывает оба порта протокола — 500 для начального обмена и 4500 для NAT-T:
[strongswan-ikev2]
enabled = true
port = 500,4500
protocol = udp
filter = strongswan-ikev2
backend = systemd
journalmatch = _SYSTEMD_UNIT=strongswan-starter.service
maxretry = 3
findtime = 20m
bantime = 1h
Прежде чем полагаться на этот джейл в проде, обязательно прогоните фильтр через fail2ban-regex на реальном фрагменте лога — ниже в разделе про проверку показано, как это сделать. Ложные срабатывания на нестандартной сборке заметно вероятнее, чем на встроенном фильтре OpenVPN.
Пороги, бан и действие
Для VPN-портов фоновый шум обычно выше, чем для SSH: боты простукивают открытые UDP-порты пачками, не пытаясь ничего подобрать осмысленно, — они просто проверяют, отвечает ли порт. Это создаёт много формальных "неудач" в логе OpenVPN даже без реального перебора паролей. Практичный компромисс — держать порог чуть жёстче, чем для SSH, потому что случайных ошибок у легитимного клиента при подключении к VPN обычно меньше, чем опечаток пароля при логине:
| Параметр | SSH (для сравнения) | OpenVPN | IKEv2 |
|---|---|---|---|
| maxretry | 5 | 3 | 3 |
| findtime | 10m | 10m | 20m |
| bantime | 1h | 1h | 1h |
Для узлов, где повторные нарушители возвращаются с одного диапазона снова и снова, включите растущий бан — bantime.increment = true и bantime.factor в секции [DEFAULT]: каждый следующий бан того же адреса будет длиннее предыдущего. Действие бана обычно наследуется из /etc/fail2ban/jail.conf (banaction, чаще всего nftables-multiport или iptables-multiport в зависимости от дистрибутива) — оно само подставляет port и protocol из секции джейла, отдельно их дублировать в action не нужно, если вы задали их в самом джейле, как в примерах выше.
Отдельно добавьте в ignoreip в [DEFAULT] свои доверенные адреса — офисный или домашний IP, с которого вы сами тестируете подключение. При отладке фильтра легко несколько раз подряд ввести неверный пароль или переподключиться с разорванным tls-auth и забанить самого себя.
Проверка работы и типичные ошибки
Перед тем как включать джейл на боевом трафике, проверьте фильтр на реальном куске лога командой fail2ban-regex — она покажет, сколько строк совпало и какие именно:
fail2ban-regex "$(journalctl -u openvpn-server@server -n 500 --no-pager)" /etc/fail2ban/filter.d/openvpn.conf
fail2ban-regex "$(journalctl -u strongswan-starter -n 500 --no-pager)" /etc/fail2ban/filter.d/strongswan-ikev2.conf
Если совпадений ноль, а неудачные попытки в логе точно есть — это почти всегда одна из трёх причин: демон пишет с другим syslog-идентификатором, чем ожидает _daemon в фильтре OpenVPN; юнит в journalmatch назван не так, как в системе; или verbosity charon слишком низкая и нужной строки просто нет в логе. После правки перезапустите fail2ban и проверьте статус джейлов:
systemctl restart fail2ban
fail2ban-client status
fail2ban-client status openvpn
fail2ban-client status strongswan-ikev2
fail2ban-client status <jail> покажет число замеченных попыток и список забаненных адресов — это главный индикатор, что защита реально работает, а не просто числится включённой. Если по ошибке забанили себя или тестовый клиент, разбан делается одной командой:
fail2ban-client set openvpn unbanip ВАШ_IP
Ещё один нюанс, который легко упустить: для UDP fail2ban не видит "успешных" попыток отдельно от "неудачных" в том смысле, в каком видит SSH, — он ориентируется только на строки-маркеры ошибок в логе. Это значит, что фильтр можно точечно проверить на конкретный тип атаки, но нельзя рассчитывать, что он поймает абсолютно все формы злоупотребления протоколом. Базовые принципы подробнее разобраны в статье про установку и настройку fail2ban на VPS, а с типовыми граблями самого fail2ban — не банит, не стартует, путает бэкенд — поможет статья частые ошибки и решения по fail2ban.
Арендуйте сервер под свои задачи!
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Нужен ли fail2ban для VPN, если уже настроен для SSH?
Да, это разные джейлы для разных портов. SSH-джейл не видит и не банит трафик на UDP 1194 или 500/4500 — под каждый сервис нужен свой фильтр и своя секция в jail.local.
Почему в логе OpenVPN так много "неудачных" попыток без перебора пароля?
Это фоновое сканирование интернета: боты простукивают открытые UDP-порты и шлют пакеты без правильного tls-auth/tls-crypt ключа. Такие пакеты доходят до демона и оставляют строку TLS Error, даже если никто не пытался подобрать пароль осмысленно.
Фильтр для strongSwan не даёт совпадений — что проверить в первую очередь?
Убедитесь, что unit в journalmatch назван правильно (systemctl list-units | grep -i strong), и поднимите verbosity в charondebug — при заниженном уровне логирования нужных строк в логе может просто не быть.
Можно ли использовать один и тот же jail.local для OpenVPN и IKEv2 на одном сервере?
Да, это просто две (или больше) отдельные секции в одном файле jail.local — они работают независимо, у каждой свой filter, port и logpath/journalmatch.
Как не забанить самого себя при отладке?
Добавьте свой текущий IP в ignoreip в секции DEFAULT перед тем, как тестировать неудачные подключения. Если уже забанили — разбаньте командой fail2ban-client set <jail> unbanip.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →