Ниже — две части.
- База — её можно быстро пролистать.
 - Боевая имплементация — там то, что мы хотим на практике.
 
Важно учесть, что настройка Nginx — не только для "наших" задач.
Все наши фреймворки, их middleware и т.д. — тоже рассчитывают на достоверные заголовки. Для корректной работы ихautobanи т.д.
  
  
  База. Зачем нужен Nginx. Логика X-Forwarded-For.
Тестировать будем на starlette.
Создадим простой main.py, который просто рисует http-мету:
from starlette.applications import Starlette
from starlette.responses import HTMLResponse
from starlette.routing import Route
async def home(request):
    return HTMLResponse(f"""<pre>
        url: {str(request.url)}
        client:
            host: {request.client.host}
            port: {request.client.port}
        headers:
            {('<br>' + ' ' * 12).join(k + ': ' + v for k, v in request.headers.items())}
    </pre>""")
app = Starlette(debug=True, routes=[Route('/', home),])
Его можно запустить на сервере:
uvicorn main:app --host=0.0.0.0 --port=2100
И, если DNS настроены:
- Его уже можно открыть в браузере по http://myproject.org:2100
 - Он уже будет знать:
- IP-пользователя в 
request.client.hostвида 'xxx.xxx.xxx.xxx'. - Host-Cервера в 
request.headers.hostвида 'myproject.org:2100'. 
 - IP-пользователя в 
 - Даже ssl/https можно прикрутить.
 
И если обращаться к серверу прям так — http://myproject.org:2100 — без прокси, без VPN-ов — как минимум с заголовками — всё будет нормально.
Но:
- Возможно — нам бы хотелось иметь возможность как-то знать о прокси/VPN-ах.
 - Возможно — нам также хотелось бы — мапить/перемапливать урлы (чтобы по 'myproject.org/api/' открывалось 'api.org/take/some/api/dude/'); эффективней обслуживать статику (не приложением); контролировать — таймауты, размеры файловых аплоудов, размеры текстовых аплоудов; иметь возможность показать пользователю хоть что-то, пока сервер лежит/перезапускается; клёвое логирование и т.д. и т.п.
 
А главное что нам хотелось бы — делать всё это через одну привычную технологию, на чём бы мы не писали наше приложение.
Nginx
Сэмулируем проксирование самих себя (может это мы сами для наших нужд):
server {
    listen 2200;
    location / {
        proxy_pass http://myproject.org:2100;
    }
}
Открываем браузер по http://myproject.org:2200.
В request.client.host лежит уже не IP-Пользователя, а IP-Cервера, который к нам обратился.
Скажем сразу — если доступа к этому конфигу у нас нет — многого мы не сделаем.
Но, можем отловить "чуть более верхнего инициатора запроса" добавив "подконтрольный" нам конфиг:
server {
    listen 2100;
    location / {
        proxy_pass http://myproject.org:2000;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
Запускаемся (но уже с портом 2000):
uvicorn main:app --host=0.0.0.0 --port=2000
Открываем браузер также по http://myproject.org:2200.
Теперь наш сервис дополнительно отображает нам некий request.headers['x-real-ip'] с IP отличающимся от request.client.host.
И если в request.client.host у нас IP-Сервера, то в request.headers['x-real-ip'] похоже IP-Роутера-Сервера. (Но это не важно).
А вот если бы тот первый конфиг был нам подконтролен (или просто настроен хорошо), то мы могли бы в обоих случаях добавить:
server {
    listen 2200;
    location / {
        proxy_pass http://myproject.org:2100;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  # +
        proxy_set_header Host $http_host;  # +
    }
}
server {
    listen 2100;
    location / {
        proxy_pass http://myproject.org:2000;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  # +
        proxy_set_header Host $http_host;  # +
    }
}
И теперь, в http://myproject.org:2200 мы получим:
- В 
request.headers.hostлежит всё как надо — 'myproject.com:2200'. - В 
request.client.hostвсё также лежит IP-Сервера. - В 
request.headers['x-real-ip']лежит всё тот же IP-Роутера-Сервера. - Но, появился ещё 
request.headers['x-forwarded-for'], и это — цепочка IP-шников! Потерянный IP-Пользователя и IP-Роутера-Сервера. 
Т.е., самый достоверный IP-пользователя — это всегда — request.headers['x-forwarded-for'][0].
Боевая имплементация
Нам конечно не нужны такие двойные конфиги.
Просто только так можно было понять 'X-Forwarded-For'.
Что и в какой степени достоверно.
И вот перед нами просто "голый" конфиг:
server {
    listen 2200;
    location / {
        proxy_pass http://headhall.com:2000;
    }
}
По http://myproject.org:2200 мы получим:
Который показывает:
- В 
request.headers.hostлежит 'myproject.org:2000', хотя браузер открыт с ':2200'. - В 
request.client.host— IP-Сервера, а не IP-Пользователя. - 
request.headers['x-real-ip']вообще нет. - 
request.headers['x-forwarded-for']также нет. 
Приводим конфиг в боевое состояние:
server {
    listen 2200;
    location / {
        # Чтобы показывал :2200
        proxy_set_header Host $http_host;
        # Пытаемся получить IP-Пользователя (и получим, если без прокси)
        proxy_set_header X-Real-IP $remote_addr;
        # Ещё пытаемся получить IP-Пользователя (и получим, если прокси наше или "нормальное")
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://headhall.com:2000;
    }
}
(Вместо $http_host конечно можно — и $host (без порта), и $server_name (если хочется) и т.д. Просто $http_host самый полный и универсальный.).
(К слову, все эти proxy_* можно указать и на уровне server).
Закрепим, что мы получим в http://myproject.org:2200:
- В 
request.headers.hostлежит гарантированный 'myproject.org:2200'. - В 
request.client.host— IP-Сервера, а не IP-Пользователя, как можно было ожидать — всегда НЕ надёжный ориентир. - 
request.headers['x-real-ip']— IP-Пользователя, но, всё же — тоже НЕ надёжный ориентир. - 
request.headers['x-forwarded-for'][0]— IP-Пользователя, как — наиболее надёжный ориентир. 
А вот теперь можно уже к — таймаутам, лимиту аплоудов и т.д.
В помощь по всему что выше:
Alphabetical index of variables: https://nginx.org/en/docs/varindex.html
Злой, но золотой комментарий на SO: https://stackoverflow.com/a/72586833/4117781
Удачи, авантюрист!
    
Top comments (2)