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

    @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 и его гео-модуль также работает правильно.

    Очень надеюсь что такой странный кейс поможет еще кому-то ;)
    Ответ написан
    Комментировать