Nginx: proxy_protocol + обычное соединения + X-Real-IP header, как заставить работать совместно?

Возникла необходимость пробросить (NAT) TCP соединения на уже работающий по внешнему адресу Nginx через HAProxy. Само собой, в таком случае мы начинаем получать IP сервера с HAProxy.

Прочитав документацию, было решено попробовать использовать PROXY Protocol (нынче поддерживается и амазоном и т.п.).
Добавил в server {} еще один listen XX proxy_protocol; и направил HAProxy на него.
realip_header пришлось настроить равным proxy_protocol (вместо "обычного" X-Real-IP) и тут начинаем получать веселые проблемы. Т.к. соединения проксируются через proxy_pass к бекенду, на него также передается реальный адрес посетителя через заголовок X-Real-IP. Однако, если в нем использовать просто $remote_addr как раньше, на выходе получаем правильный адрес только если человек прошел напрямую на Nginx, в связке HAProxy->Nginx (proxy_protocol) получаем IP HAProxy... Если взять $proxy_protocol_addr, тогда уже человек, пришедший напрямую, вообще не будет его иметь.

Попытался поиграться с map и проверкой наличия proxy_protocol_addr, но это не срабатывает все равно.

Может кто-то сталкивался с подобной проблемой? Это пока только базовый вопрос - дальше нужно еще хуже связку настроить:
HAProxy->Nginx (proxy_protocol)
Nginx->Nginx (regular)
Direct to Nginx
И во всех случаях найти реальный адрес клиента...

Если будут нужны какие-то конфиги, могу предоставить.

Спасибо за внимание.

UPDATE: Решено самим собой... Комментарий выделяю. Если будет решение лучше - с радостью приму и обновлю.
  • Вопрос задан
  • 2113 просмотров
Решения вопроса 1
@alexkuzko Автор вопроса
Вот финальное решение, из минусов:

1) Опирается на CF-Connecting-IP (это из Cloudlflare)

2) В access_log будет логгироваться IP внешнего Nginx, хотя в конце будет виден реальный клиентский IP ($http_x_forwarded_for), можно заменить log_format

3) Багофича: если вынести все proxy_set_header за пределы вирт.хоста (server {}), то они НЕ применяются в proxy_pass. Так как не обрабатываются в его контексте. Причина: map это динамическая штука согласно документации (Since variables are evaluated only when they are used, the mere declaration even of a large number of “map” variables does not add any extra costs to request processing) и она запускается на обработку только если "нужна"... Почему Nginx считает что она не нужна если описана в nginx.conf (и даже корректно используется в log_format), а не в server {}? Видимо на то есть причины.

Итак, вначале общие настройки:
===== nginx.conf OR conf.d/000-realip.conf =====
set_real_ip_from 1.1.1.1/24; # haproxies internal
set_real_ip_from 2.2.2.xxx; # caravan
real_ip_header proxy_protocol; # using proxy_protocol as header, it cannot be dynamically set

# check CF-Connecting-IP, use $remote_addr as fallback
map $http_cf_connecting_ip $cf_ip_addr {
"" $remote_addr;
default $http_cf_connecting_ip;
}

# dynamic address for further X-Real-IP header passed to backend
map $proxy_protocol_addr $real_ip_addr {
"" $cf_ip_addr;
default $proxy_protocol_addr;
}
# updated log_format, make sure to have it before using in access_log
log_format main '$real_ip_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;

===== nginx.conf OR conf.d/000-realip.conf =====

===== nginx.conf OR conf.d/001-vhost.conf =====
server {
listen 80;
listen 81 proxy_protocol;

server_name DOMAIN www.DOMAIN;
location /
{
proxy_pass http://DOMAIN_UPSTREAM;
proxy_set_header Connection "";
proxy_pass_header Set-Cookie;
proxy_set_header Host $host;
proxy_set_header Cookie $http_cookie;

proxy_set_header X-Real-IP $real_ip_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

}
===== nginx.conf OR conf.d/001-vhost.conf =====

Результат (по логам Nginx):
HAProxy->Nginx: 3.3.3.3 - - [22/Feb/2017:23:37:35 +0300] "GET /p.php HTTP/1.1" 200 1340 "-" "curl/7.38.0" "-"
Nginx->Nginx: 2.2.2.2 - - [22/Feb/2017:23:37:49 +0300] "GET /p.php HTTP/1.1" 200 1400 "-" "curl/7.38.0" "3.3.3.3"
Direct to Nginx: 3.3.3.3 - - [22/Feb/2017:23:37:58 +0300] "GET /p.php HTTP/1.1" 200 1340 "-" "curl/7.38.0" "-"

3.3.3.3 - это реальный IP клиента, 2.2.2.2 это IP внешнего Nginx.

Как видно, только при использовании внешнего Nginx вы не можем переопределить $remote_addr и не можем его заменить путем использования realip модуля т.к. он у нас настроен на proxy_protocol. Но даже в этом случае у нас остается бекап в виде $http_x_forwarded_for. Это что касается логов (тем более что там можно тоже заюзать $real_ip_addr что я в итоге и сделал). Сам по себе бекенд теперь всегда получает нужный X-Real-IP и его гео-модуль также работает правильно.

Очень надеюсь что такой странный кейс поможет еще кому-то ;)
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
@miksir
IT
Интересно. Пока вижу только два варианта. 1) не использовать прокси протокол, а просто ставить на haproxy заголовок X-Real-Ip и его расшифровывать на nginx. 2) Описать два сервера на разных портах, один для прокси протокола на кастомном порту, другой для обычных 80/443.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы