Шифруем всё подряд
Эра незашифрованного веба проходит, и это хорошо. В этой инструкции мы предполагаем, что на вашем сервере работает веб-сервер Nginx. И теперь мы сделаем так, чтобы все посетители сайта пользовались исключительно протоколом HTTPS. Кроме этого мы включим HSTS – это «HTTP Strict Transport Security», когда сайт не только поддерживает HTTPS, но и настаивает на его использовании.
Для этого есть множество способов, но я опишу метод под названием «HTTPS termination». Иначе говоря, мы поставим перед веб-сервером обратный прокси, который и будет обеспечивать HTTPS. Это получается проще и гибче, чем настраивать HTTPS только при помощи возможностей веб-сервера. Возможно, вам покажется контринтуитивным, что добавление ещё одного приложения в стек упростит вашу жизнь – но это действительно так.
Уточним, что данный рецепт подходит для серверов на базе Linux, на которых установлен Nginx.
То, что будет работать прежде всех остальных приложений в стопке – это HAProxy. Это в первую очередь приложение для балансировки – он умеет распределять приходящие запросы между разными физическими серверами. Много высоконагруженных сайтов используют его в этом качестве (тот же reddit), но в последней версии у него появилась возможность выполнять SSL termination. Он умеет устанавливать HTTPS-соединения от имени сервера.
Поэтому мы поставим HAProxy, скормим ему наши сертификаты SSL/TLS, поручим перенапрявлять все HTTP запросы на HTTPS, и покажем ему уже сам веб-сервер в качестве бэкенда.
Установка HAProxy
Порты 80 и 443 будут смотреть в интернет и получать HTTP и HTTPS трафик. Все HTTP запросы получат редирект 301 на тот же URL, но по HTTPS, и затем перенаправятся на бэкендовый веб-сервер (nginx) по чистому HTTP.
HAProxy package, включённый в поставку Ubuntu 14.04 LTS довольно старый, поэтому добавим репозиторий:
sudo add-apt-repository ppa:vbernat/haproxy-1.5
Затем обновим исходники и установим приложение:
sudo aptitude update
sudo aptitude install haproxy
Основные настройки лежат в /etc/haproxy/haproxy.cfg. Вот мои настройки:
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
ssl-default-bind-ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA+RC4:EECDH:EDH+aRSA:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4
ssl-default-bind-options no-sslv3 no-tlsv10
tune.ssl.default-dh-param 4096
defaults
log global
mode http
option httplog
option dontlognull
option forwardfor
option http-server-close
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
frontend yourservername
bind *:80
bind *:443 ssl crt /etc/ssl/private/cert1.pem crt /etc/ssl/private/cert2.pem
acl secure dst_port eq 443
redirect scheme https if !{ ssl_fc }
rspadd Strict-Transport-Security:\ max-age=31536000;\ includeSubDomains;\ preload
rsprep ^Set-Cookie:\ (.*) Set-Cookie:\ \1;\ Secure if secure
default_backend webservername
backend webservername
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
server webservername 192.168.1.50:80
listen stats *:9999
stats enable
stats uri /
Кстати, если вам понадобится подсветка синтаксиса конфига HAProxy для vim, её можно взять здесь. Разберём настройки подробнее.
Global
Оставляем первый кусок нетронутым. Это настройки логов, директория и права доступа.
Следующие части нужно поправить – там указано, где находится CA root и лежат сертификаты SSL/TLS. Возможно, нужно будет поменять ca-base и crt-base.
Строка ssl-default-bind-ciphers определяет, какие коды SSL/TLS будут применяться HAProxy при соединении с клиентом. Я использую рекомендованный список от Qualys/SSL Labs. Я также отредактировал строчку ssl-default-bind-options и запретил SSLv3 и TLS1.0, т.к. они дырявые. Последняя строка, tune.ssl.default-dh-param, сообщает программе о необходимости использовать не более 4096 бит в параметре Diffie-Hellman при обмене ключами DHE.
Defaults
Добавляем пару вещей — forwardfor и http-server-close. Поскольку мы используем приложение в качестве прокси, оно должно сообщать серверу IP-адреса, с которых идут запросы. Иначе это будет выглядеть, будто весь трафик идёт с HAProxy. Поэтому forwardfor сообщает, что программа работает как обратный прокси, и необходимо добавлять заголовок X-Forwarded For для сервера.
Настройка http-server-close нужна для быстродействия — HAProxy будет решать, закрывать ли соединение или использовать его повторно, при этом поддерживая более продвинутые вещи вроде WebSockets.
Certificates
Первая часть секции сообщает, какой трафик HAproxy должна обрабатывать и куда его отправлять.
Мы привязываем HAproxy к портам 80 и 443, она слушает HTTP на порту 80 и HTTPS на порту 443. Для HTTP мы скармливаем ей два разных сертификата. HAproxy использует Server Name Identification (SNI) чтобы привести хост входящего запроса в соответствие с нужным SSL/TLS сертификатом. У меня на сервере есть три сайта, они используют разные групповые сертификаты (*.bigdinosaur.org, *.chroniclesofgeorge.com и *.bigsaur.us) и HAProxy правильно выбирает нужный из них.
Единственное, что надо сделать – объединить файлы сертификата и приватного ключа в один .pem:
cat your-decrypted-ssl-key.key your-ssl-cert.crt > your-ssl-cert.pem
Убедитесь, что владельцем файла будет root:root, и права у него должны быть только на чтение:
sudo chown root:root your-ssl-cert.pem
chmod 400 your-ssl-cert.pem
От HTTP к HTTPS, и HSTS в качестве бонуса
Теперь определим ACL, список контроля доступа. В HAProxy это список вещей, удовлетворяющих определённому критерию:
acl secure dst_port eq 443
Мы создали ACL по имени secure, который совпадает со всем, что идёт на TCP порт 443. Он нам скоро понадобится.
Следующая строка – та самая, где происходит перенаправление трафика с HTTP на HTTPS.
redirect scheme https if !{ ssl_fc }
Это значит, что если входящий запрос не HTTPS, то надо отправить перенаправление 301 к тому же ресурсу, но по схеме HTTPS.
Это именно то HTTP Strict Transport Security – всем браузерам предписывается использование HTTPS:
rspadd Strict-Transport-Security:\ max-age=31536000;\ includeSubDomains;\ preload
Настройка добавляет нужную строку в заголовки. Браузер, распознающий этот заголовок, понимает, что сайт предпочитает работать по HTTPS, и это указание действительно в течение года (31,536,000 seconds). Директива preload сообщает Google-боту, что ваш сайт можно добавить в их список сайтов, поддерживающих HSTS.
HSTS – штука правильная. Шифрование должно быть всегда, и администраторам надо стремиться распространять его везде.
Кстати – необходимо учесть, чтобы все куки также включали атрибут secure, поскольку с точки зрения вашего веб-сервера всё происходит по обычному протоколу HTTP. Для этого используем директиву rsprep:
1
rsprep ^Set-Cookie:\ (.*) Set-Cookie:\ \1;\ Secure if secure
Обратите внимание на «if secure». Это значит, что меняются только куки, идущие по HTTPS (secure – это переменная, которую мы несколько линий назад определили). Вообще, всё должно работать через HTTPS, поэтому это в принципе необязательно – но можно и подстраховаться.
Последняя строка определяет, куда отправлять трафик. Это default_backend. Тут можно определять несколько серверов для распределения нагрузки и т.п. Но, поскольку у нас есть один бэкенд-сервер, то всё довольно просто.
back end
Поскольку у нас один бэкенд-сервер, эта секция коротка. HAProxy необходимо лишь добавить пару заголовков, чтобы сервер понял, что реальное общение происходит через HTTPS, даже если он видит только лишь HTTP:
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
Первая директива устанавливает заголовок, объясняющий, что клиент изначально пришёл на порт 443. Вторая усиливает первую, устанавливая X-Forwarded-Proto в HTTPS, если запрос шёл через HTTPS, чтобы веб-сервер понимал, что происходит и не создавал неправильных ответов.
Затем мы объясняем HAProxy, куда слать запросы – имя, IP и порт веб-сервера. У меня веб-сервер работал на отдельной машине, поэтому я пишу сюда другой IP и 80-й порт. Если у вас всё работает на одном компьютере, то пишите localhost и порт.
Статистика, если нужно
Последняя секция диктует HAProxy выдавать статусную страницу по заданному порту. Тут можно добавить basic auth, добавив stats auth username:password, или определить отличающийся URL.
Если вы используете директиву HSTS «includesubdomains», у вас может не получиться запросить статусную страницу по имени, поскольку веб-браузер попытается загрузить её HTTPS-версию, а HAProxy отдаёт только HTTP-версию. Это можно обойти, запрашивая страницу по IP вместе с портом (http://192.168.x.x:9999).
Подчищаем
Сохраните настройки, но пока не перезапускайте сервис HAProxy. Если у вас всё работает на одной машине, и nginx также слушает события на портах 80 и 443, нужно кое-что подправить.
Поскольку nginx больше не будет отдавать HTTPS-запросы, нужно убрать все упоминания HTTPS из всех файлов виртуальных хостов.
Вот и всё. Нужно только добавить в главный файл nginx.conf две строчки, чтобы убедиться, что nginx будет заменять ip-адреса согласно заголовку X-Forwarded-For, если запросы приходят от 127.0.0.1:
set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
Это будет работать, если вы скомпилили nginx с опцией ngx_http_realip_module.
Перезапустите HAProxy (service haproxy restart) и она начнёт слушать запросы.
Проверка работы
Сделайте запрос к сайту через http. Если URL меняется на https и вы видите сайт – всё работает. Можно убедиться, что заголовок HSTS отправляется корректно:
curl -s -D- https://yoursite.whatever/ | grep Strict
farcaller
Аналогично для nginx решается через:
для HTTP, и
для HTTPS
farcaller
jonic
Тот случай когда коммент полезнее топика.
AlexWinner
Я бы только заменил
rewrite ^ https://$server_name$request_uri? permanent;
на
return 301 https://$server_name$request_uri;
ИМХО return логичнее и читабельнее.
shamanis
А я заменил https://$server_name$request_uri на https://$server_name$uri, т.к. у Nginx есть баг при редиректе дублировать GET-переменные. Сам столкнулся недавно. Но, возможно, данный баг возникает только при стеке с Django.
VBart
Баг на самом деле не в nginx, и заключается в том, что люди не читают документацию, где черными русскими буквами на белом фоне написано:
Обратите внимание, что у человека выше было указано правильно:https://$server_name$request_uri?
, хотя как уже было сказано, правильнее будет использоватьreturn
.Aclz
Оно чем-то лучше/хуже habrahabr.ru/post/252821?
farcaller
Почти что идентично, оно основано как раз на этом посте.
armab
У меня уже давно правильно сконфигурированный SSL является одним из факторов оценки IT компании (и ее команды).
К сожалению, очень многие не парятся и в тестах их SSL www.ssllabs.com/ssltest показывает целый букет брешей.
Наиболее встречаемые: POODLE, использование слабого RC4, использование SHA1 сертификата вместо SHA2.
Реально эти тесты косвенно показывают:
Remper
«технически подкованы» и «такие простые элементы безопасности» — очень большое преувеличение. Конечно всегда удобно оценивать других людей с позиции своей предметной области. Например, если я не эксперт в инфобезе, почему я должен знать про POODLE? Разве не для этого мы все не лезем в криптографию и используем надёжные библиотеки, которые стабильно обновляем, чтобы не беспокоиться об очередном баге, который так и так вылезает каждые n месяцев?
А теперь из этого постулата просто представьте, что не каждой компании по карману содержать отдельного инфобезника.
Лучше патчи пишите в популярные проекты (типа того же nginxа), чтобы людей предупреждали о SHA1 и SSLv3 ворнингом, чем всех по своей линейке мерять.
armab
Вы правы, это сугубо субъективная оценка и наверное планка слишком высока.
Здесь главное не впадать в крайности. Это можно применять к tech-компаниям с 10+ человек в команде, где уже какие-то тех. процессы устоялись и обычно кто-то занимается серверами. Конечно, бесмысленно говорить об очень маленьких компаниях вроде «2 человека и собака» и конечно есть исключения из правил.
Приведу пример:
Западный стартап, с помощью которого можно заказать чартер/частный самолет. $6к+ средняя транзакция. Полный букет брешей в SSL тесте — первый плохой знак.
Смотрим глубже — PHP 5.3 светится в хедерах. Дальше — domain.com/blog/readme.html — старая дырявая версия Wordpress.
Можно ли уже сказать что-то о технической части проекта? Спрашиваем овнера: «Что происходит?». Оказывается работают индусы за копейки :)
Не обязательно содержать инфобезника, должен быть баланс, когда программист хоть немного интересуется что происходит нового в IT мире, а уж тем более тот кто занимается серверами.
Просто это не единственный фактор, это часть паттерна, некий звоночек.
Remper
Дело даже не в высоте планки, скорее в её специализированности. Такие ресурсы как хабр конечно же помогают (наверное все слышали хотя бы про Heartbleed) + у большинства есть какие-то базовые представления независящие от инфобеза — что ввод надо фильтровать, что html надо эскейпить, что библиотеки нужно своевременно обновлять.
Моя мысль больше в том, что очень легко можно пропустить какой-то абстрактный POODLE и это больше показатель, что комьюнити не достаточно хорошо справляется с задачей оповещения о таких вещах. Опять же — какие-то ворнинги от заведомо опасной конфигурации могли бы быть полезны.
То есть мы не можем ожидать, что все будут подкованы в инфобезе. И это справедливо для любой области. Например, если вы не спец в базах данных, вы можете плохо спроектировать базу данных, можете писать неэффективные запросы и в большинстве случаев ваша система всё равно будет работать. Если вам не критична скорость — ваша задача решена.
Но в случае инфобеза — ликбез и какие-то защиты от дурака должны продвигаться агрессивней, просто потому что бреши в безопасности наносят вполне ощутимый вред. Импакт очень значительный.
Но в целом про «звоночек» вы правы. Я просто считаю, что с учётом важности инфобеза — этим в том числе должно заниматься комьюнити.
reji
[ sarcasm on ]
Обоже, Яндекс и Google нанимают слабых айтишников!
[ sarcasm off ]
armab
Не надо троллить. Там цепочка для поддержки старых браузеров
armab
Проблема достаточно большая.
Интерактивная инфографика со средней температурой по интернету для топ 1М сайтов:
www.trustworthyinternet.org/ssl-pulse
grossws
Ещё можно добавить server_tokens off; в http/server, чтобы заголовок
Server: nginx/1.x.y
отдавался без версии (Server: nginx
).kireevco
Расскажите, кто пользовался HAProxy и nginx в качестве балансировщика, какие преимущества HAProxy имеет перед nginx?
vaniaPooh
Если я не ошибаюсь, то Nginx в бесплатной версии не умеет делать активную балансировку, т.е. если реплика завалилась, то пользователь это увидит с определенной вероятностью.
kireevco
Читаю документацию по бесплатной версии:
То есть если health check не прошел, трафик перестает идти на эту ноду.
AlexWinner
Вот как раз в вашей цитате написано: «passive» health checks. То есть, запрос от пользователя пойдет на упавшую ноду, провисит там до таймаута, и потом nginx может переслать его на здоровую ноду. Таким образом, пользователь будет ждать всё это время таймаута. (После этого да, nginx может пометить ноду как упавшую).
В то время как varnish и haproxy умеют делать active health checks — они сами посылают раз в какое-то время запросы на ноды, чтобы проверить их.
kireevco
Да, точно. Ключ в 'in-band (or passive)'. Спасибо!
Есть github.com/yaoweibin/nginx_upstream_check_module, wiki.nginx.org/HttpHealthcheckModule и еще пара модулей которые должны реализовать активные проверки, никто не использует, случайно?
Alukardd
На нагруженном проекте, это не важно, т.к. запрос от пользователя придёт с большой долей вероятности раньше чем отработает active check…
trublast
В целом согласен, хотел написать тоже самое. А для ненагруженных вряд ли кто использует балансировщики.
Ну разве что ситуация, когда нужно не распределение нагрузки, а резервирование, в этом случае активная проверка имеет право на существование.
kemko
И не только. Если один бэкэнд провалил запрос — его можно попытаться всё-таки выполнить на другом: nginx.org/ru/docs/http/ngx_http_proxy_module.html#proxy_next_upstream
Это, конечно, не полноценный health check, но уже что-то.
n1nj4p0w3r
Что вспомнилось из практики:
— proxy protocol для tcp, что позволяет с удобствами балансировать тот-же postfix
— kerberos/ntlm при L7 балансировке
— haproxy умеет RPC over HTTP v1 и RPC over HTTP v2, что позволяет ему выполнять L7 балансировку до exchange rpc over http (outlook anywhere) и rdp over http (MS Terminal Services Gateway)
Упоминая L7 балансировку я прежде всего я имею ввиду ssl offloading, а уж потом игры с заголовками, куками и т.д.
kireevco
Про TCP и Kerberos/NTLM понял, спасибо.
Но ведь nginx умеет (и умел) ssl offloading, а http заголовки и cookies (в том числе через lua) хорошо должны быть доступны?
n1nj4p0w3r
Я не имел ввиду, что nginx не умеет ssl offoading или ковыряние в заголовках, я обратил внимание на то, что ssl offloading в моих юзкейсах критичен, а kerberos/rpc over http при tcp балансировке можно получить и на nginx.
Botkin
Проксирование виндовых серверов с NTLM смог осилить только на haproxy. Может, конечно, руки кривые, но этот продукт показался мне очень дружелюбным и очевидным в настройке.
До haproxy извращался с arr в IIS (писал на хабр об этом)
onthefly
слово «стек» можно было и не переводить
SLY_G Автор
Иногда мозг в режиме перевода начинает переводить всё подряд. Исправил.
alkresin
А что хорошего-то?
ntfs1984
А ничего.
Те кому нужно (читайте, трехбуквенные конторы) — те при желании найдут и перехватят и с зашифрованным вебом.
Остальные будут платить дополнительным временем (а кто на платном трафике — те и деньгами) за дополнительные рукопожатия и прочие проверки.
questor
Провайдеры перестанут внутрь веб-страниц свои левые скрипты вставлять, баннеры, собственные панели и прочую муть. На мой взгляд — уже весомый плюс, можно и согласиться на некоторый оверхед ресурсов.
vsb
Не факт, что перестанут. Кто-ж им помешает делать MITM своим самоподписанным сертификатом? Ещё и инструкцию на сайте выложат по добавлению сертификата в систему, чтобы «браузер не глючил».
ntfs1984
Примерчик приведите пожалуйста из личного опыта.
Последний раз такое наблюдал лет 10 назад на каком-то уже мертвом хостинге а-ля народ.ру.
cy-ernado
Билайн и бесплатное WiFi в метро, например.
Gendalph
habrahabr.ru/post/257133