# теория · шаг 3 из 6
SNI: чей сертификат отдавать
Коротко
Сертификат нужен раньше, чем придёт HTTP-запрос с заголовком Host. Имя сервера
клиент присылает в самом начале рукопожатия - это SNI.
- Блок выбирается по SNI теми же правилами, что и по
Host: точное имя, маска, регулярка,default_server. - Неизвестное имя получает сертификат блока по умолчанию, то есть чужой. Это
чинит
ssl_reject_handshake on- клиент получает предупреждение TLS 112unrecognized nameи никакого сертификата. listen ... sslбез сертификата не даёт nginx стартовать - кроме блока сssl_reject_handshake.- Wildcard покрывает ровно один уровень и не покрывает сам домен - оба факта проверены.
Если знаешь, почему ssl_certificate живёт в server, и видел ssl_reject_handshake - листай до «Теперь сам».
Сначала ответь сам
server {
listen 443 ssl;
server_name shop.local;
ssl_certificate /certs/shop.fullchain.crt;
ssl_certificate_key /certs/shop.key;
}
server {
listen 443 ssl;
server_name blog.local;
ssl_certificate /certs/blog.fullchain.crt;
ssl_certificate_key /certs/blog.key;
}
Кто-то направил на твой адрес домен unknown.local и открыл его. Какой
сертификат он получит?
Сертификат shop.local - первого блока на порту. Имя не совпало ни с одним
server_name, сработало правило третьего раздела: первый блок на паре «адрес и
порт» становится блоком по умолчанию. Пользователь увидит предупреждение с чужим
доменом, а сканер интернета - подсказку, какие сайты живут на этом адресе.
На что это похоже
Гардероб на входе в здание с несколькими организациями. Пальто принимают до того, как ты сказал, к кому идёшь, - значит, номерок надо выдать заранее.
Поэтому на входе спрашивают только одно: «в какую фирму?» Вопрос звучит раньше всего остального разговора, и от ответа зависит, к какой стойке тебя отправят. Не назвал фирму - отправят к первой по списку.
Механизм: имя приходит до запроса
Правило
server {
listen 443 ssl default_server;
ssl_reject_handshake on;
}
Такой блок ставят первым файлом по алфавиту, и он обрывает рукопожатие для всех
имён, не совпавших ни с одним server_name. Замер на стенде:
406752FA427F0000:error:0A000458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name:../ssl/record/rec_layer_s3.c:1593:SSL alert number 112
no peer certificate available
Сертификата клиент не видит вовсе - значит, и чужого домена в предупреждении не будет, и сканеру нечего собирать.
ssl_certificate в этом блоке не нужен, и это единственный случай, когда
listen ... ssl без сертификата не считается ошибкой. Во всех остальных:
[emerg] no "ssl_certificate" is defined for the "listen ... ssl" directive
in /etc/nginx/conf.d/x.conf:1
Разбор: кто ещё попадёт под отказ
Тот же замер, но клиент вообще не отправил SNI:
tlsv1 unrecognized name:../ssl/record/rec_layer_s3.c:1593:SSL alert number 112
no peer certificate available
Результат тот же. Это и есть цена решения: клиенты без SNI - очень старые браузеры, часть встроенных устройств, иногда самописные скрипты - перестанут открывать сайт совсем, а не увидят предупреждение. Сегодня таких почти нет, но если в требованиях есть древние клиенты, для такого сайта нужен отдельный адрес.
Ровно по этой же причине openssl s_client без -servername показывает не тот
сертификат: он ничем не отличается от старого клиента.
Разбор: что покрывает wildcard
Стенд, сертификат на *.shop.local, три запроса:
| адрес | результат curl |
|---|---|
a.shop.local |
200 |
a.b.shop.local |
(60) SSL: no alternative certificate subject name matches target host name 'a.b.shop.local' |
shop.local |
(60) SSL: no alternative certificate subject name matches target host name 'shop.local' |
Звёздочка покрывает ровно один уровень и не покрывает сам домен. Поэтому в wildcard-сертификаты всегда включают голый домен отдельным именем, а не надеются на маску.
Что ломается без этого
Расхождение между тем, для чего выписан сертификат, и тем, что обслуживает
блок, - самая частая причина предупреждений после добавления поддомена.
Добавили api.shop.local в server_name, а сертификат перевыпустить забыли:
имя совпало, блок выбран, сертификат отдан старый, браузер ругается.
Проверяется парой команд без всяких догадок:
openssl x509 -in fullchain.pem -noout -ext subjectAltName
nginx -T | grep -A2 'server_name api'
Списки должны совпадать. И помни, что server_name и SAN - разные списки в
разных местах: nginx про содержимое сертификата ничего не знает и на
несоответствие не ругается.
Зачем это в работе
Блок с ssl_reject_handshake - две строки, которые стоит завести на любом
сервере с публичным адресом. Он закрывает три вещи сразу: чужой домен в
предупреждении пользователя, подсказку сканерам и случайное обслуживание
трафика, направленного к тебе по ошибке или намеренно.
Проверить, что он работает, можно с любой машины:
openssl s_client -connect shop.local:443 -servername чтоугодно.local </dev/null
Увидел alert number 112 - защита на месте. Увидел сертификат своего сайта -
блок по умолчанию всё ещё отдаёт его кому попало.
Теперь сам
На сервере два сайта. Коллега добавил третий и написал в его блоке
listen 443 ssl default_server;, потому что «так было в примере». Что
изменилось для двух старых сайтов?
Для них по имени ничего: SNI совпадает, блоки выбираются точно. Изменилось
поведение для неизвестных имён - теперь чужой сертификат отдаёт третий сайт, а
не первый. А если флаг default_server уже стоял у другого блока на этом порту,
nginx откажется собирать конфиг: два блока по умолчанию на одной паре «адрес и
порт» - ошибка, разбирали в третьем разделе.
Главное
Имя приходит в SNI до HTTP-запроса, поэтому ssl_certificate задаётся в
server, а блок выбирается теми же правилами приоритета, что и по Host.
Неизвестное имя попадает в блок по умолчанию и получает чужой сертификат;
закрывается это блоком с ssl_reject_handshake on, после которого клиент видит
предупреждение TLS 112 и никакого сертификата - включая клиентов, которые SNI не
отправляют вовсе. listen ssl без сертификата не даёт стартовать, кроме этого
случая. Wildcard покрывает один уровень и не покрывает сам домен.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий