Запрос, который ваш редирект никогда не увидит
Почти каждый сайт с сертификатом делает одно и то же: отвечает на порту 80 редиректом 301 на адрес https://. Это правильно, и так и нужно делать. Просто происходит это на один запрос позже, чем нужно.
Когда кто-то вводит example.com в адресную строку, переходит по ссылке из старого письма или открывает закладку многолетней давности, у браузера нет причин предполагать HTTPS. Он открывает обычное HTTP-соединение и запрашивает страницу. Ваш редирект отвечает — но запрос уже ушёл в открытом виде, через ту сеть, в которой в этот момент оказался посетитель: кафе, аэропорт, отель, captive portal, домашний роутер, который никто не обновлял.
Любой, кто может прочитать этот запрос, может на него и ответить. Вместо того чтобы передать ваш 301, он отдаёт собственную копию вашего сайта по HTTP и незаметно проксирует всё остальное к вам по HTTPS. Посетитель видит страницу, которая выглядит правильно, минус замочек, который почти никто не проверяет, и вводит в неё пароль. Это называется SSL stripping — атака понижения, нацеленная как раз на тот запрос, для устранения которого и существует ваш редирект.
HSTS убирает этот запрос из мира вовсе. Как только браузер увидел заголовок, он сам переписывает http://example.com/... в https://... ещё до того, как что-либо покинуло устройство. Перехватывать нечего — запроса в открытом виде просто не существует, и нет предупреждения о сертификате, которое посетитель мог бы проигнорировать.
Что говорит заголовок
На практике всё сводится к Strict-Transport-Security: max-age=31536000. В заголовке может присутствовать три настройки, и они сильно различаются по тому, на что они вас обязывают.
max-age — единственная обязательная: сколько секунд браузер должен помнить, что этот хост доступен только по HTTPS. 31536000 — это год. Счётчик сбрасывается с каждым ответом, поэтому у активно посещаемого сайта впереди всегда полный год, а сайт, на который никто не заходит, со временем забывается.
includeSubDomains распространяет правило на все имена под вашим доменом — www, api, shop, staging и то, о котором вы забыли. Это действительно усиливает защиту, потому что cookie, установленный на родительском домене, можно атаковать через любой его поддомен. Но это же и настройка, которая чаще всего всё ломает, потому что она применяется и к хостам, которые вы, возможно, не контролируете и для которых у вас может не быть сертификатов.
preload — это запрос на то, чтобы ваш домен зашили прямо в сами браузеры, тогда режим «только HTTPS» действует ещё до первого визита посетителя на ваш сайт. Она требует includeSubDomains и длинного max-age, и её очень трудно отменить.
В основе всех трёх — одно правило: заголовок учитывается только в ответе по HTTPS. Браузеры намеренно игнорируют его по обычному HTTP, потому что иначе кто угодно в сети мог бы закрепить за собой домен, который ему не принадлежит.
Почему мы отправляем только max-age
Пресет на cdn.com.tr отправляет только max-age=31536000 и ничего больше. Это осознанное решение по умолчанию, а не ограничение, и стоит понимать, к чему обязали бы вас две другие настройки.
includeSubDomains затрагивает имена, которых нет перед глазами на странице, где вы включили опцию: устаревший хост mail. на старом сервере, страница status., внутренний инструмент на поддомене, который годами отвечал по обычному HTTP. В тот момент, когда правило попадает в браузер, каждый из них становится недоступен в этом браузере — не предупреждение, а жёсткий отказ, — и удаление заголовка это не исправляет, потому что браузер уже записал правило.
preload подводит в том же направлении, только медленнее: отзыв — это заявка в стороннем списке и ожидание, пока правило доедет до релизов браузеров, а это месяцы.
Годовой max-age на том хосте, который вы реально обслуживаете, даёт всю пользу безопасности для этого хоста с первого же визита. Более жёсткие флаги стоит добавлять позже, осознанно, когда каждое имя под вашим доменом инвентаризировано и переведено на HTTPS. Начинайте там, где вы уверены, а не там, где потом можно пожалеть.
Чек-лист перед тем, как включать
Сам HSTS не рискован. Рискованно включать его на сайте, который к этому не готов. Сначала пройдитесь по этому списку.
Каждая страница загружается по HTTPS. Не только главная: админка, API, эндпоинты загрузки файлов, адреса вебхуков, старые ссылки из кампаний. Всё, что работает только на порту 80, перестанет работать для каждого, кто увидел заголовок.
Сертификат действителен и продлевается сам. При включённом HSTS истёкший сертификат — уже не предупреждение, которое посетитель может закрыть, а стена. Сертификаты на cdn.com.tr выпускаются и продлеваются автоматически — именно это свойство HSTS у вас и предполагает.
Смешанного контента не осталось. Скрипты, стили, изображения и iframe со ссылками через http:// должны быть уже исправлены; HSTS лишь делает их поломку громче.
Вы знаете, что происходит на ваших поддоменах. Не потому что вы включаете includeSubDomains сегодня, а потому что рано или поздно захотите, и инвентаризация — это и есть настоящая работа.
Ваш редирект с HTTP на HTTPS остаётся на месте. HSTS защищает браузеры, которые уже видели заголовок; редирект ловит всех остальных. Это не альтернативы друг другу, а дополнение.
Как включить
В панели откройте Delivery rules → Security presets. Поле «Security setting» — это мультивыбор edge-пресетов, применяемых к аккаунту: отметьте Hsts и сохраните. Страница находится по адресу /management/cdn/advanced-management.
Настройка задаётся на уровне аккаунта, поэтому хост, который ещё не готов, может просто подождать на собственном аккаунте, не задерживая те, что уже готовы.
После сохранения edge добавляет заголовок к HTTPS-ответам этого аккаунта — и только к HTTPS-ответам: обычный HTTP-запрос по-прежнему получает ваш редирект без заголовка, именно этого и требует спецификация. На вашем источнике ничего не меняется: заголовок не нужно добавлять ни в nginx, ни в Apache, ни во фреймворке, потому что именно edge решает, каким будет ответ, который видит посетитель.
Проверка за десять секунд
Запросите заголовки и поищите одну строку. Важны два результата, и второй — как раз тот, который люди пропускают.
Заголовок должен быть в ответах по HTTPS и больше нигде
# должно вывести: strict-transport-security: max-age=31536000
curl -sI https://example.com/ | grep -i strict-transport
# не должно вывести вообще ничего
curl -sI http://example.com/ | grep -i strict-transport
Что запоминает браузер
Командная строка показывает, что вы отправляете. А работает заголовок на самом деле в браузере, и стоит один раз убедиться, что правило действительно закрепилось.
Зайдите на сайт по HTTPS, затем откройте новую вкладку и наберите просто имя хоста. Адресная строка должна сразу же переключиться на https:// — переписывание происходит внутри браузера, поэтому перехватывать в сети нечего и никакого редиректа не требуется. Chrome показывает, что у него сохранено, по адресу chrome://net-internals/#hsts, включая срок действия — это удобно, когда вы проверяете, а не гадаете.
Две привычки избавят вас от путаницы. Тестируйте из профиля браузера, который никогда не заходил на сайт, если хотите увидеть поведение при первом визите, и помните: браузер, который выучил правило до того, как вы что-то поменяли, продолжит по нему действовать — об этом следующий раздел.
Откат — и то, чего он не может
Снимите отметку Hsts в том же мультивыборе и сохраните. Edge немедленно перестаёт отправлять заголовок, и любой браузер, который его никогда не видел, с этого момента ведёт себя как обычно.
А вот что не происходит — и это удивляет людей: браузеры, которые уже записали правило, продолжают его соблюдать, пока не истечёт их max-age. Отключение пресета до них не дотягивается. При годовом max-age посетитель, который видел заголовок вчера, будет принудительно требовать HTTPS ещё год, независимо от того, что говорит ваш сервер сейчас.
Спецификация предусматривает для этого случая плавное сворачивание — заголовок со значением max-age=0 говорит браузеру забыть правило при следующем визите по HTTPS. Это работает, только пока сайт остаётся доступен по HTTPS, и заголовок нужно продолжать отправлять, пока не вернутся нужные вам браузеры, — это спланированное отступление, а не переключатель.
Именно поэтому чек-лист важнее плана отката. Реальный сценарий провала — никогда не «HSTS нам не подошёл», а «один URL работал только по HTTP, и мы узнали об этом постфактум». Починить этот URL — почти всегда более быстрый путь назад.
Место HSTS среди остальных ваших защит
HSTS делает ровно одну вещь: убирает запрос в открытом виде. Стоит проговорить это чётко, потому что фраза «у нас есть HSTS» иногда звучит так, будто вопрос безопасности этим уже закрыт.
Это не сертификат — HSTS предполагает, что он действителен, и делает недействительный сертификат фатальным. Это не шифрование — этим занимается TLS, а HSTS лишь гарантирует, что используется именно TLS. Он ничего не инспектирует, поэтому не останавливает ни инъекции, ни эксплойты, ни вредоносную нагрузку — это работа WAF. Он ничего не считает, поэтому перебор паролей и скрапинг остаются задачей rate limiting. И он ничего не говорит о том, кто вообще может до вас достучаться — для этого есть блокировка по стране и ASN.
Ради чего стоят эти пять минут — ради соотношения. Один заголовок, одна настройка, никакой постоянной поддержки — и целый класс атак понижения становится невозможен против ваших посетителей.
Частые вопросы
Достаточно ли одного HSTS?
Нет, и не должно быть. HSTS гарантирует, что соединение зашифровано, но ничего не говорит о том, что по нему передаётся. Запрос с SQL-инъекцией доходит по HTTPS так же благополучно, как и по HTTP. Его место — рядом с действительным сертификатом, WAF, rate limiting и разумными правилами доступа, а не вместо них.
Заменяет ли HSTS мой редирект с HTTP на HTTPS?
Нет. Оставьте редирект как есть. HSTS начинает действовать только после того, как браузер хотя бы раз увидел заголовок по HTTPS, поэтому каждый новый посетитель, каждое новое устройство и каждый краулер всё ещё приходят на порт 80 и нуждаются в 301. Эти два механизма закрывают разные половины одной и той же проблемы.
Покрывает ли это мои поддомены?
Не с этим пресетом. Он отправляет только max-age, поэтому правило действует именно на тот хост, который отдал ответ. Чтобы охватить поддомены, нужен includeSubDomains — а это куда более серьёзное обязательство: правило вступает в силу в браузере немедленно и не может быть оттуда отозвано, поэтому прежде чем включать его, каждое имя под вашим доменом должно работать по HTTPS.
Стоит ли отправлять домен в список предзагрузки HSTS?
Только после того, как HTTPS повсюду перестал быть чем-то интересным — месяцы подряд. Предзагрузка зашивает ваш домен прямо в браузер, так что защищает даже самый первый запрос посетителя, — но выход из списка означает заявку третьей стороне и ожидание релизов браузеров, поэтому ошибка держится очень долго. Годовой max-age даёт большую часть пользы и оставляет путь назад.
Защищает ли HSTS самый первый визит посетителя?
Сам по себе — нет, это единственный пробел, который он оставляет. Именно первый визит и учит браузер правилу, и защищён он вашим редиректом и действительным сертификатом, а не HSTS. Закрыть этот пробел может только предзагрузка, для того список и существует.
Повлияет ли это на производительность?
Незначительно, и в вашу пользу. Заголовок — это пара десятков байт, а как только он есть у браузера, каждая ссылка http:// на ваш сайт переписывается прямо внутри браузера вместо того, чтобы стоить лишний round-trip до вашего редиректа. Посетители, которые раньше проходили через 301, теперь его полностью пропускают.