Лог, который описывает вас лучше, чем история браузера
Перед тем как ваша машина отправит хоть один зашифрованный байт на сайт, ей нужно спросить у кого-то, где этот сайт живёт. Этот вопрос идёт в открытом виде, к резолверу, который вы почти наверняка не выбирали, а от того, кто хранит ответ, зависит, насколько подробно записана ваша жизнь. История браузера — это список страниц, которые вы сами решили сохранить. Лог резолвера — это всё: каждый сайт, каждое приложение, проверяющее обновления, каждый облачный сервис, с которым разговаривает ваш телефон, пока вы спите, каждый домен из каждого письма, которое вы открывали, по порядку, с метками времени — смотрел на экран человек или нет.
Прочтите неделю этого лога — и человека можно восстановить целиком. Банк, которым он пользуется, авиакомпания, которую он только что забронировал, аптека, приложение для свиданий, система учёта кандидатов у рекрутера, час, когда он проснулся, и час, когда он лёг. Для этого не нужно ничего расшифровывать. Всё несут одни имена — и имена остаются той единственной частью транзакции, которая в большинстве конфигураций по замыслу передаётся третьей стороне.
Сегодня этой третьей стороной оказывается тот, на кого указал ваш DHCP-лиз, — ваш провайдер, ваш работодатель, кафе с wi-fi. Те, кто это замечает, обычно переходят на публичный резолвер, а это реальный шаг вперёд по части добросовестности и шаг в сторону — не вперёд — по части приватности: вы не убрали наблюдателя, вы просто сменили компанию, обменяв зарегулированного телекома на CDN, живущий рядом с рекламным бизнесом, или некоммерческую организацию, чья политика может измениться вместе с её финансированием. Добросовестные операторы публикуют честные политики хранения, и некоторые действительно их соблюдают. Но политика — это обещание о поведении, а конфигурационный файл — это утверждение о возможностях. Обещание нельзя проверить аудитом. Двадцать строк, которые вы написали сами, на машине, которую вы арендуете, в стране, которую вы выбрали, — можно.
SP·02Форвардинг переносит лог. Рекурсия дробит его.
Почти любая статья, которая обещает «запусти свой собственный DNS-сервер», в итоге строит форвардер: небольшой кэш в вашей сети, который передаёт всё незнакомое на 1.1.1.1 или 9.9.9.9. Штука по-настоящему полезная — это быстро, это пять строк, и это отучает вашу локальную сеть следить за вами. Но совокупный лог она оставляет ровно там же, где он и был. Каждое имя, которое вы ищете, всё равно приходит в одну и ту же компанию, теперь удобно подписанное единственным IP-адресом вашего резолвера, — от этого поток становится легче атрибутировать, а не труднее.
Рекурсивный резолвер делает эту работу сам, а не перекладывает её на кого-то. Получив запрос про news.example.io, он спрашивает корневой сервер, кто отвечает за .io, затем спрашивает серверы .io, кто отвечает за example.io, и только потом обращается напрямую к этому оператору. Три разговора с тремя не связанными друг с другом сторонами, ни одна из которых не занимается агрегацией, и — это здесь самое важное — ни одна из них никогда не видит ваш запросный поток целиком. Операторы корневых серверов видят тонкий ручеёк запросов к TLD. Verisign видит, что кто-то по вашему адресу тронул что-то в зоне .com. Авторитативный сервер конкретного домена видит тот трафик, который увидел бы в любом случае, — вы всё равно собираетесь к нему подключиться.
QNAME-минимизация обостряет этот эффект значительно, и современный Unbound делает это по умолчанию. Вместо того чтобы отправлять всё имя целиком каждому серверу в цепочке — а именно так резолверы поступали тридцать лет подряд, — он отправляет каждому серверу только ту метку, которая нужна для ответа: io. — корню, example.io. — серверам .io, и только затем полное news.example.io. — тому оператору, которому оно действительно причитается. Иерархия перестаёт быть трансляцией ваших намерений всем подряд и становится тем, чем и была задумана: делегированием.
Компромисс здесь настоящий, и его стоит назвать прямо. Холодный рекурсивный запрос — это несколько обменов пакетами там, где форвардер обходится одним, поэтому первое обращение к незнакомому домену заметно медленнее. Вы забираете себе ответственность за кэш, которая раньше была чужой проблемой. И вы больше не пользуетесь общим кэшем, прогретым миллионами других людей. В обмен на это полная, упорядоченная, помеченная временем запись того, что вы искали, перестаёт существовать где бы то ни было за пределами вашего собственного диска.
SP·03Что это скрывает, а что — совершенно точно нет
Честные рамки этой меры куда уже, чем предполагает маркетинг вокруг приватного DNS, и именно знание этих границ не даёт принять плохое решение на одной лишь силе хорошего чувства.
Это убирает ровно одну вещь: агрегированный лог имён, который держит у себя единственный наблюдатель. Это немало, потому что именно агрегат представляет коммерческую ценность и именно его запрашивают массово. Но это не всё.
Это не скрывает само соединение. После того как имя разрешилось, ваше устройство всё равно открывает сессию к этому адресу, и любой, кто смотрит на ваш аплинк, видит IP назначения. Для сайта на выделенной инфраструктуре IP-адрес и есть идентификатор. Не скрывает это и имя хоста на проводе: если обе стороны не поддерживают Encrypted Client Hello, TLS-хендшейк всё равно несёт имя сервера в открытом виде — ту же самую информацию, которая была бы у вашего резолвера.
Не скрывает это и запросы от вашего хостинг-провайдера. Вот здесь чаще всего ошибаются, так что стоит сказать это без оговорок: исходящий трафик вашего резолвера — вопросы, которые он задаёт корневым, TLD- и авторитативным серверам, — покидает VPS по UDP-порту 53 в незашифрованном виде, и сеть, в которой сидит ваш сервер, может прочитать всё это целиком. До корневых серверов никакого DoT не существует. Вы не удалили наблюдателя, а скорее переместили его — от розничного провайдера, который продаёт данные и отвечает на повестки в вашей собственной стране, к хостинг-сети в юрисдикции, которую вы выбрали осознанно, и которая благодаря QNAME-минимизации видит лишь раздробленный поток. Это настоящее улучшение, и это вопрос о том, чей закон действует на этом отрезке провода, а не то, что может решить конфигурационный файл.
И это не даёт вам толпы. Резолвер, которым пользуется одно домохозяйство, без всякой неоднозначности приписывает каждый запрос именно этому домохозяйству. Против глобального пассивного наблюдателя оживлённый общий резолвер и правда прячет лучше. Против вашего провайдера, вашего работодателя, дата-брокеров, покупающих телеметрию резолверов, и обычных массовых запросов, которые на самом деле случаются с обычными людьми, — ваш резолвер лучше, но только если последний участок от ваших устройств идёт внутри туннеля. Используйте это вместе с вашим собственным туннелем WireGuard, а не вместо него. Сам по себе приватный резолвер по большей части просто переносит ваши метаданные. За туннелем он закрывает тот единственный канал, который туннель оставляет открытым.
SP·04Ошибка, которая превращает ваш резолвер в чужое оружие
Здесь есть ровно один способ всё испортить по-крупному, допустить его легко и случайно, а последствия достаются посторонним раньше, чем вам самим.
Резолвер, который отвечает всем подряд, — это открытый резолвер, а открытый резолвер — это усилитель. DNS работает по UDP, исходный адрес в UDP подделывается тривиально, а маленький запрос способен породить ответ, во много раз превышающий его размер. Атакующий отправляет вашей машине 60-байтовый вопрос с адресом жертвы, подделанным в качестве отправителя; ваша машина исполнительно отправляет жертве ответ, в несколько десятков раз крупнее. Сделайте это одновременно с нескольких тысяч открытых резолверов — и жертва падает с интернета, получив поток, который на уровне пакетов совершенно точно выглядит так, будто идёт от вас. Мишень не вы. Вы — оружие, и трафик в отчёте об инциденте — ваш.
Дальше — не самое красивое: жалобы на абьюз от сетей, о которых вы никогда не слышали, провайдер, который занулит маршрут до вашего адреса, чтобы защитить собственный транзит, и разговор с поддержкой, которого вы предпочли бы не иметь. Вы оказываетесь на стороне, которая вызывает то самое событие, что описано в нашем runbook на первый час DDoS-атаки, и ни в одной версии этой истории ваш аптайм не выживает.
От этого защищают два независимых замка, и нужны оба, потому что каждый закрывает провал другого. Первый — это собственный access-control в Unbound: он должен refuse весь интернет по обоим семействам IP, а затем явно allow loopback и подсеть вашего туннеля, — список default-deny, а не allow-список с разрешающим хвостом. Второй замок — это то, слушает ли демон хоть что-то вовне: привяжите его к 127.0.0.1 и адресу туннеля, никогда к 0.0.0.0, и держите порт 53 закрытым на публичном интерфейсе на уровне firewall.
Ловушка — сделать только первое. Правило access-control не заставляет порт исчезнуть: отклонённый запрос — это всё равно принятый пакет и отправленный пакет, ваш адрес всё равно всплывает в сканах, которые составляют карту открытых резолверов, а одна последующая правка не той секции превращает отказ в ответ. Привязка и firewall делают эту ошибку структурно невозможной, а не расстоянием в одну строку конфига. Если на этой же машине крутятся контейнеры, перечитайте как Docker публикует порты в обход вашего firewall, прежде чем считать, что написанное вами правило и есть правило, которое действует.
Настройки Unbound по умолчанию разумны. Но не приватны.
Unbound поставляется настроенным на корректность и стабильность — и это правильный дефолт для программы, которую в основном разворачивают провайдеры. Горстка настроек превращает его в инструмент, построенный для того, кто его запускает. Ни одна из них не экзотична — просто из коробки они выключены или оставлены без определённой позиции.
Перестаньте отвечать на вопросы о самом себе. По умолчанию резолвер с радостью сообщит версию своего ПО и имя хоста через класс CHAOS — version.bind и hostname.bind — а это бесплатная разведка для любого, кто решает, стоит ли ваша машина внимания. hide-identity и hide-version ничего не стоят и убирают этот отпечаток.
Отказывай закрыто, а не открыто. DNSSEC-валидация — это разница между резолвером, который обнаруживает подделанный ответ, и резолвером, который его отдаёт. harden-dnssec-stripped отказывается принимать неподписанный ответ для зоны, которая должна быть подписана, harden-glue и harden-below-nxdomain закрывают два классических вектора отравления кэша, а aggressive-nsec позволяет резолверу отвечать на несуществующие имена прямо из кэшированных записей отказа, не переспрашивая снова. use-caps-for-id добавляет случайный регистр букв как дополнительную энтропию против слепого спуфинга — это ничего не стоит, но иногда несовместимо с плохо собранным авторитативным сервером, и это стоит знать заранее, прежде чем вы потратите вечер на домен, который не резолвится.
Не записывайте ничего. Unbound не логирует запросы, если его не попросить, но настройки, которые включат это, — всего одна незакомментированная строка, а некоторые сборки дистрибутивов поставляются с более разговорчивым дефолтом. Задайте verbosity: 0 и явно укажите log-queries: no, чтобы намерение было видно прямо в файле, а не выводилось из его отсутствия. И не забудьте про часть, которая настройкой не является: сам кэш — это тоже запись. unbound-control dump_cache на работающей машине печатает недавнюю историю того, что эта машина искала, и живёт она в памяти машины, до которой могут дотянуться чужие руки. Она угасает сама по себе, и именно поэтому короткое время жизни кэша и приватность находятся в лёгком напряжении друг с другом, — и именно поэтому не стоит держать резолвер живым месяцами на хосте, которому вы совсем не доверяете.
Кэшируйте осознанно. Каждый ответ, отданный из кэша, — это наблюдение, которое никогда не происходит выше по цепочке, так что здоровый кэш — это не только про скорость, но и про приватность. prefetch обновляет популярные записи до того, как они истекут, так что типичный случай вообще перестаёт трогать сеть; serve-expired удерживает вас на связи, когда авторитативный сервер ненадолго недоступен. Повышение cache-min-ttl дальше снижает болтливость наверх, но при этом перекрывает осознанные решения операторов доменов — низкие TTL — это именно то, на чём работают CDN и failover, — так что порог в минуту-две разумен, а порог в час рано или поздно оставит вас на мёртвом адресе.
Два способа подключиться: туннель или DNS-over-TLS
Вашим устройствам нужно как-то дотягиваться до резолвера, и выбор между двумя разумными вариантами — это в основном вопрос того, что вы готовы опубликовать.
Для почти всех лучший ответ — это туннель. Если ноутбук и телефон уже держат сессию WireGuard до этого сервера, резолвер может слушать на адресе туннеля и говорить обычным DNS по порту 53. Трафик уже зашифрован и уже аутентифицирован туннелем, так что не нужно получать сертификат, не нужно открывать новый порт в интернет, не нужно выставлять стек TLS напоказ незнакомцам, и — вот недооценённая часть — имени хоста нигде вообще нет. Руководство по WireGuard на этом сайте оставляет у клиентов DNS = 9.9.9.9 именно потому, что раньше туда просто нечего было поставить лучше. Вот что встаёт в эту строку вместо этого: адрес туннеля вашего собственного сервера.
DNS-over-TLS — это для устройства, которое не может держать туннель. Поле Private DNS в Android — самый весомый аргумент в его пользу: оно работает системно, переживает перезагрузки и покрывает приложения, до которых иначе не дотянуться. Цена — это имя хоста с действительным сертификатом, а сертификат означает публичную, постоянную запись в логах Certificate Transparency, привязывающую это имя к моменту, когда вы его создали. Дальше passive DNS свяжет это имя с адресом сервера. Если смысл всего упражнения был в том, чтобы не оставлять своё имя на инфраструктуре, используйте имя хоста, которое никак не ведёт к вам, и регистрируйте его с той же осторожностью, что описана в материале про анонимную регистрацию домена, — а не поддомен домена, которым вы пользуетесь для всего остального: это навечно связывает их друг с другом.
Есть и вторая, более тонкая цена. Эндпоинт DoT, до которого дотягиваются роуминговые телефоны, невозможно ограничить по адресу отправителя, так что по определению это резолвер, которым сможет воспользоваться кто угодно посторонний, если узнает имя. Это не усилитель — TLS через TCP требует завершённого хендшейка, так что адрес отправителя невозможно подделать и нечего отражать, — но это раздаваемая вами ёмкость и сервис, который стоит просканировать. Держите включёнными лимиты запросов на IP, выбирайте имя хоста, которое никто не угадает, и относитесь к этому как к осознанному исключению для одного-двух устройств, а не как к двери по умолчанию.
DNS-over-HTTPS — это третий вариант, и здесь он обычно неверный. Ему нужен веб-сервер перед резолвером, а это больше подвижных частей и больше поверхности атаки — ради единственного преимущества быть неотличимым от обычного веб-трафика. Это преимущество решающее, если вы обходите сеть, блокирующую DoT, и неважное, если это не ваш случай.
SP·07Блокировка — это другой продукт; решите заранее, а не когда она уже прикручена
Рано или поздно кто-то предложит добавить блоклисты, и предложение действительно выглядит привлекательно: фильтрация на уровне резолвера покрывает каждое устройство в туннеле, включая smart TV и приложения на телефоне, куда никакое расширение браузера не дотянется. На мобильных это вообще единственное практическое место для вмешательства. Это же и та функция, которая с наибольшей вероятностью заставит вас тихо перестать доверять собственной инфраструктуре, и разобраться, почему именно, стоит заранее, а не по ходу дела.
Первая цена — в том, что сбои не похожи на сбои. Сломанный резолвер заявляет о себе сам; заблокированный домен выглядит как кнопка оплаты, которая ничего не делает, приложение, застывшее на спиннере, письмо, которое никогда не приходит. Симптом проявляется через недели после того, как вы поставили список, на устройстве, о котором вы даже не думали, и ничто не связывает его с решением про DNS, которое вы приняли совсем в другом месяце. Каждый блоклист, который вы ставите, — это политика, написанная незнакомцем и молча навязанная вашему домохозяйству. Если вы его добавляете — запишите себе, что добавили, держите его маленьким и репутационным, и держите способ выключить его одной командой: первым шагом отладки для чего угодно необъяснимого в вашей сети должно стать «отключить фильтр и попробовать снова», и этот шаг обязан быть дешёвым.
Вторая цена — в том, что это не делает того, на что люди надеются. Приложение с зашитым адресом резолвера или встроенным собственным клиентом DoH никогда ничего не спрашивает у вашего резолвера — оно просто открывает соединение на зашитый IP и всё. Браузеры всё чаще резолвят через собственный DoH, если их не заставить иначе. Блокировка на DNS — это слой гигиены, убирающий немалую часть низкоусильного трекинга и рекламного шума, а не средство защиты, потому что всё враждебное по своей природе обходит его стороной.
Если всё же хочется, предпочтите маленькую локальную зону в Unbound второму демону — local-zone: "tracker.example." always_nxdomain не требует ни лишнего софта, ни веб-интерфейса на порту, который потом придётся защищать, ни нового сервиса, который может упасть и утащить с собой резолвинг имён. Держите должностную инструкцию этой машины в одну строку: она резолвит имена. Каждая дополнительная обязанность, которую вы на неё вешаете, — это ещё один способ для всего сразу перестать работать.
Резолвер, который вы запускаете, — зависимость, которая принадлежит вам
Когда падает сайт, который вы хостите, кто-то не может что-то прочитать. Когда падает ваш резолвер, не работает ничего — ни браузер, ни почта, ни пакетный менеджер, ни приложение, которое должно было сообщить вам, что сервер упал. Это самый несущий сервис, который можно поставить на дешёвую машину, и падает он так, что это совсем не похоже на DNS.
Реалистичные режимы отказа стоит знать заранее, потому что у каждого свой почерк. VPS перезагружается, а Unbound никогда не был включён в автозагрузку — так что всё работает до первого незапланированного рестарта. Большой блоклист выталкивает кэш в swap на инстансе с 1 ГБ памяти, и OOM-killer выбирает жертвой именно резолвер. Где-то в зоне ломаются собственные подписи DNSSEC, и ваш правильно настроенный резолвер отказывается отдавать ответ, пока все, кто сидит на невалидирующем резолвере, продолжают спокойно бродить по сайту, — ваша машина права, а сайт всё равно выглядит для вас сломанным, и это сбивающие с толку пять минут, если вы успели забыть, что у вас включена валидация. Или отваливается туннель, и поскольку DNS = 10.66.0.1 существует только внутри туннеля, у устройства не остаётся вообще никакого резолвера, и оно сообщает, что оффлайн.
Смягчить это на удивление дёшево. Включите сервис в автозагрузку и реально проверьте это перезагрузкой, а не предположением. Дайте клиентам второй, резервный резолвер, чтобы мёртвый туннель деградировал, а не останавливал всё, — публичный резолвер в этой роли — это маленький, явный компромисс по приватности, который действует только тогда, когда ваш собственный недостижим, и обычно это правильный обмен. Держите serve-expired включённым, чтобы короткая авария выше по цепочке не становилась вашей аварией. Проверяйте резолвер откуда-то ещё, а не с самой машины, — это единственный способ заметить разницу между «упал» и «недостижим».
И храните копию конфига. Всё это — несколько десятков строк, на доводку которых у вас ушёл целый вечер и которые вы не вспомните через год; им место в ваших зашифрованных бэкапах вне сервера рядом с ключами WireGuard, — тогда пересборка займёт двадцать минут, а не ещё один вечер. Вот честное резюме всего этого упражнения: маленькая машина, делающая одну работу, за цену самого дешёвого тарифа из списка, заменяющая обещание о хранении данных на устройство, при котором запись просто никогда не создаётся.
SP·09Шаг за шагом
-
01
Начните с машины, которая уже защищена
Резолвер — маленький, тихий сервис, и именно поэтому так и тянет поставить его на что попало. Не надо — эта машина увидит каждое имя, которое ищет каждое устройство в вашем туннеле, так что она заслуживает того же обращения, что и всё остальное, хранящее секреты. Разверните самый маленький тариф, какой нравится; 1 ГБ памяти более чем достаточно для одного домохозяйства, поскольку размеры кэша ниже измеряются десятками мегабайт. Пройдите первый час после развёртывания прежде чем делать что-либо ещё: именованный пользователь, SSH только по ключу, default-deny на обоих семействах IP, автоматические обновления безопасности.
Затем установите Unbound. Пакет дистрибутива поставляется с корневыми hints и корневым доверенным якорем DNSSEC и настраивает автоматическую ротацию этого якоря — один из немногих случаев, когда версия из пакетного менеджера по-настоящему избавляет вас от целого класса будущих поломок.
sudo apt update && sudo apt install -y unbound dnsutils unbound -V | head -n 3 # the packaged trust anchor the resolver will validate against sudo ls -l /var/lib/unbound/root.key
Если
root.keyотсутствует, значит ваш пакет не запустилunbound-anchor, и валидация будет отказывать закрыто вообще на всём. Сгенерируйте его один раз командойsudo -u unbound unbound-anchor -a /var/lib/unbound/root.key, прежде чем продолжать. -
02
Верните порт 53 у systemd-resolved
На большинстве современных дистрибутивов порт 53 уже кем-то занят:
systemd-resolvedдержит заглушку-слушатель на127.0.0.53, а/etc/resolv.conf— это симлинк, указывающий на неё. Unbound откажется стартовать — либо стартует, но не забиндит ничего полезного, — пока это не будет решено. Сначала посмотрите, потом редактируйте.ss -ulpn 'sport = :53' ls -l /etc/resolv.conf
Прочтите этот абзац, прежде чем выполнять следующий блок: в промежутке между отключением заглушки и запуском Unbound на этой машине вообще не работает DNS. Закончите шаги 3 и 4 за один присест и не запускайте операцию
aptв другом окне заранее — она зависнет на резолвинге имени, а вы примете это за резолвер, который вы ещё не запустили.# stop resolved from holding the port (appends inside the [Resolve] section) printf 'DNSStubListener=no\n' | sudo tee -a /etc/systemd/resolved.conf sudo systemctl restart systemd-resolved # point the host at the resolver it is about to run sudo rm -f /etc/resolv.conf printf 'nameserver 127.0.0.1\noptions edns0 trust-ad\n' | sudo tee /etc/resolv.conf # the port should now be free ss -ulpn 'sport = :53'
-
03
Напишите конфигурацию, которая рекурсит и забывает
Оставьте пакетную конфигурацию в покое и добавьте свой собственный файл в drop-in-каталоге, чтобы обновление пакета никогда молча не отменяло ваши решения. Каждая строка ниже — либо про отказ незнакомцам, либо про рекурсию от корня, либо про то, чтобы не оставлять записей. Замените
10.66.0.1и10.66.0.0/24на адрес туннеля и подсеть с вашего собственного сервера WireGuard, если они отличаются.sudo tee /etc/unbound/unbound.conf.d/private-resolver.conf > /dev/null <<'EOF' server: # listen for the host itself and for the tunnel — never on 0.0.0.0 interface: 127.0.0.1 interface: 10.66.0.1 port: 53 # default-deny: refuse the internet, then allow what you trust access-control: 0.0.0.0/0 refuse access-control: ::/0 refuse access-control: 127.0.0.0/8 allow access-control: 10.66.0.0/24 allow # recurse from the root and send each server only the label it needs qname-minimisation: yes harden-dnssec-stripped: yes harden-below-nxdomain: yes harden-glue: yes aggressive-nsec: yes use-caps-for-id: yes # answer nothing about the software or the host hide-identity: yes hide-version: yes # keep no query log, and say little to the journal verbosity: 0 log-queries: no log-replies: no # a warm cache is an upstream observation that never happens cache-min-ttl: 120 cache-max-ttl: 86400 prefetch: yes prefetch-key: yes serve-expired: yes # belt and braces if a rule above is ever loosened ratelimit: 1000 ip-ratelimit: 100 # sizing for a small instance num-threads: 2 so-reuseport: yes msg-cache-size: 32m rrset-cache-size: 64m EOFОбратите внимание, чего нет в этом файле: здесь нет
forward-zone. Именно его отсутствие делает это рекурсивным резолвером, а не кэшем перед чужим. Если позже вы вставите сюда сниппет из какого-нибудь туториала, который добавляет такую секцию, вы тихо перечеркнёте весь смысл этого упражнения. -
04
Запустите и докажите, что DNSSEC действительно валидирует
Проверьте синтаксис, прежде чем что-либо перезапускать — на машине, чей собственный
resolv.confтеперь указывает на Unbound, ошибка конфига означает отсутствие резолвинга имён прямо во время отладки.sudo unbound-checkconf sudo systemctl enable --now unbound systemctl --no-pager status unbound | head -n 5
Теперь проверьте два поведения, которые действительно важны, потому что резолвер, который отвечает, — это не то же самое, что резолвер, который валидирует. Подписанное имя обязано вернуться с флагом
ad— authenticated data, — а имя с намеренно сломанными подписями обязано отказать закрыто сSERVFAIL, а не зарезолвиться.# should show: flags: qr rd ra ad dig @127.0.0.1 example.com +dnssec | grep -E '^;; flags' # should show: status: SERVFAIL (not NOERROR, not an address) dig @127.0.0.1 dnssec-failed.org | grep -E '^;; ->>HEADER' # and a normal name should simply work dig @127.0.0.1 +short cloudflare.com
Если сломанная зона резолвится в адрес, значит валидация не работает: проверьте, что
/var/lib/unbound/root.keyсуществует и доступен для чтения пользователюunbound. Если всё подряд отдаётSERVFAIL, обычная причина — это сильно разъехавшиеся часы: у подписей есть окна действительности, и машина, разошедшаяся на несколько часов, отказывает всему интернету сразу. -
05
Закройте публичную дверь, оставьте открытым только туннель
Конфигурация уже отказывает незнакомцам. Этот шаг делает так, что незнакомцу вообще нечего достигать, — второй из двух замков, и именно тот, что переживёт будущую правку первого.
# DNS is reachable from the tunnel interface only sudo ufw allow in on wg0 to any port 53 proto udp sudo ufw allow in on wg0 to any port 53 proto tcp sudo ufw status verbose
Затем проверьте это единственным способом, который что-то значит, — с другой машины. Проверка с самого сервера ничего не доказывает вообще — loopback разрешён намеренно. Запустите это с вашего ноутбука с отключённым туннелем, или с любого другого хоста, который у вас есть.
# must time out. an answer here means you are running an open resolver. dig @SERVER_PUBLIC_IP example.com +time=3 +tries=1 # same question over IPv6, which is the half people forget dig -6 @SERVER_IPV6 example.com +time=3 +tries=1
Если хоть один из них вернул ответ, остановитесь и исправьте это прямо сейчас, а не после жалобы на абьюз. Обычные причины — это оставшийся в пакетном конфиге
interface: 0.0.0.0, правило firewall, разрешающее 53 глобально после более раннего эксперимента, либо Docker, публикующий порт контейнера в обходufwцеликом. -
06
Направьте на него ваши устройства
На стороне клиента это правка в одну строку. В конфигурации клиента WireGuard строка
DNSстановится адресом туннеля вашего сервера вместо публичного резолвера — это и есть та правка, что отправляет на пенсиюDNS = 9.9.9.9из руководства по WireGuard.[Interface] PrivateKey = <paste client.key> Address = 10.66.0.2/32, fd86:ea04:1115::2/128 DNS = 10.66.0.1 [Peer] PublicKey = <paste server.pub> Endpoint = YOUR_SERVER_IP:51820 AllowedIPs = 0.0.0.0/0, ::/0 PersistentKeepalive = 25
Поднимите туннель и подтвердите с клиента две отдельные вещи: что ответы приходят от вашего резолвера, и что рекурсия на самом деле выходит в мир именно с вашего VPS, а не откуда-то ещё. Вторая проверка — та, что реально полезна:
whoami.akamai.netвозвращает адрес того резолвера, который спросил, так что он должен напечатать публичный IP вашего сервера и ничего больше.# answers should come from the tunnel address dig example.com | grep -E 'SERVER:' # should print your VPS public IP — this is the leak test dig +short whoami.akamai.net # and the ad flag should still be there, end to end dig example.com +dnssec | grep -E '^;; flags'
-
07
Добавьте DNS-over-TLS только для устройства, которое не может держать туннель
Пропустите этот шаг, если у вас нет конкретного устройства — как правило, это телефон на Android с его системной настройкой Private DNS, — которое вы хотите покрыть без постоянного туннеля. Для этого нужны имя хоста и сертификат, а это имя хоста становится постоянной публичной записью в логах Certificate Transparency, так что выбирайте такое, которое никак не ведёт к вашим прочим личностям.
sudo apt install -y certbot sudo ufw allow 80/tcp comment 'certbot, temporarily' sudo certbot certonly --standalone -d dns.example.net sudo ufw delete allow 80/tcp # unbound must be able to read the key sudo usermod -a -G ssl-cert unbound 2>/dev/null || true sudo chgrp -R ssl-cert /etc/letsencrypt/live /etc/letsencrypt/archive sudo chmod -R g+rX /etc/letsencrypt/live /etc/letsencrypt/archive
Добавьте TLS-листенер отдельным drop-in-файлом, чтобы можно было удалить его одним движением, если вы измените решение.
# NOTE: drop-in files are read in alphabetical order and the last # access-control line for a given prefix wins — so this file must # sort AFTER private-resolver.conf, or its refuse rule overrides # the allow below and every DoT client gets REFUSED. sudo tee /etc/unbound/unbound.conf.d/zz-dot.conf > /dev/null <<'EOF' server: interface: 0.0.0.0@853 interface: ::0@853 tls-service-key: "/etc/letsencrypt/live/dns.example.net/privkey.pem" tls-service-pem: "/etc/letsencrypt/live/dns.example.net/fullchain.pem" # roaming clients have no fixed address, so this endpoint must accept any. # safe only because port 53 stays bound to loopback + wg0 and blocked at # the firewall — TLS over TCP cannot be spoofed, so it cannot be reflected. access-control: 0.0.0.0/0 allow access-control: ::/0 allow EOF sudo ufw allow 853/tcp sudo unbound-checkconf && sudo systemctl restart unboundПроверьте это снаружи туннеля, прежде чем довериться, а затем впишите имя хоста в поле Private DNS на телефоне. Продление — это та часть, которая тихо ломается три месяца спустя: certbot заменяет сертификат, но Unbound держит старый в памяти, так что добавьте deploy-хук, который его перезагружает.
kdig -d @dns.example.net +tls-ca +tls-host=dns.example.net example.com echo 'systemctl reload unbound' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/unbound.sh sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/unbound.sh


