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

http://arstechnica.com/information-technology/2015/05/web-served-how-to-make-your-site-all-https-all-the-time-for-everyone/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+arstechnica%2Findex+%28Ars+Technica+-+All+content%29
  • Перевод

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


Эра незашифрованного веба проходит, и это хорошо. В этой инструкции мы предполагаем, что на вашем сервере работает веб-сервер 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
Поделиться публикацией
Комментарии 38
    +39
    Аналогично для nginx решается через:
    listen 80;
    rewrite ^ https://$server_name$request_uri? permanent;
    

    для HTTP, и
    listen 443 ssl spdy;
    
    ssl on;
    ssl_dhparam /etc/nginx/ssl/dhparam.pem;
    ssl_certificate /etc/nginx/ssl/xxx.pem;
    ssl_certificate_key /etc/nginx/ssl/yyy.key;
    
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    # ssl_protocols TLSv1.1 TLSv1.2; для поддержки старого андроида
    ssl_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:DES-CBC3-SHA:!DES:!RC4:!aNULL:!eNULL:!LOW:!MD5:!EXP:!PSK:!SRP:!DSS:!CAMELLIA:!SEED";
    ssl_prefer_server_ciphers on;
    
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 5m;
    
    ssl_stapling on;
    ssl_stapling_verify off;
    ssl_trusted_certificate /etc/nginx/ssl/zzz.pem;
    resolver 8.8.8.8;
    
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
    

    для HTTPS
      0
      для поддержки старого андроида
      конечно же имелось в виду — если не нужна поддержка старого андроида
        +20
        Тот случай когда коммент полезнее топика.
          +8
          Я бы только заменил

          rewrite ^ https://$server_name$request_uri? permanent;

          на

          return 301 https://$server_name$request_uri;

          ИМХО return логичнее и читабельнее.
            0
            А я заменил https://$server_name$request_uri на https://$server_name$uri, т.к. у Nginx есть баг при редиректе дублировать GET-переменные. Сам столкнулся недавно. Но, возможно, данный баг возникает только при стеке с Django.
              +6
              Баг на самом деле не в nginx, и заключается в том, что люди не читают документацию, где черными русскими буквами на белом фоне написано:
              Если в строке замены указаны новые аргументы запроса, то предыдущие аргументы запроса добавляются после них. Если такое поведение нежелательно, можно отказаться от этого добавления, указав в конце строки замены знак вопроса, например:

              rewrite ^/users/(.*)$ /show?user=$1? last;
              
              Обратите внимание, что у человека выше было указано правильно: https://$server_name$request_uri?, хотя как уже было сказано, правильнее будет использовать return.
          • НЛО прилетело и опубликовало эту надпись здесь
              0
              Почти что идентично, оно основано как раз на этом посте.
              –2
              У меня уже давно правильно сконфигурированный SSL является одним из факторов оценки IT компании (и ее команды).

              К сожалению, очень многие не парятся и в тестах их SSL www.ssllabs.com/ssltest показывает целый букет брешей.
              Наиболее встречаемые: POODLE, использование слабого RC4, использование SHA1 сертификата вместо SHA2.

              Реально эти тесты косвенно показывают:

              - насколько компания реально заботится о безопасности
              - насколько админ/команда технически подкованы
                - следят за новостями безопасности
                - если такие простые элементы безопасности не учтены, то что же творится в самой системе?
                - косвенно: насколько hire отдел/CEO квалифицирован при выборе работников
              


              • НЛО прилетело и опубликовало эту надпись здесь
                  +2
                  Вы правы, это сугубо субъективная оценка и наверное планка слишком высока.
                  Здесь главное не впадать в крайности. Это можно применять к tech-компаниям с 10+ человек в команде, где уже какие-то тех. процессы устоялись и обычно кто-то занимается серверами. Конечно, бесмысленно говорить об очень маленьких компаниях вроде «2 человека и собака» и конечно есть исключения из правил.

                  Приведу пример:
                  Западный стартап, с помощью которого можно заказать чартер/частный самолет. $6к+ средняя транзакция. Полный букет брешей в SSL тесте — первый плохой знак.
                  Смотрим глубже — PHP 5.3 светится в хедерах. Дальше — domain.com/blog/readme.html — старая дырявая версия Wordpress.
                  Можно ли уже сказать что-то о технической части проекта? Спрашиваем овнера: «Что происходит?». Оказывается работают индусы за копейки :)

                  Не обязательно содержать инфобезника, должен быть баланс, когда программист хоть немного интересуется что происходит нового в IT мире, а уж тем более тот кто занимается серверами.

                  Просто это не единственный фактор, это часть паттерна, некий звоночек.
                  • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  [ sarcasm on ]
                  Обоже, Яндекс и Google нанимают слабых айтишников!
                  [ sarcasm off ]
                    0
                    Не надо троллить. Там цепочка для поддержки старых браузеров
                    +1
                    Проблема достаточно большая.

                    Интерактивная инфографика со средней температурой по интернету для топ 1М сайтов:
                    www.trustworthyinternet.org/ssl-pulse

                    У четверти сайтов - мусорный F рейтинг
                    image


                    Те четверть сайтов такие
                    image

                    0
                    Ещё можно добавить server_tokens off; в http/server, чтобы заголовок Server: nginx/1.x.y отдавался без версии (Server: nginx).
                    +7
                    Расскажите, кто пользовался HAProxy и nginx в качестве балансировщика, какие преимущества HAProxy имеет перед nginx?
                      –2
                      Если я не ошибаюсь, то Nginx в бесплатной версии не умеет делать активную балансировку, т.е. если реплика завалилась, то пользователь это увидит с определенной вероятностью.
                        +1
                        Читаю документацию по бесплатной версии:
                        Reverse proxy implementation in nginx includes in-band (or passive) server health checks. If the response from a particular server fails with an error, nginx will mark this server as failed, and will try to avoid selecting this server for subsequent inbound requests for a while.

                        The max_fails directive sets the number of consecutive unsuccessful attempts to communicate with the server that should happen during fail_timeout. By default, max_fails is set to 1. When it is set to 0, health checks are disabled for this server. The fail_timeout parameter also defines how long the server will be marked as failed. After fail_timeout interval following the server failure, nginx will start to gracefully probe the server with the live client’s requests. If the probes have been successful, the server is marked as a live one.


                        То есть если health check не прошел, трафик перестает идти на эту ноду.
                          +4
                          Вот как раз в вашей цитате написано: «passive» health checks. То есть, запрос от пользователя пойдет на упавшую ноду, провисит там до таймаута, и потом nginx может переслать его на здоровую ноду. Таким образом, пользователь будет ждать всё это время таймаута. (После этого да, nginx может пометить ноду как упавшую).
                          В то время как varnish и haproxy умеют делать active health checks — они сами посылают раз в какое-то время запросы на ноды, чтобы проверить их.
                            +2
                            Да, точно. Ключ в 'in-band (or passive)'. Спасибо!
                            Есть github.com/yaoweibin/nginx_upstream_check_module, wiki.nginx.org/HttpHealthcheckModule и еще пара модулей которые должны реализовать активные проверки, никто не использует, случайно?
                              +5
                              На нагруженном проекте, это не важно, т.к. запрос от пользователя придёт с большой долей вероятности раньше чем отработает active check…
                                0
                                В целом согласен, хотел написать тоже самое. А для ненагруженных вряд ли кто использует балансировщики.
                                Ну разве что ситуация, когда нужно не распределение нагрузки, а резервирование, в этом случае активная проверка имеет право на существование.
                              +1
                              И не только. Если один бэкэнд провалил запрос — его можно попытаться всё-таки выполнить на другом: nginx.org/ru/docs/http/ngx_http_proxy_module.html#proxy_next_upstream
                              Это, конечно, не полноценный health check, но уже что-то.
                            +1
                            Что вспомнилось из практики:
                            — 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, а уж потом игры с заголовками, куками и т.д.
                              0
                              Про TCP и Kerberos/NTLM понял, спасибо.
                              Но ведь nginx умеет (и умел) ssl offloading, а http заголовки и cookies (в том числе через lua) хорошо должны быть доступны?
                                0
                                Я не имел ввиду, что nginx не умеет ssl offoading или ковыряние в заголовках, я обратил внимание на то, что ssl offloading в моих юзкейсах критичен, а kerberos/rpc over http при tcp балансировке можно получить и на nginx.
                                0
                                Проксирование виндовых серверов с NTLM смог осилить только на haproxy. Может, конечно, руки кривые, но этот продукт показался мне очень дружелюбным и очевидным в настройке.
                                До haproxy извращался с arr в IIS (писал на хабр об этом)
                                  0
                                  Вдруг кому пригодится: Load Balancing Microsoft Exchange with NGINX Plus. Оно в значительной степени верно и для бесплатной версии.

                                  RPC over HTTP прекрасно работает начиная с 1.7.11.
                                +3
                                добавление ещё одного приложения в стопку

                                слово «стек» можно было и не переводить
                                  0
                                  Иногда мозг в режиме перевода начинает переводить всё подряд. Исправил.
                                  +1
                                  Эра незашифрованного веба проходит, и это хорошо

                                  А что хорошего-то?
                                  • НЛО прилетело и опубликовало эту надпись здесь
                                      +3
                                      Провайдеры перестанут внутрь веб-страниц свои левые скрипты вставлять, баннеры, собственные панели и прочую муть. На мой взгляд — уже весомый плюс, можно и согласиться на некоторый оверхед ресурсов.
                                        +2
                                        Не факт, что перестанут. Кто-ж им помешает делать MITM своим самоподписанным сертификатом? Ещё и инструкцию на сайте выложат по добавлению сертификата в систему, чтобы «браузер не глючил».
                                        • НЛО прилетело и опубликовало эту надпись здесь

                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                  Самое читаемое