Оптимизируя серверное окружение для максимальной производительности, обязательно столкнешься с задачей поддержки SSL-сертификатов и оптимизации скорости SSL-защищенного соединения.
Совершенно условно, задачу по настройке поддержки SSL для nginx на Linux-окружении (в примерах используется RedHat/CentOS, но большая часть советов платформо-независимы) можно разбить на несколько составляющих:
- Оптимизация TCP/IP стека, включая размеры TCP окна.
- Оптимизация TLS-стека, включая OpenSSL.
- Конфигурация nginx с учетом настроек производительности, безопасности и обратной совместимости.
Оптимизация TCP/IP стека
Это название совершенно условно, потому что здесь речь пойдет, в основном, только про те системные параметры, которые критически влияют на производительность SSL-соединения.
На тему оптимизации TCP/IP было достаточно много статей (например, такая, почти не потерявшая актуальности, настройка sysctl или исследование алгоритмов выбора размера окна). Базово нужно максимально оптимизировать процесс переговоров о размере TCP пакета (окна), включить syncookies, увеличить размеры буферов. Опционально, можно также уменьшить число попыток восстановления SYN/SYNACK/keepalive.
Более-менее безопасный набор настроек для широкого круга серверов выглядит так:
# Включаем syncookies net.ipv4.tcp_syncookies = 1 # Включаем изменение окна net.ipv4.tcp_window_scaling = 1 # Оптимизация изменения размера TCP окна net.ipv4.tcp_congestion_control = cubic # flush cached window size net.ipv4.route.flush = 1 # Не используем ssthresh от предыдущих соединений net.ipv4.tcp_no_metrics_save = 1 net.ipv4.tcp_moderate_rcvbuf = 1
Самое значительное изменение, которое затрагивает сам SSL, — это размер первоначального окна, с которым сервер начинает общение с клиентом, — initcwnd. По умолчанию (до ядра 2.6.39), этот параметр выставлен в 3 (т.е. первый пакет с данными должен быть не больше 3*1460 байт). Его можно увеличить до 10 (сэкономив один или несколько циклов отправки-получения данных, т.е., как минимум, 2 времени пинга до сервера).
Подробное описание, что такое initcwnd и как его менять. Одной строкой это можно сделать так:
echo '$(echo "ip route change " $(ip route show | grep ^default | sed "s/initcwnd [0-9]*//") " initcwnd 10" | grep $1 2>/dev/null)' >> /sbin/ifup-local
Оптимизация TLS-стека
Эта часть описана в материалах менее глубоко, и кроме Ильи Григорика (автора Is TLS Fast Yet?) этим мало кто занимался. Если в двух словах, то кроме разбиение обычного потока данных на TCP-пакеты, в случае с SSL добавляется промежуточный слой, который также разбивает поток на пакеты.
И первое, что нужно сделать, — это соотнести TLS-пакеты с TCP-пакетами. Илья предлагает установить его по размеру TCP-пакета, чтобы исключить пересылку всего TLS-пакета при потери одного из TCP-пакетов, передающих TLS-пакет. Для nginx при этом нужно изменить в src/event/ngx_event_openssl.h константу NGX_SSL_BUFSIZE
на 1460 и пересобрать nginx (его в любом случае нужно будет пересобирать, чтобы включить ssl/spdy, но об этом дальше). Подробнее об оптимизации TLS буферов.
Как верно отметили в дискуссии nginx, при большом значении initcwnd указанная оптимизация может быть излишней. Начиная с версии 1.5.9, размер TSL-буфера можно задать настройкой nginx (что чуть далее по тексту и будет сделано). Если вы используете nginx до версии 1.5.9, то NGX_SSL_BUFSIZE
можно выставить в 8096 (спасибо
Дополнительно в версии nginx, начиная с 1.5.7, устранена проблема маленького буфера для SSL-сертификатов. Еще одна причина обновиться до последней стабильной версии. D
По аналогии с TCP Slow Start нужно будет устранить проблему TLS Slow Start (т.е. задержку при отправке начального объема данных). Для этой цели нужен OpenSSL версии не ниже 1.0.1а (а без уже найденных уязвимостей — 1.0.1i). Версия 1.0.1 также понадобится для поддержки SPDY. Оптимизация nginx для работы с TSL.
Все это приводит к ускорению установления SSL-соединения в 2-3 раза.
В качестве быстрого списка для проверки, все ли оптимизировано, можно использовать следующий:
- Обновить ядро до последней версии (Linux: 3.2+).
- Убедиться, что размер окна
cwnd
выставлен в 10. - Устранить проблему slow-start.
- Убедиться, что изменение окна включено (window scaling).
- Устранить передачу ненужной информации.
- Сжать передаваемые данные.
- Расположить сервера максимально близко к пользователю для уменьшения времени пинга.
- Использовать уже установленные TCP-соединения во всех возможных случаях.
Оптимизация nginx + OpenSSL
Да, базовая вещь — это собрать nginx с поддержкой SSL и SPDY. Понадобится OpenSSL не ниже 1.0.1, если из пакетов не устанавливается — нужно будет собирать вручную. В качество хинта при конфигурации используйте следующую строку:
./configure --with-md5-passwords --with-ldflags=-lcrypt --prefix=/usr --sysconfdir=/etc
Это позволит установить OpenSSL поверх текущего системного и использовать для сборки nginx.
При сборке nginx указываем модули:
--with-http_spdy_module \ --with-http_ssl_module
Если вы хотите защитить SSL от атак типа BREACH, можно дополнительно воспользоваться модулем nginx-hiding-length (не отключать же gzip-сжатие из-за этого).
После сборки nginx для хоста, для которого у нас есть секретный ключ (SCR, вопрос об использовании эллиптических кривых для его генерации остается за рамками статьи) и публичный ключ (сертификат), нужно подготовить примерно такую конфигурацию (блок server):
# включаем SSL-соединение на 443 порту с поддержкой listen 443 ssl spdy; # включаем наиболее безопасные протоколы ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # включаем скрепление сертификатов ssl_stapling on; ssl_stapling_verify on; # ускоряем получение данных при установлении соединения, http://nginx.org/ru/docs/http/ngx_http_ssl_module.html#ssl_buffer_size ssl_buffer_size 8k; # локальный список корневых сертификатов, нужен для удостоверения скрепления сертификатов ssl_trusted_certificate /etc/pki/tls/certs/ca-bundle.crt; # список SSL-шифров ssl_ciphers "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:EDH+aRSA:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4"; # отдаем предпочтения серверным шифрам (для большей стойкости) ssl_prefer_server_ciphers on; # назначаем время жизни SSL-сессий ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # добавляем заголовок для использования только HTTPS при наличии HTTP для того же хоста - чтобы исключить взлом в обход HTTPS add_header Strict-Transport-Security 'max-age=15552000'; # Включаем случайную строку в ответ length_hiding on;
Существует много рекомендаций по набору SSL шифров (с учетом Forward Secrecy): от Mozilla, от SSLLabs, от hasGeek и другие. Более подробно можно прочитать в статье про настройку nginx. Предлагаемый вариант базируется на рекомендациях от SSLLabs с отключением RC4 как совсем не безопасного. Указанные настройки позволяют добиться оценки 95 по тесту Qualsys.
Чтобы проверить, какие из этих шифров поддерживаются вашей OpenSSL, можно выполнить:
openssl ciphers -V 'EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4'
При этом шифр 3DES не рекомендуется подключать из соображений производительности (еще один вектор DDoS-атаки в связи с большой сложностью шифра, исследование скорости разных шифров).
Скрепление сертификатов (OCSP Stapling) позволяет существенно сократить время на удостоверение сертификата домена. Цепочка сертификатов (доменный — промежуточный центр авторизации — корневой центр авторизации) может содержать 3-4 уровня. И на каждый уровень браузер должен устанавливать соединение и получать сертификат. Можно отправить все сертификаты (включая промежуточный: именно за этим была настройка TCP-окон отправки, чтобы цепочка сертификатов гарантированно поместилась в в одну пересылку пакетов) разом, тогда браузер проверит всю цепочку локально, а запросит только корневой (который в большинстве случаев уже находится на клиенте).
В принципе, список основных действий для оптимизации SSL на этом заканчивается:
- Применить действия по оптимизации TCP.
- Обновить TLS-библиотеки до последней версии, (пере)собрать сервер с их поддержкой.
- Включить и настроить кэширования и восстановления SSL-сессий.
- Проверить настройки оптимального кэширования SSL-сессий.
- Настроить шифры forward secrecy для включения TLS False Start.
- Завершать TLS-сессии максимально близко к пользователям.
- Использовать динамические TLS-окна для оптимизации задержек/пропускной способности.
- Проверить, что размер файла сертификатов не превышает начального размера TCP-окна.
- Оставить в цепочке сертификатов только необходимые.
- Включить скрепление сертификатов (OCSP stapling).
- Отключить TLS-сжатие.
- Добавить поддержку SNI.
- Добавить заголовок HTTP Strict Transport Security.
За кадром остается вопрос о динамическом TLS-окне (небольшом при начале соединения, возрастающем по мере использования и схлопывающимся после простоя), безопасном использовании TLS-сжатия, стойкости шифрования эллиптическими кривыми (ECC) и их использование (спасибо
P.S. Источники картинок: yuridejager.wordpress.com, www.igvita.com, zombe.es, chimera.labs.oreilly.com