новая версия модуля 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