Как перевести сайт целиком на постоянный HTTPS для всех

от автора


Шифруем всё подряд

Эра незашифрованного веба проходит, и это хорошо. В этой инструкции мы предполагаем, что на вашем сервере работает веб-сервер 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 

ссылка на оригинал статьи http://habrahabr.ru/post/257609/