Резолвер, который не может ответить на google.com, обычно означает сбой сети или провайдера. В прогонах, описанных 31 октября, причина была другой. Контейнер с BIND 9.16.50 из Debian 11, запущенный с пустым каталогом состояния, вернул SERVFAIL на подписанный iana.org, на dnssec-failed.org, где SERVFAIL как раз правильный ответ, и на неподписанный google.com. В named.log появилась строка о том, что корневой набор ключей проверить не удалось: validating ./DNSKEY: no valid signature found (DS). Такие же сообщения об отсутствии допустимой подписи пришли как минимум от пяти разных корневых серверов, включая 198.41.0.4.

Отказ на именах без подписи выглядит странно, пока не вспомнишь, как устроена цепочка доверия. У валидирующего резолвера есть якорь доверия (trust anchor): ключ корня или его хеш, с которого начинается проверка любой цепочки. Резолвер верит ответу не потому, что тот пришёл с авторитетного сервера, а потому, что подпись сходится к якорю. Отсутствие подписи у имени тоже нужно доказать, и без цепочки это не выйдет: требуется подписанное подтверждение того, что делегирование в незащищённую зону законно. Если цепочка рвётся на первом шаге, резолвер не может ни проверить подпись, ни доказать её отсутствие, и по правилам DNSSEC, описанным в RFC 4035, отдаёт SERVFAIL вместо ответа.

С 14:00 UTC 11 октября 2026 года корневая зона подписывает свой набор ключей только новым ключом KSK-2024 с тегом 38696. Старый KSK-2017 с тегом 20326 остаётся опубликованным, но подписей им больше нет. Вывод запроса к живому корню, сохранённый автором 30 октября, выглядит так: ключ подписи ключей с тегом 38696, ключ подписи зоны с тегом 8763, ключ подписи ключей с тегом 20326, и единственная подпись набора от ключа 38696. Резолвер, который доверяет только 20326, получает оба ключа, но проверить подпись набора не может.

Дальше важна оговорка о статусе источника. Это самостоятельно опубликованный технический разбор на Хабре под ником opium, настоящее имя автора в тексте не указано. Рецензирования он не проходил, сырые логи и конфигурации отдельно не приложены, независимого воспроизведения в переданных материалах нет. Поэтому дальше речь идёт о результатах описанного автором эксперимента, а не о рецензированной работе.

Методика была такой. Пакеты скачивались из архивов выпусков, для Debian 11 с archive.debian.org, распаковывались через dpkg-deb -x без установки, бинарники запускались в одноразовых контейнерах debian:11 и debian:12 на непривилегированных портах. Каталог состояния каждый раз создавался пустым, как при пересоздании контейнера без тома. Проверялись три имени: iana.org как подписанное, dnssec-failed.org как имя с намеренно испорченной подписью, где правильный ответ именно SERVFAIL, и google.com как неподписанное. Все прогоны уложились в две минуты 31 октября, между 10:38 и 10:40 UTC. К каждому отказу прилагался контроль: тот же бинарник с якорем, в котором есть 38696. Версии указаны точно: bind9 1:9.16.50, unbound и unbound-anchor 1.17.1, dnsmasq-base 2.90-4~deb12u2, dns-root-data 2024071801~deb12u1.

Штатный bind.keys из Debian 11 содержит только 20326. С ним все три имени дали SERVFAIL без флага ad. Тот же named, которому подложили bind.keys с двумя якорями, 20326 и 38696, ответил на iana.org через NOERROR с флагом ad, сохранил SERVFAIL на dnssec-failed.org и ответил NOERROR на google.com. В логе появилось сообщение managed-keys-zone о том, что ключ с тегом 38696 теперь признан доверенным, причём обычное тридцатидневное ожидание при этом не применялось. Поведение на испорченной подписи сохранилось, и это существенно: резолвер не отключил проверку, а восстановил её.

Почему раньше тот же тест давал противоположный результат. За четыре дня до смены Эфраин Гарай проверил пакеты и образы и показал, что BIND 9.16.50 из Debian 11 при первом запуске сам начинает доверять новому ключу. Механизм сохранился, изменилось условие: 7 октября набор DNSKEY ещё был подписан старым ключом, поэтому пустое хранилище могло проверить его поставляемым якорем и записать актуальные ключи. После 11 октября та же проверка не проходит. Это хороший пример того, как рабочий тест тихо меняет смысл: вывод «BIND из Debian 11 доверяет новому ключу» был верен 7 октября и неверен для контейнера, пересозданного после смены.

С unbound выяснилась отдельная ловушка, связанная с кодами возврата. Встроенный якорь unbound-anchor 1.17.1 из Debian 12 содержит только 20326. Если проверка по DNS не проходит, программа скачивает файл root-anchors.xml с data.iana.org и проверяет его подпись встроенным сертификатом ICANN Root CA, действующим до 18 декабря 2029 года. При открытом HTTPS всё сработало: код возврата 1, в root.key оказались оба DS, 20326 и 38696, и unbound с этим файлом отвечал на iana.org с флагом ad. Затем программе подставили адрес 127.0.0.1 вместо сервера IANA, где никто не слушает. Результат: код 0, сообщение connect: Connection refused, строка о том, что root-anchors.xml получить не удалось, и root.key только с DS 20326. Unbound с таким файлом писал в лог failed to prime trust anchor -- DNSKEY rrset is not secure, то есть сообщал, что инициализировать якорь доверия не удалось и набор DNSKEY не защищён, и отдавал SERVFAIL на все три имени. Код 0 здесь не значит, что якорь обновлён, он значит лишь, что программа не считает ситуацию аварийной. Недоступность IANA автор изобразил подменой адреса; поведение при таймауте файрвола по логике программы должно дать тот же код, но отдельно это не проверялось.

У dnsmasq расхождение прошло ещё на один уровень. Пакет dnsmasq-base 2.90 в Debian 12 несёт файл trust-anchors.conf с единственной строкой trust-anchor=.,20326. Голый dnsmasq с этим файлом и апстримами 1.1.1.1 и 8.8.8.8 ответил SERVFAIL на все три имени и записал в лог строку о том, что корневой набор объявлен испорченным. Но сервисная обёртка пакета dnsmasq берёт якоря не отсюда: она читает /usr/share/dns/root.ds из пакета dns-root-data, где 38696 уже присутствует, и превращает каждую строку в отдельный параметр запуска. Автор прогнал ровно её преобразование и запустил тот же бинарник с полученными якорями: iana.org через NOERROR с ad, dnssec-failed.org через SERVFAIL. С unbound в Debian 12, по его предположению, та же картина: сервис перед запуском копирует root.key из dns-root-data, а встроенный якорь unbound-anchor остаётся старым.

Отсюда общий вывод, который шире одного ключа. Во всех трёх случаях исправление в дистрибутиве есть, но лежит не в той программе, которую запускает типичный контейнер: в соседнем пакете, в обёртке сервиса или на HTTPS-сервере IANA. Пока корень подписывался старым ключом, разницы не было видно. Похожая логика знакома тем, кто ловил невидимый BOM в начале CSV, из-за которого не находится первый столбец: симптом указывает на разбор данных, а причина лежит в байтах, которые никто не проверял.

Практическая часть статьи сводится к тому, что смотреть надо не на версию пакета и не на наличие тега 38696 в ответе dig DNSKEY .. С флагом +cd или через невалидирующий пересыльщик этот набор придёт и тому, кто 38696 не доверяет, поэтому такой запрос мало что доказывает. Проверять нужно поведение резолвера: dig @резолвер +dnssec iana.org A должен дать NOERROR с флагом ad, а dig @резолвер dnssec-failed.org A должен дать SERVFAIL. Второй запрос не менее важен первого: он показывает, что проверка вообще включена. Делать это стоит после пересоздания контейнера с пустым состоянием, потому что долгоживущий процесс может держать ключ в памяти или в своём файле. Если в entrypoint есть unbound-anchor, стоит заглянуть в получившийся root.key и поискать там 38696. В собственных Dockerfile для dnsmasq и unbound якорь с 38696 надёжнее задать явно, например из свежего dns-root-data.

Ограничения здесь важны не меньше результата. Всё сказанное относится к запуску с нуля, к указанным пакетам Debian 11 и 12 и к описанной конфигурации. Резолвер с записываемым состоянием, проживший с 2025 года, мог принять 38696 по механизму автоматического обновления якорей из RFC 5011: для этого нужен сохраняемый на диске набор и не меньше тридцати дней присутствия нового ключа в зоне, а сам документ прямо оговаривает, что начальную настройку он не решает. Ubuntu, Alpine и сторонние образы вроде mvance/unbound не перезапускались. Замеры Джеффа Хастона из APNIC, по пересказу автора, не показали заметного сбоя после смены; что именно там измерялось и на какой выборке, в тексте не сказано, поэтому считать эти замеры прямым сравнением с пустым контейнером оснований нет. Не объяснено и наблюдение из того же периода, где Pi-hole v5 с единственным якорем 20326 работал до 12 октября. Наконец, эксперимент выполнен одним человеком и не воспроизведён независимо.

Наша интерпретация скорее про цену неявного состояния, чем про саму смену ключа. Смена KSK произошла по плану и в срок, старый ключ остался в зоне, а поломка возникла у того, кто каждый раз начинает с чистого образа и потому зависит от того, что в этот образ положили. Два сигнала, которым обычно доверяют, здесь не работают: код возврата 0 у unbound-anchor и наличие свежих данных в пакете дистрибутива. Оба говорят о процессе, а не о результате, и проверять нужно результат, то есть ответы резолвера на подписанное и на намеренно испорченное имя. Остаётся открытым вопрос, почему в чужом наблюдении резолвер со старым якорем держался до 12 октября: ответ на него потребовал бы знать, было ли у того экземпляра записываемое состояние и что именно происходило при первом старте.

Источники и литература

Схема объясняет, что устаревший якорь доверия в образе обрывает проверку корневого набора ключей и приводит к отказу на всех именах, а добавление ключа 38696 возвращает нормальные ответы

Фото: Brett Sayles / Pexels