Spark-in.me Часть 5 - переход на HTTPS

Или почему проще делать сразу

Posted by snakers41 on June 21, 2017

Статьи цикла

Spark-in.me - как, зачем и почему. Часть 1 - почему?

Spark-in.me - как, зачем и почему. Часть 2 - как? Архитектура приложения и структура БД

Spark-in.me Часть 3 - DIY поддержка и админство сайта

Spark-in.me Часть 4 - Базовое админство для обычных человеков (postgres и не только)

Spark-in.me Часть 5 - переход на HTTPS

Заманчивый шильдик скрывает много деталей


TLDR

СТАВЬТЕ HTTPS СРАЗУ ЧЕРЕЗ  https://letsencrypt.org И НЕ МУЧАЙТЕСЬ


Когда я планировал функционал канала и сайта, я думал про https в последнюю очередь. Ни для кого не секрет, что Google в ранжировании сайтов использует более 200 различных параметров, также среди которых есть наличие https. Памятуя о том, какие муки бывают при переходе на https у крупных компаний и памятуя о том, как я лично лицезрел такой переход в менее крупной компании, я внес несколько вещей в архитектуру spark-in-me, которые потом как оказалось позволили сделать миграцию без проблем:

  1. Картинки для статей лежат на отдельном сервисе по отдельному url;
  2. Список картинок / файлов ведется с самого начала;
  3. В статье картинки явно прописаны в виде колонок в БД (не считая текста статьи);
  4. Все картинки навигации веб-сайта (например шапка) прописаны в специальных таблицах в БД;
  5. Почти все сущности (автор, главная, тег) имеют свои картинки, прописанные в БД, на клиенте нет хардкода, кроме адреса АПИ (меняется заменой 1 строки);
  6. Уровни максимально разделены - база, морда, АПИ и веб-сервер (nginx) почти никак не связаны друг с другом;



Гугл намекает, что как бы он даже не против, если вы зальете все комбинации своего сайта


Вообще взгляды на переход на https ранжируются от "закинул сертификат в корень и все" и "он должен быть сразу"  до "все и так работает". На самом деле в свете последних событий  в 2017 году https считается как бы уже обязательным и показателем, что вам не безразлично.  Потом в один знакомый разработчик рассказал мне про существование бесплатной CA (certification authority), которая не только выдает бесплатные сертификаты, но и поддерживает плагины для apache и nginx (в том числе), чтобы установка сертификатов делалась 1 командой (обновлять можно по крону). Вот ссылки:

  1. Главная страница - https://letsencrypt.org;
  2. Конкретные команды для набора систем;
  3. Если хотите закоптиться самостоятельно, то вот подробный гайд от Digital Ocean, где эта CA используется только для получения сертификата, остальное делается руками;


Когда я увидел, что список команд выглядит примерно вот так, я понял что надо все сделать и быстро:

$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install python-certbot-nginx 
$ sudo certbot --nginx


Чтобы ничего не падало (а оно все-таки падало ненадолго) я разбил мигацию на следующие шаги.

1. Установка сертификатов

Делается командами по ссылкам описанным выше. Пара тонких моментов

  • не забудьте прописать в своей CDN или консоли вашего регистратора доменов, записи CNAME для www версий своих доменов (пример www.example.com => example.com);
  • Если у вас уже стоят некие конфиги серверов, то (и по дефолту тоже) отвечайте "нет" на вопросы утилиты certbot, когда она попросит сменить конфиги;


2. Правки виртуальных хостов

После установки сертификатов (забыв о том, что если сайт на http, то https  контент можно получать, а не наоборот) я внес следующие правки в конфиг nginx для основого виртуального хоста (изначально я предполагал, что в тестовый период нужны будут какие-то костыли на стороне клиента, чтобы не переходить одним махом, но это оказалось избыточным). Зато чуточку разрбрался в nginx.

Обратите внимание на то, что я немного подчистил вывод certbot, разбил на 2 блока и добавил header, который в итоге не пригодился.

# http://nginx.org/en/docs/varindex.html
# https://serverfault.com/questions/638097/passing-ssl-protocol-info-to-backend-via-http-header
# https://serverfault.com/questions/213185/how-to-restart-nginx
# https://serverfault.com/questions/527780/nginx-detect-https-connection-using-a-header
# https://stackoverflow.com/questions/17483641/nginx-to-node-js-pass-params

server {
    listen 80;
    server_name spark-in.me www.spark-in.me;

    root /var/www/spark-in-me/blog;
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X_SPARK_SSL 0;
    }
    location ~ /\.(ht|git) {
        deny all;
    }

}
server {
    listen 443 ssl; # managed by Certbot
    server_name spark-in.me www.spark-in.me;
    ssl_certificate /etc/letsencrypt/live/spark-in.me/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/spark-in.me/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    root /var/www/spark-in-me/blog;
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X_SPARK_SSL 1;
    }
    location ~ /\.(ht|git) {
        deny all;
    }
}


Для меня как оказалось такой шаг был скорее избыточным, но для более крупного проекта, где нельзя "сразу" все сделать,  он может быть вполне оправданным с целью тестирования "а что же отваливается?" без безвозвратной миграции. Протестировали, но все наши ссылки продолжают работать, SEO не падает, никто не жалутся...

3. Правки архитектуры вызова сервисов 

Надо не забыть прописать разрешенные заголовки в АПИ (у вас может быть миллион сервисов, каждый из которых ссылается друг на друга). Вот пример моего кривого хардкода (мусор лишний убран), который внезапно работает

/*Protections against CSRF attacks*/
if ("POST" == $_SERVER["REQUEST_METHOD"]) {
    if (isset($_SERVER["HTTP_ORIGIN"])) {
   
    $http_origin = $_SERVER['HTTP_ORIGIN'];
        $address = "http://".$_SERVER["SERVER_NAME"];
        
        /*
        Uncomment the protection bit during deploy
        if (strpos($address, $_SERVER["HTTP_ORIGIN"]) !== 0) {
            exit("CSRF protection in POST request: detected invalid Origin header: ".$_SERVER["HTTP_ORIGIN"]);
        }
        */
    } else {
    if(!isset($http_origin)) {
    $http_origin = '';
    }
    }
}
/*Headers for modern http-request libraries*/
if (
$http_origin == "http://spark-in.me" 
|| $http_origin == "http://api.spark-in.me" 
|| $http_origin == "http://admin.spark-in.me" 
|| $http_origin == "http://pics.spark-in.me"  
|| $http_origin == "http://author.spark-in.me" 
|| $http_origin == "https://spark-in.me" 
|| $http_origin == "https://api.spark-in.me" 
|| $http_origin == "https://admin.spark-in.me" 
|| $http_origin == "https://pics.spark-in.me"  
|| $http_origin == "https://author.spark-in.me"
|| $http_origin == "http://www.spark-in.me" 
|| $http_origin == "http://www.api.spark-in.me" 
|| $http_origin == "http://www.admin.spark-in.me" 
|| $http_origin == "http://www.pics.spark-in.me"  
|| $http_origin == "http://www.author.spark-in.me" 
|| $http_origin == "https://www.spark-in.me" 
|| $http_origin == "https://www.api.spark-in.me" 
|| $http_origin == "https://www.admin.spark-in.me" 
|| $http_origin == "https://www.pics.spark-in.me"  
|| $http_origin == "https://www.author.spark-in.me"
) {  
   header("Access-Control-Allow-Origin: $http_origin");
}
else {
// Do nothing
}
header("Access-Control-Allow-Headers: X-Requested-With");

После этого во всех сервисах надо сменить адреса API-endpoint-ов на https. После этого нужно все протестировать.

4. Правки контента

После этого нужно пройтись по контенту, который подгружается на вашей странице (не по ссылкам, а именно по загружаемому контенту) и везде заменить ссылки на https. В моем случае это была прошивка блога (подробнее тут). 

В моем случае все свелось к паре взглядов на ER диаграмму в БД и такую замену (названия колонки и таблицы случайные):

UPDATE file SET host = replace(host, 'https://pics.spark-in.me/', 'https://pics.spark-in.me/')


После этого у меня перестали вываливаться ошибки (почти, но перестали появляться ошибки связанные со смешанным контентом и https, остались неведомые баги клиента).

5. Установка редиректа

Полезные ссылки по теме 1 2. Тут важно помнить, что по-хорошему вам важно будет сохранить не только свои "старые" ссылки, но и параметры, с которыми ваши клиенты и партнеры шлют вам трафик. В моем случае я ограничился добавлением этого в виртуальный хост nginx

return         301 https://$server_name$request_uri;

6. Заливка изменений в Google Search Console


Иногда документация Гугла это кладезь знаний - раз и два. А вообще Гугл, судя по картинке выше,  "хочет" чтобы вы имели условно 4 версии вашего сайта (с и без www * с и без https). Убедитесь, что там не начинают лезть новые ошибки, что все индексируется и доступно. Залейте сайтмапы для всех версий сайта.



Статьи цикла

Spark-in.me - как, зачем и почему. Часть 1 - почему?

Spark-in.me - как, зачем и почему. Часть 2 - как? Архитектура приложения и структура БД

Spark-in.me Часть 3 - DIY поддержка и админство сайта

Spark-in.me Часть 4 - Базовое админство для обычных человеков (postgres и не только)

Spark-in.me Часть 5 - переход на HTTPS