Хотя мы не заявляем услугу 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 по умолчанию логичны и обоснованы,
- но в этом правиле могут встречаться исключения,
- которые настолько редки, что лишь подтверждают его.