Nginx перед Cloudflare

25 июня 2019

Хотя мы не заявляем услугу Anti-DDOS в составе нашего CDN, сама суть CDN такова, что большинство типов атак останавливается на входных узлах и не доходит до клиентов.

К ним относятся:

  • все виды ICMP, UDP и SYN-флуда;
  • сканирование портов для поиска уязвимых сервисов;
  • перегрузка веб-сервера типовыми запросами.

В распоряжении атакующих остаётся сравнительно узкий набор атак на прикладной уровень, универсальная защита против которых действительно относится к компетенции профессиональных Anti-DDOS.

Часть из них CDN так же способен смягчить — за счёт кэширования и динамической фильтрации атакуемых URL на узлах CDN.

Благодаря этому достаточно распространена ситуация, когда клиент подключается к нам не с нуля, а переходит с других сервисов, в том числе предоставлящих Anti-DDOS.

Ничто не омрачало данного процесса до тех пор, пока очередной потенциальный мигрант не захотел перепрыгнуть пропасть в два прыжка:

  • на первом мы должны были использовать в качестве клиентского сайта входные серверы Cloudflare, которым клиент пользовался до нас,
  • и лишь убедившись в отсутствии проблем, клиент указывал нам свой настоящий IP, убирая промежуточное звено в лице Cloudflare.

Поскольку желание клиентов — для нас закон, мы с энтузиазмом приступили к настройке, но немедленно столкнулись со следующей проблемой:

  • оригинальные URL вида https://sitename.ru/file.jpg скачиваются нормально,
  • но запросы вида https://user123456.clients-cdnnow.ru/file.jpg, т.е. проксируемые через наш CDN к оригинальному URL, возвращают ошибку 502.

Включение отладки в Nginx'e показало, что проблема кроется на нашей стороне (длинные строки из error.log разбиты на несколько строк для удобства восприятия):

2018/02/27 20:26:07 [error] 4808#4808: *108897463 SSL_do_handshake() failed
(SSL: error:10000410:SSL routines:OPENSSL_internal:SSLV3_ALERT_HANDSHAKE_FAILURE:SSL alert number 40 error:1000009a:SSL routines:OPENSSL_internal:HANDSHAKE_FAILURE_ON_CLIENT_HELLO)
while SSL handshaking to upstream, client: 10.20.30.40, server: user123456.clients-cdnnow.ru,
request: "GET /file.jpg HTTP/1.1",
upstream: "https://104.28.3.17:443/file.jpg",
host: "user123456.clients-cdnnow.ru"

2018/02/27 20:26:07 [warn] 4808#4808: *108897463 upstream server temporarily disabled while SSL handshaking to upstream,
client: 10.20.30.40,
server: user123456.clients-cdnnow.ru,
request: "GET /file.jpg HTTP/1.1",
upstream: "https://104.28.3.17:443/file.jpg",
host: "user123456.clients-cdnnow.ru"

Сначала мы решили, что используем слишком старую версию SSL-протокола, от которой Cloudflare отказался, но эта версия не подтвердилась:

  • директива proxy_ssl_protocols у нас отсутствовала,
  • а значение по умолчанию “TLSv1 TLSv1.1 TLSv1.2” было для Cloudflare приемлемым.

К счастью, внимательное чтение журналов Nginx'a дало нам ключ к пониманию проблемы:

  • Когда Nginx устанавливает соединение с upstream'ом, в HTTP-запросе он передаёт поле “Host:”.
  • По умолчанию в поле “Host:” подставляется имя upstream'a — и это правильно.
  • Однако при использовании HTTPS перед отправкой HTTP-запроса устанавливается SSL-соединение.
  • При установке SSL-соединения так же передаётся имя целевого сервера — через т.н. SNI.
  • И по умолчанию Nginx передаёт в SNI не имя upstream'a, а свой собственный server_name, т.е. в нашем случае user123456.clients-cdnnow.ru, про который CF не знает.
  • Из-за неизвестного имени в SNI CF разрывает SSL-сессию до того, как наш Nginx сумеет передать ему правильное имя в HTTP-запросе.
  • Поэтому проблема не проявлялась до тех пор, пока клиенты мигрировали к нам с Cloudflare в один шаг, или в два шага, но используя только HTTP без HTTPS.

Решение оказалось примитивным — перед proxy_pass добавлены две строки:

proxy_ssl_server_name  on;
proxy_ssl_name         $proxy_host;

Переменная “proxy_host” у нас уже установлена в клиентское имя сайта, т.е. “sitename.ru”.

Выводы:

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


← Назад в Блог

Подпишитесь на новые статьи: