DNS, TLS, сертификаты
Кратко о теме
Заголовок раздела «Кратко о теме»DNS и TLS решают две разные задачи, которые в жизни браузера и любого сетевого клиента идут подряд: сначала надо узнать, куда подключаться, потом — убедиться, что на том конце тот самый сервер, и что канал никто не читает. DNS — распределённая иерархическая база данных, отображающая доменные имена в записи (чаще всего в IP-адреса). Ключевая идея — делегирование: ни один сервер не знает всех имён мира, он знает только свою зону и указатели на серверы дочерних зон. Корень (.) делегирует зону com. серверам TLD-регистратуры, те делегируют google.com. серверам Google, и уже они авторитетно отвечают на www.google.com. Резолвер спускается по этой цепочке сверху вниз и кеширует всё, что получил, на время TTL — именно кеширование делает систему работоспособной под нагрузкой всего интернета.
Данные в DNS хранятся не «строками», а наборами ресурсных записей (RRset): каждая запись — это кортеж «имя, класс (обычно IN), тип, TTL, значение». Тип определяет смысл значения: A — IPv4, AAAA — IPv6, CNAME — псевдоним, MX — почтовый маршрут и так далее. Ответ авторитетного сервера возвращает весь RRset целиком, а клиент выбирает из него сам (например, один из нескольких A-адресов) — на этом строится примитивная round-robin-балансировка на уровне DNS.
TLS (Transport Layer Security) — протокол поверх надёжного транспорта (TCP; в QUIC его handshake встроен внутрь), который даёт три свойства: аутентификацию сервера (и опционально клиента) по сертификатам X.509, конфиденциальность (симметричное AEAD-шифрование) и целостность (тот же AEAD-тег плюс криптографический контроль всей истории handshake). Схема гибридная: асимметричная криптография используется только на handshake, чтобы согласовать общий секрет и подтвердить личность, дальше данные шифруются быстрым симметричным алгоритмом (AES-GCM, ChaCha20-Poly1305). Современный handshake всегда использует эфемерный Диффи–Хеллман (ECDHE/X25519), что даёт forward secrecy: компрометация приватного ключа сервера завтра не расшифрует записанный сегодня трафик.
Доверие к сертификату строится на PKI: сертификат сервера подписан промежуточным CA, тот — корневым, а корневые сертификаты предустановлены в trust store ОС/браузера. Клиент проверяет цепочку подписей до доверенного корня, срок действия, соответствие имени хоста полю SAN, назначение ключа, отзыв (OCSP/CRL) и — в браузерах — наличие SCT из Certificate Transparency. Отдельно и обязательно: сервер должен доказать владение приватным ключом (в TLS 1.3 — сообщением CertificateVerify, подписью над транскриптом handshake), иначе любой мог бы скачать чужой публичный сертификат и им прикрыться.
Вопросы и ответы
Заголовок раздела «Вопросы и ответы»Что такое ДНС-записи и какие есть типы ДНС-записей?
Заголовок раздела «Что такое ДНС-записи и какие есть типы ДНС-записей?»Коротко. DNS-запись (resource record, RR) — единица данных в зоне: имя, тип, класс, TTL и значение. Основные типы: A (IPv4), AAAA (IPv6), CNAME (псевдоним), NS (делегирование зоны), SOA (параметры зоны), MX (почтовые серверы), TXT (произвольный текст — SPF, DKIM, верификация владения), PTR (обратная зона, IP → имя), SRV (адрес+порт сервиса), CAA (кто из CA может выпускать сертификаты на домен).
Глубже. Записи одного имени и типа образуют RRset и всегда возвращаются целиком — поэтому у домена может быть несколько A-записей, и клиент/резолвер перебирает их. Тонкости, которые любят спрашивать: CNAME не может сосуществовать с другими записями того же имени, поэтому на «голом» домене (example.com, у которого обязаны быть SOA и NS) CNAME невозможен — провайдеры решают это нестандартными ALIAS/ANAME, разворачивая псевдоним на своей стороне. SOA содержит серийный номер зоны и таймеры (refresh/retry/expire/minimum), где minimum сегодня трактуется как TTL негативного кеширования (RFC 2308) — «этого имени нет» тоже кешируется. SRV — это _service._proto.name со значением «приоритет, вес, порт, хост»; на нём построен discovery в Kubernetes (_http._tcp.my-svc.ns.svc.cluster.local) и раньше держался XMPP/SIP. Для DNSSEC добавляются DNSKEY, RRSIG, DS, NSEC/NSEC3. Из свежего — SVCB/HTTPS (RFC 9460): позволяют отдать в DNS ALPN, порт, подсказки адресов и ключи Encrypted Client Hello, чтобы браузер сразу пошёл в HTTP/3 без Alt-Svc-редиректа.
Что такое DNS?
Заголовок раздела «Что такое DNS?»Коротко. DNS (Domain Name System) — глобальная распределённая иерархическая система имён, которая переводит человекочитаемые доменные имена в IP-адреса и другие ресурсные записи. Это не один сервер, а дерево зон с делегированием и агрессивным кешированием на каждом уровне.
Глубже. Пространство имён — дерево с корнем .; имя читается справа налево (www.google.com. = корень → com → google → www). Ответственность за поддерево называется зоной, и зона может делегировать часть себя дочерним серверам записями NS. Участники: stub-резолвер в ОС/приложении (умеет только спросить «полный ответ, пожалуйста»), рекурсивный резолвер (провайдерский, корпоративный, 8.8.8.8, 1.1.1.1 — он и делает всю работу и кеширует), авторитетные серверы зон. Формат сообщения общий для запроса и ответа: заголовок с флагами (QR, RD — recursion desired, RA, AA — authoritative answer, TC — truncated, RCODE) и четыре секции: Question, Answer, Authority, Additional. Практическая ценность DNS — не только «имя → IP», а слой косвенности: можно поменять адрес сервиса, переехать в другой ДЦ, отдать разным регионам разные адреса (GeoDNS) или увести трафик при аварии, не трогая клиентов. Плата за это — TTL: пока кеши не протухли, старый адрес живёт, поэтому DNS-failover измеряется минутами, а не секундами. В Go резолвинг делает net.Resolver; рантайм умеет два бэкенда — чистый Go-резолвер и системный через cgo, выбор управляется GODEBUG=netdns=go|cgo; важно помнить, что стандартная библиотека Go не кеширует DNS-ответы сама, кеш — это ОС или локальный резолвер.
Какой протокол используется на транспортном уровне для DNS? На каком порту?
Заголовок раздела «Какой протокол используется на транспортном уровне для DNS? На каком порту?»Коротко. Классический DNS работает на UDP, порт 53; TCP на том же 53-м порту используется, когда ответ не влезает в датаграмму (сервер выставляет флаг TC, и клиент повторяет запрос по TCP), а также для передач зон (AXFR/IXFR). Из шифрованных вариантов: DoT — TCP/853, DoH — HTTPS/443, DoQ — QUIC/853.
Глубже. UDP выбран из-за цены: один пакет туда, один обратно, без handshake и состояния — при объёмах DNS это принципиально. Исторический лимит DNS-ответа по UDP — 512 байт полезной нагрузки; EDNS(0) (RFC 6891) добавил псевдозапись OPT, в которой клиент объявляет, какой размер UDP-ответа он готов принять (типично 1232 байта — консервативное значение, чтобы не попадать на IP-фрагментацию). Отсутствие handshake — источник двух проблем: подделка ответов (спуфинг, отсюда рандомизация Query ID и порта источника, а системно — DNSSEC, который подписывает данные, но не шифрует их) и DNS-amplification DDoS, когда маленький запрос со спуфленным адресом жертвы порождает большой ответ. Отсюда же современная тенденция — DoT/DoH/DoQ, которые прячут запросы от провайдера, но переносят доверие на выбранного резолвера. В Kubernetes и обычных Linux-хостах поведение stub-резолвера задаётся /etc/resolv.conf: nameserver, search-домены, ndots (по умолчанию 1, в подах k8s — 5, из-за чего внешние имена сначала безуспешно перебираются по search-доменам — классическая причина «медленного DNS» в кластере).
Как устанавливается защищенное соединение по TLS?
Заголовок раздела «Как устанавливается защищенное соединение по TLS?»Коротко. Сначала TCP-хендшейк, затем TLS-хендшейк: клиент шлёт ClientHello (версии, список шифронаборов, свою половину эфемерного ключа key_share, SNI, ALPN), сервер отвечает ServerHello со своим key_share и выбранным шифронабором, обе стороны считают общий секрет по ECDHE и выводят из него симметричные ключи; сервер шифрованно присылает сертификат и CertificateVerify (подпись над транскриптом, доказывающая владение приватным ключом), обе стороны обмениваются Finished — MAC над всей историей сообщений — и дальше идут прикладные данные под AEAD. В TLS 1.3 это один RTT.
Глубже. Отличие TLS 1.2: там два RTT, ключевой материал согласовывался после ServerHello через ServerKeyExchange/ClientKeyExchange, а сертификат передавался открытым текстом; допускался RSA key exchange (клиент шифровал premaster публичным ключом сервера) — без forward secrecy, и в TLS 1.3 он выпилен вместе с CBC, RC4, сжатием и переговорами о произвольных DH-группах. В TLS 1.3 остались только AEAD-наборы: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256. Ключи выводятся HKDF-цепочкой, у handshake-трафика и application-трафика они разные, есть KeyUpdate для перегенерации на длинных соединениях. Возобновление сессии в 1.3 делается через PSK-тикеты (NewSessionTicket), а поверх PSK возможен 0-RTT — данные летят вместе с ClientHello, но такие данные уязвимы к replay, поэтому под 0-RTT можно класть только идемпотентные запросы. Finished защищает от downgrade-атак: любая подмена списка шифронаборов посередине сломает MAC транскрипта; дополнительно в ServerHello.random зашиты «канареечные» байты против отката версии. Мьютуал-TLS — тот же поток плюс CertificateRequest от сервера и Certificate+CertificateVerify от клиента.
// Минимально разумный TLS-сервер и клиент на Go.srv := &http.Server{ Addr: ":443", TLSConfig: &tls.Config{ MinVersion: tls.VersionTLS12, // 1.3 согласуется автоматически NextProtos: []string{"h2", "http/1.1"}, // ALPN GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { return certForSNI(chi.ServerName) // выбор сертификата по SNI }, },}_ = srv.ListenAndServeTLS("", "")
cli := &http.Client{Transport: &http.Transport{ TLSClientConfig: &tls.Config{ MinVersion: tls.VersionTLS12, RootCAs: pool, // свой trust store вместо системного ServerName: "api.internal", // если ходим по IP, но проверяем имя },}}_ = cliИз версионных изменений Go: с Go 1.18 клиент по умолчанию не согласует ниже TLS 1.2; в Go 1.22 по умолчанию отключены шифронаборы с RSA key exchange (вернуть можно GODEBUG=tlsrsakex=1); в Go 1.23–1.24 в TLS 1.3 по умолчанию включён гибридный постквантовый обмен ключами (в 1.23 — черновой X25519Kyber768, в 1.24 — стандартизованный X25519MLKEM768).
Что происходит когда вбиваешь google.com в браузерере, зачем нужен DNS?
Заголовок раздела «Что происходит когда вбиваешь google.com в браузерере, зачем нужен DNS?»Коротко. Браузер разбирает строку (это URL или поисковый запрос), проверяет HSTS-список и подставляет https://, резолвит имя в IP через цепочку кешей и DNS-резолверов, устанавливает TCP (или QUIC) соединение, проводит TLS-хендшейк с SNI и ALPN, отправляет HTTP-запрос, получает ответ и рендерит страницу, попутно повторяя всё то же для подресурсов. DNS нужен потому, что маршрутизация в интернете работает по IP-адресам, а имена дают слой косвенности: адрес можно менять, размножать и раздавать по регионам, не трогая пользователей и ссылки.
Глубже. Детали, которыми отличают хороший ответ. Резолвинг идёт по слоям: кеш браузера → кеш ОС (nscd/systemd-resolved/DNS Client) → /etc/hosts → рекурсивный резолвер → корень/TLD/авторитетный сервер; на каждом шаге работает TTL. Для имени вроде google.com вернутся и A, и AAAA, и браузер применит Happy Eyeballs (RFC 8305): начнёт подключаться по IPv6, а через ~250 мс параллельно по IPv4 и возьмёт то, что установилось первым. Дальше TCP three-way handshake на 443, TLS 1.3-хендшейк, в ClientHello уходит SNI (имя хоста открытым текстом, если не используется ECH) и ALPN (h2, http/1.1), сервер может ответить заголовком Alt-Svc, и следующее соединение уйдёт уже по HTTP/3 поверх QUIC/UDP. Затем HTTP-запрос, ответ (для google.com — скорее всего 301 на www.google.com, то есть цикл повторяется), парсинг HTML, построение DOM/CSSOM, загрузка подресурсов. Полезно добавить, что реальный IP приходит от GeoDNS/anycast и указывает на ближайшую точку присутствия CDN, а не на «сервер Google» — то есть DNS здесь работает ещё и как механизм географической балансировки.
Application Load Balancer (L7) - прием входящих запросов и терминирование tls
Заголовок раздела «Application Load Balancer (L7) - прием входящих запросов и терминирование tls»Коротко. L7-балансировщик принимает TCP-соединение и завершает (терминирует) на себе TLS: у него лежат сертификат и приватный ключ, он проводит хендшейк с клиентом, расшифровывает трафик, разбирает HTTP-запросы и маршрутизирует их по хосту/пути/заголовкам в нужную target-группу, открывая к бэкендам свои соединения (в plaintext или с повторным шифрованием). Плата за это — балансировщик видит трафик в открытом виде и становится точкой, где нужно управлять сертификатами.
Глубже. Что происходит на терминации: по SNI выбирается сертификат (один ALB спокойно обслуживает десятки доменов), по ALPN согласуется протокол (h2 наружу, при этом к бэкенду может идти HTTP/1.1), после расшифровки становятся доступны маршрутизация по path/host/header, WAF, rate limiting, сжатие, sticky sessions по cookie, и балансировка становится по-запросная, а не по-соединенческая — в HTTP/2 несколько потоков одного соединения могут уехать на разные поды. Исходный IP клиента после терминации теряется, поэтому балансировщик добавляет X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Port (в AWS ALB ещё и X-Amzn-Trace-Id), а приложение обязано доверять этим заголовкам только от самого балансировщика — иначе спуфинг клиентского IP. Альтернативы терминации: TLS passthrough на L4 (NLB/stream в nginx) — балансировщик не расшифровывает, маршрутизирует максимум по SNI, сертификат живёт на бэкенде, клиентский IP сохраняется (или прокидывается PROXY-протоколом); и re-encryption (TLS bridging) — терминировали, посмотрели, снова зашифровали к бэкенду, что обязательно в zero-trust-схемах и в service mesh с mTLS. Практические грабли: автоматическое продление сертификата (ACM/cert-manager) и перезагрузка конфигурации без разрыва соединений; несогласованные keep-alive/idle-таймауты между LB и бэкендом дают спорадические 502 (бэкенд закрывает соединение ровно в момент переиспользования — таймаут на бэкенде должен быть больше, чем на LB); health-check-и должны ходить на реальный лёгкий эндпоинт; при mTLS клиентский сертификат после терминации передаётся бэкенду заголовком (X-Amzn-Mtls-Clientcert, ssl_client_escaped_cert в nginx).
В чем смысл TLS? Зачем он нужен? Как происходит проверка, что сервер - это тот, за кого он себя выдает (как браузерер понимает, что сертификату можно доверять)?
Заголовок раздела «В чем смысл TLS? Зачем он нужен? Как происходит проверка, что сервер - это тот, за кого он себя выдает (как браузерер понимает, что сертификату можно доверять)?»Коротко. TLS даёт три вещи: конфиденциальность (посредник не прочитает трафик), целостность (не изменит незаметно) и аутентификацию сервера (клиент говорит именно с тем доменом, который набрал). Доверие проверяется по цепочке: сертификат сервера подписан промежуточным CA, тот — корневым, корневой лежит в предустановленном trust store браузера/ОС; дополнительно проверяются срок действия, соответствие имени хоста полю SAN, назначение ключа и отзыв; и главное — сервер подписывает своим приватным ключом данные хендшейка, доказывая, что сертификат действительно его.
Глубже. Полный список проверок при валидации: (1) построение цепочки от листового сертификата до корня, которому клиент доверяет, с проверкой подписи каждого звена публичным ключом вышестоящего; (2) notBefore/notAfter каждого сертификата в цепочке; (3) у промежуточных — basicConstraints: CA=TRUE и допустимая длина пути, keyUsage: keyCertSign; (4) у листового — subjectAltName содержит запрошенное имя (в том числе wildcard *.example.com, покрывающий ровно один уровень); поле CN как источник имени современными браузерами игнорируется; (5) extendedKeyUsage: serverAuth; (6) отзыв — CRL, OCSP, OCSP stapling (сервер сам прикладывает свежий подписанный CA ответ, экономя клиенту запрос и не сливая CA историю посещений), в Chrome — собственные CRLSets; (7) Certificate Transparency: браузер требует SCT-подтверждения, что сертификат опубликован в публичных CT-логах, — это и вылавливает misissuance. Ключевой момент, который обычно и хотят услышать: сертификат публичный, его может скачать кто угодно, поэтому сам по себе он ничего не доказывает; доказательством служит CertificateVerify — подпись приватным ключом над транскриптом хендшейка (в TLS 1.2 при ECDHE — подпись параметров в ServerKeyExchange), которую клиент проверяет публичным ключом из сертификата. Отсюда же понятно, почему MITM-прокси (корпоративный, Fiddler, Charles) работает только если его корневой сертификат вручную добавлен в trust store. Для повышенных требований поверх PKI ставят pinning (в Go — tls.Config.VerifyPeerCertificate или собственный RootCAs), а InsecureSkipVerify: true отключает разом весь этот блок и в проде недопустим.
DNS сервер хранит у себя все домены мира или как он находит тe, которых у него нет?
Заголовок раздела «DNS сервер хранит у себя все домены мира или как он находит тe, которых у него нет?»Коротко. Нет, не хранит. Авторитетный сервер знает только свои зоны, а рекурсивный резолвер вообще ничего не «знает» — он начинает с корневых серверов (их адреса зашиты в файле root hints) и спускается по делегациям: корень отвечает «спроси серверы зоны com.», те — «спроси серверы example.com.», и уже они дают финальный ответ. Полученное резолвер кеширует на TTL, поэтому большинство реальных запросов дальше кеша не уходят.
Глубже. Такие запросы называются итеративными: резолвер задаёт вопрос без флага «сделай всё сам» и получает не ответ, а referral — NS-записи дочерней зоны в секции Authority и их IP в Additional (glue-записи; они обязательны, когда NS-имя лежит внутри самой делегируемой зоны, иначе получилась бы циклическая зависимость). Корневых серверов — 13 «букв» (a..m.root-servers.net), но это не 13 машин: каждая буква анонсируется по anycast сотнями инстансов по миру, и запрос уходит в топологически ближайший. Нагрузка на корень мала ещё и потому, что резолверы кешируют NS-записи TLD надолго, а современные умеют aggressive NSEC caching и QNAME minimization (спрашивают у корня только com., не раскрывая полное имя). Отрицательные ответы (NXDOMAIN) тоже кешируются — на время из поля minimum в SOA. Полную копию зоны сервер получает только в одном сценарии — трансфер зоны (AXFR/IXFR) между primary и secondary одной зоны, по TCP и с авторизацией; «скачать весь интернет» так нельзя, а корневая зона хоть и публична, но содержит только делегации TLD, а не сами домены.
Что такое TLS?
Заголовок раздела «Что такое TLS?»Коротко. См. выше про смысл TLS: это криптографический протокол поверх надёжного транспорта, дающий аутентификацию по сертификатам X.509, конфиденциальность и целостность данных. Отличие этой формулировки от предыдущего вопроса — здесь стоит добавить историю и место в стеке: TLS — прямой потомок SSL (SSL 2.0/3.0 давно запрещены), актуальны версии 1.2 (RFC 5246) и 1.3 (RFC 8446), а «HTTPS» — это просто HTTP, пропущенный через TLS.
Глубже. По уровням TLS живёт между транспортом и приложением: для приложения это обычный поток байт, для сети — TCP-соединение, внутри которого идут TLS-рекорды (заголовок с типом и длиной + AEAD-шифротекст). Протокол состоит из подпротоколов: Handshake (согласование), Record (обёртка данных), Alert (ошибки и close_notify) и — до 1.3 — Change Cipher Spec. Один и тот же TLS используют HTTPS, gRPC, SMTPS/STARTTLS, а в QUIC хендшейк TLS 1.3 встроен в сам транспорт, поэтому HTTP/3 шифрован по определению. Важные оговорки, которые отличают точный ответ: TLS не скрывает факт соединения и его метаданные — IP назначения, размеры и тайминги пакетов, а также SNI, который до появления Encrypted Client Hello летит открытым текстом (в Go поддержка ECH добавлялась в 1.23–1.24). TLS также не защищает от уязвимостей приложения — он гарантирует безопасность канала, а не корректность того, что по нему передаётся.
Частые ошибки на собесе
Заголовок раздела «Частые ошибки на собесе»- Говорят «DNS — это сервер, который хранит соответствие имён и IP», не упоминая иерархию, делегирование и кеширование; на уточняющий вопрос «а если имени нет?» ответа не остаётся.
- Утверждают, что DNS работает «только по UDP». TCP/53 — штатный режим при усечённом ответе (
TC) и при трансфере зон, а DoT/DoH вообще живут на 853/443. - Путают
CNAMEиA: заявляют, что на apex-домене можно поставить CNAME, и не знают про запрет сосуществования CNAME с другими записями. - Считают TTL мгновенным рычагом: «поменяли A-запись — трафик сразу переехал». Пока кеши резолверов и приложений не истекли, часть клиентов идёт по старому адресу.
- Описывают TLS как «шифрование трафика», забывая про аутентификацию, и не могут объяснить, чем HTTPS защищает от MITM, если тот тоже умеет шифровать.
- Говорят, что «данные шифруются публичным ключом сервера»: в TLS 1.3 RSA key exchange нет вовсе, ключи согласуются по ECDHE, а сертификат нужен только для подписи и подтверждения личности.
- Не могут ответить, что мешает злоумышленнику взять публичный сертификат google.com и им прикрыться — упускают
CertificateVerifyи владение приватным ключом. - Путают forward secrecy с «сертификат перевыпускается раз в 90 дней» и не связывают её с эфемерностью DH-ключей.
- Про L7-балансировщик забывают, что после терминации TLS теряется исходный IP клиента, и что доверять
X-Forwarded-Forот кого угодно нельзя. - В Go-контексте: считают, что
net.Dialкеширует DNS (не кеширует), и пишутInsecureSkipVerify: true«чтобы заработало на стенде», не понимая, что это отключает всю проверку подлинности сервера.
Что почитать
Заголовок раздела «Что почитать»- RFC 1034/1035 — базовая архитектура и формат сообщений DNS: https://datatracker.ietf.org/doc/html/rfc1035
- RFC 6891 (EDNS(0)) и RFC 2308 (негативное кеширование) — почему ответы больше 512 байт и как кешируется NXDOMAIN: https://datatracker.ietf.org/doc/html/rfc6891
- RFC 8446 — TLS 1.3, включая полный поток сообщений и вывод ключей: https://datatracker.ietf.org/doc/html/rfc8446
- The Illustrated TLS 1.3 Connection — побайтовый разбор реального хендшейка: https://tls13.xargs.org/
- Документация
crypto/tlsиnetв Go (tls.Config,net.Resolver,GODEBUG=netdns): https://pkg.go.dev/crypto/tls и https://pkg.go.dev/net - Kubernetes DNS для сервисов и подов (
ndots, search-домены, SRV-записи): https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/
Список исходных вопросов с привязкой к компаниям: ../questions/dns-tls.md