Убедитесь, что nginx работает правильно с прокси-протоколом локально

Среда

Я настроил поддержку протокола Proxy на классическом балансировщике нагрузки AWS, как показано здесь, который перенаправляет трафик на внутренние экземпляры nginx (сконфигурированные с ModSecurity).

Все отлично работает, и я могу попасть на мои сайты из открытого интернета.

Теперь, поскольку моя конфигурация nginx выполнена в пользовательских данных AWS, я хочу выполнить некоторые проверки, прежде чем экземпляр начнет обслуживать трафик, который достижим через перехватчики жизненного цикла AWS.

проблема

Перед включением прокси-протокола я проверял работоспособность моего экземпляра nginx, и ModSecurity работает, проверяя 403 ответ от этой команды

$ curl -ks "https://localhost/foo?username=1'%20or%20'1'%20=%20'"

После включения Proxy Protocol я больше не могу этого делать, так как команда завершается с ошибкой ниже, которая ожидается по этой ссылке.

# curl -k https://localhost -v
* About to connect() to localhost port 443 (#0)
*   Trying ::1...
* Connected to localhost (::1) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* NSS error -5938 (PR_END_OF_FILE_ERROR)
* Encountered end of file
* Closing connection 0
curl: (35) Encountered end of file

# cat /var/logs/nginx/error.log
2017/10/26 07:53:08 [error] 45#45: *5348 broken header: "���4"�U�8ۭ�u��%d�z��mRN�[e��<�,�
�+̩�    �0��/̨��98k�̪32g�5=�/<
" while reading PROXY protocol, client: 172.17.0.1, server: 0.0.0.0:443

Какие еще варианты у меня есть, чтобы программно проверить nginx, кроме curl? Может быть, что-то на другом языке?

3 ответа

Решение

Спасибо Тарун за подробное объяснение. Я обсуждал в группе и закончил тем, что создал еще один виртуальный хост nginx на порту 80 и использовал его для проверки ModSecurity, как показано ниже.

curl "http://localhost/foo?username=1'%20or%20'1'%20=%20'"`

Вы можете использовать --haproxy-protocol curl, который добавляет в запрос дополнительную информацию о протоколе прокси.

curl --haproxy-protocol localhost

Так:

curl -ks "https://localhost/foo?username=1'%20or%20'1'%20=%20'"

Прокси-протокол добавляет текстовую строку перед потоковой передачей

PROXY TCP4 127.0.0.1 127.0.0.1 0 8080

Выше приведен пример, но это происходит с самого начала. Теперь, если я слушаю NGINX по SSL и http, используя оба proxy_protocol затем он ожидает увидеть эту строку сначала, а затем любую другую вещь

Так что если

$ curl localhost:81
curl: (52) Empty reply from server

И в логах nginx

web_1  | 2017/10/27 06:35:15 [error] 5#5: *2 broken header: "GET / HTTP/1.1

Если я сделаю

$ printf "PROXY TCP4 127.0.0.1 127.0.0.1 0 80\r\nGET /test/abc\r\n\r\n" | nc localhost 81
You can reach API /test/abc and args_given = ,

Оно работает. Поскольку я могу отправить протокол прокси, он принимает

Теперь в случае SSL, если я использую ниже

printf "PROXY TCP4 127.0.0.1 127.0.0.1 0 8080\r\nGET /test/abc\r\n\r\n" | openssl s_client -connect localhost:8080

Это все равно ошибка

web_1  | 2017/10/27 06:37:27 [error] 5#5: *1 broken header: ",(��   @_5���_'���/��ߗ

Это связано с тем, что клиент пытается сначала выполнить рукопожатие, а не отправлять сначала прокси-протокол, а затем рукопожатие.

Таким образом, вы возможные решения

  1. Завершите SSL на LB, а затем обработайте http на nginx с proxy_protocol и используйте опцию команды nc, которую я разместил
  2. Добавить listen 127.0.0.1:<randomlargeport> и выполнить свой тест, используя то же самое. Это все еще безопасно, так как вы слушаете только localhost
  3. Добавьте другой порт SSL и используйте listen 127.0.0.1:443 ssl а также listen <private_ipv4>:443 ssl proxy_protocol

Все решения в порядке приоритета, на мой выбор, вы можете сделать свой выбор

К сожалению, в моем случае bash-версия не работала, поэтому я написал код на python3:

#!/usr/bin/env python3

import socket
import sys

def check_status(host, port):
    '''Check app status, return True if ok'''
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.settimeout(3)
        s.connect((host, port))
        s.sendall(b'GET /status HTTP/1.1\r\nHost: api.example.com\r\nUser-Agent: curl7.0\r\nAccept: */*\r\n\r\n')
        data = s.recv(1024)
        if data.decode().endswith('OK'):
            return True
        else:
            return False
try:
    status = check_status('127.0.0.1', 80)
except:
    status = False
if status:
    sys.exit(0)
else:
    sys.exit(1)
Другие вопросы по тегам