новая версия модуля ngx_http_realip_module
Gena Makhomed
gmm на csdoc.com
Вс Июн 11 23:31:04 UTC 2023
On 09.06.2023 9:29, Maxim Dounin wrote:
>> (1) client ==> vps_server ==> main_server
>>
>> (2) client ==> cloudflare => vps_server ==> main_server
> Если у вас в обоих случаях vps_server проксирует всё через stream
> с proxy_protocol, то на принимающей стороне вам в любом случае
> надо сначала достать реальный адрес клиента из proxy_protocol.
> А уже потом смотреть в заголовки (или не смотреть, если на
> vps_server пришли не с IP-адресов Cloudflare).
>
> То есть, фактически, для корректной работы такой схемы - нужен
> "real_ip_recursive on;" (http://nginx.org/r/real_ip_recursive)
> и заголовок со списком нужных адресов.
>
> Сейчас из коробки такое можно сделать дополнительным
> проксированием с установкой заголовка. Для стандартного заголовка
> X-Forwarded-For, благо Cloudflare его ставит, конфигурация будет
> выглядеть как-то так:
>
> server {
> listen 8080 proxy_protocol;
>
> set_real_ip_from <vps>;
> real_ip_header proxy_protocol;
>
> location / {
> proxy_pass http://127.0.0.1:8081;
> proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
> }
> }
>
> server {
> listen 8081;
>
> set_real_ip_from 127.0.0.1;
> set_real_ip_from <cloudflare>;
>
> real_ip_header X-Forwarded-For;
> real_ip_recursive on;
>
> ...
> }
Это будет работать только в том случае, если система защиты от DDoS
указывает IP адрес клиента в заголовке X-Forwarded-For. Если же какая-то
экзотическая система защиты от DDoS будет указывать реальный IP клиента
в каком-то другом заголовке, например, только в загловке X-Real-IP -
тогда этот метод работать не будет, потому что в nginx есть переменная
$proxy_add_x_forwarded_for но в nginx нет перменной $proxy_add_x_real_ip
Что же нам делать в том случае, если какая-то система защиты от DDoS
передает реальный IP клиента в заголовке отличном от X-Forwarded-For ?
>> Или существует какой-то еще лучший вариант решения этой задачи?
>
> Я периодически думаю о том, чтобы научить модуль realip брать
> список IP-адресов не из заголовка, а непосредственно из
> переменной. Тогда необходимость в дополнительном проксировании в
> подобных странных конфигурациях отпадёт.
Каким же образом тут можно будет обойтись без двойного проксирования,
если необходимо будет сначала указывать real_ip_header proxy_protocol;
чтобы узнать реальный IP сервера cloudflare, который подключился к VPS,
и при этом - несколько директив real_ip_header нельзя указывать в одном
контексте. А если указать одну директиву real_ip_header одновременно
и в контексте server и одну в контексте location, то согласно правил
наследования директив в nginx - директива real_ip_header в контексте
location будет выполняться, а директива real_ip_header в контексте
server выполняться не будет, и всегда будет просто проигнорирована.
Максим, можете пояснить в чем тут моя ошибка и что я не так понял?
> Но в целом это выглядит как достаточно маргинальный use case,
> IMHO, и доступное сейчас решение с дополнительным проксированием
> ему плюс-минус адекватно.
Максим, я понимаю, что эта схема подключения основного сервера
(1) client ==> vps_server ==> main_server
(2) client ==> cloudflare => vps_server ==> main_server
выглядит для Вас достаточно маргинальным вариантом, но у меня
просто нет другого выхода, потому что был уже раньше случай,
когда промежуточного tcp-прокси на vps_server не было - и когда
пришла мощная DDoS-атака на уровне L3/L4 - хостер просто отключил
сервер от интернета, сделав black hole routing для main_server IP.
В этой же, новой схеме - промежуточных vps_server может быть несколько
штук, так что если придет какая-то мощная DDoS-атака на уровне L3/L4
на vps_server - то от интернета будет отключен только этот один
vps_server, и перестанет работать только часть сайтов, которые
указывают на IP адрес этого vps_server. А все остальные сайты,
которые указывают на другие vps_server, проксирующие запросы
на main_server через tcp proxy - будут работать и дальше,
кроме того main_server в такой схеме точно никогда не будет
отключен от хостером интернета, потому что злоумышленники
не будут знать его IP адрес, он будет более-менее надежно
скрыт от всех пользователей. Способ простой - можно взять
еще одну VPS за 5 USD и пустить весь исходящий трафик через
нее, тогда весь исходящий трафик от main_server будет идти
в интернет через эту VPS и в случае DDoS-атаки на нее -
основной сервер продолжит работать как и раньше, и даже более того,
изменив настройки в конфиге WireGuard на main_server можно будет
пустить исходящий трафик от main_server через совсем другую VPS.
В среднем самая дешевая vps_server на KVM стоит около 5 USD в месяц,
dedicated bare metal main_server стоит около 200-300-400 USD в месяц,
поэтому с моей точки зрения - гораздо лучше будет иметь десять
промежуточных vps_server и в случае мощной L3/L4 DDoS-атаки иметь
Denial of Service только на 1/10 часть сайтов, вместо того,
чтобы иметь Denial of Service на все 100% сайтов, которые
размещены на main_server, очень сильно рискуя при этом что хостер
не выдержит и вообще отключит весь сервер main_server от интернета.
А Cloudflare, Variti, StormWall и другие сервисы защиты от DDoS -
это просто дополнительный уровень защиты, который позволит терминировать
все L3/L4 DDoS-атаки на уровне системы защиты от DDoS, так что через
vps_server на main_server придет только часть L7 атаки, с которой
main_server уже сможет справить самостоятельно, например, с помощью
https://github.com/makhomed/autofilter или с помощью встроенных в nginx
модулей ngx_http_limit_conn_module и / или ngx_http_limit_req_module
Поэтому идеальным вариантом решения для меня в такой ситуации -
была бы возможность указывать в конфиге одновременно все используемые
системы защиты от DDoS, чтобы сайтам можно было бы в любой момент
времени прозрачно переключаться между ними, вообще не внося никаких
изменений в конфигурацию nginx на хостинговом сервере main_server
и на всех промежуточных tcp-прокси vps_server.
Можно ли это сделать? Да, эта задача имеет 100% решение.
Например, такой расширенный вариант синтаксиса:
set_real_ip_from <cloudflare> real_ip_header CF-Connecting-IP;
set_real_ip_from <cloudflare> real_ip_header CF-Connecting-IP;
set_real_ip_from <cloudflare> real_ip_header CF-Connecting-IP;
set_real_ip_from <variti> real_ip_header X-Forwarded-For;
set_real_ip_from <variti> real_ip_header X-Forwarded-For;
set_real_ip_from <variti> real_ip_header X-Forwarded-For;
set_real_ip_from <stormwall> real_ip_header X-Forwarded-For;
set_real_ip_from <stormwall> real_ip_header X-Forwarded-For;
set_real_ip_from <stormwall> real_ip_header X-Forwarded-For;
То есть, сейчас модуль ngx_http_realip_module имеет три директивы:
set_real_ip_from
real_ip_header
real_ip_recursive
В текущей версии модуля - директив set_real_ip_from
может быть несколько в одном контексте, но при этом
- каждая из этих директив может иметь только одине параметр.
Я предлагаю 100% обратно совместимый метод добавления дополнительной
функциональности:
добавить также варианты директив set_real_ip_from
с тремя и пятью параметрами:
set_real_ip_from <address> real_ip_header <header>
set_real_ip_from <address> real_ip_header <header> real_ip_recursive <x>
таким образом, не добавляя новых директив в nginx можно получить
новую функциональность, сохранив при этом 100% обратную совместимость.
Внутри модуля - хранить адреса из первого аргумента директивы
set_real_ip_from в форме аналогичной тому, как это делает модуль geo,
во внутреннем представлении - "значением" будет структура, состоящая
из двух полей: real_ip_header и real_ip_recursive.
Если в директиве set_real_ip_from указаны все пять параметров -
тогда значения полей real_ip_header и real_ip_recursive заполняются
из дополнительных параметров. Если три или один - тогда отсутствующие
парметры берутся из директив real_ip_header и/или real_ip_recursive.
Таким образом - получается несколько вариантов записи директив
модуля в конфигурационном файле nginx и при этом - всего одно
внутреннее представление, с которым в рантайме работает модуль.
В результате - получится максимально гибкая и максимально быстрая
и максимально удобная версия модуля ngx_http_realip_module,
которая позволит пользователям самостоятельно менять системы
защиты от DDoS, без необходимости вносить какие-либо изменения
в конфиг nginx. Это будет очень удобное свойство/качество,
которое будет заметно облегчать и упрощать жизнь админам
хостинговых серверов и владельцам сайтов.
Максим, вопрос в том, можно ли будет в основной ветке nginx заменить
текущую версию модуля ngx_http_realip_module новой версией модуля,
в которой будет реализован вариант директивы set_real_ip_from
с одним, тремя и пятью параметрами?
Если можно - я попробую самостоятельно создать новую версию
такого модуля ngx_http_realip_module, и буду присылать в список
рассылки nginx-devel варианты модуля Вам на code review, ладно?
Почему я об этом спрашиваю - просто хочу заранее с Вами договориться
- о возможности включения новой версии модуля в основную ветку nginx.
Чтобы не получилась такая ситуация, что я большое количество времени
и сил потрачу зря и что результаты работы придется потом выборосить.
Не знаю, сколько времени у меня займет, чтобы разобраться
в том, как работает nginx и какое у него внутреннее API,
но ведь если решение этой проблемы / задачи больше всех
нужно именно мне, то логично будет именно мне написанием
новой версии модуля ngx_http_realip_module и заниматься.
--
Best regards,
Gena
Подробная информация о списке рассылки nginx-ru