2 lines
24 KiB
JSON
2 lines
24 KiB
JSON
{"index":23,"slug":"editorial-2027-05-mechanism-http-tls-guide","title":"Почему HTTPS ошибается в разных местах: карта границ DNS, TCP, TLS и HTTP","excerpt":"Ошибка сертификата, таймаут и 404 могут выглядеть одинаково для пользователя, но требуют разных проверок. Разбираем порядок запроса, доверие к серверу, роль proxy и воспроизводимую запись результата.","contentHtml":"<p>В браузере одна красная страница, в логе клиента другая строка, а инженер уже меняет таймаут или отключает проверку сертификата. Такой ремонт часто начинается раньше факта: запрос мог остановиться на DNS, TCP или TLS и не дойти до HTTP. В обратной ситуации сертификат проверен, но приложение или proxy вернули <code>404</code>, и поиски проблемы в TLS только уводят в сторону.</p><p>У запроса есть наблюдаемая последовательность: имя превращается в адрес, TCP принимает соединение, TLS проверяет защищённый канал и identity сервера, затем HTTP передаёт метод, цель и поля. Диагностика становится воспроизводимой, если на каждой границе задать свой вопрос, записать факт и не переносить вывод на следующий уровень. Эта схема не обещает мгновенно назвать виновника; она сужает место поиска и подсказывает следующий безопасный тест.</p><h2>Один URL — несколько независимых границ</h2><p>Начните с URI, например <code>https://api.example.test/orders</code>. Из него клиент получает схему, имя, порт и путь. DNS отвечает, какой адрес связан с именем. TCP отвечает, удалось ли открыть соединение с адресом и портом. Для HTTPS поверх TCP запускается TLS. Только после успешного TLS клиент отправляет HTTP-сообщение и получает статус, поля и тело.</p><p>Порядок важен не как учебная условность, а как ограничение доказательств. Если имя не разрешилось, вы ещё не проверяли порт. Если TCP не установлен, у приложения нет основания считать запрос полученным. Если TLS остановился на проверке identity, HTTP-заголовки и статус не объясняют отказ. Если статус <code>404</code> уже прочитан, TLS-обмен прошёл для этого соединения, но это ещё не доказательство, что ответ сформировал нужный origin.</p><div class=\"table-scroll\"><table><caption>Что доказывает наблюдение на каждой границе</caption><thead><tr><th scope=\"col\">Граница</th><th scope=\"col\">Наблюдение</th><th scope=\"col\">Допустимый вывод</th><th scope=\"col\">Чего пока нет</th></tr></thead><tbody><tr><td>DNS</td><td>Получен A или AAAA-ответ</td><td>Имя разрешилось в один из адресов</td><td>Доступность порта и соответствие сертификата</td></tr><tr><td>TCP</td><td>Соединение установлено</td><td>Адрес принял соединение на выбранном порту</td><td>Успешная проверка TLS и HTTP-маршрут</td></tr><tr><td>TLS</td><td>Клиент принял защищённый канал</td><td>Проверки identity и доверия прошли для этого клиента</td><td>Нужный handler получил запрос</td></tr><tr><td>HTTP</td><td>Получены статус и поля</td><td>HTTP-обмен состоялся</td><td>Тело создал origin, а не cache или proxy</td></tr><tr><td>Приложение</td><td>Доверенный лог связал request id с handler</td><td>Известен компонент, обработавший вход</td><td>Так же ли настроены другие адреса и регионы</td></tr></tbody></table></div><figure><img src=\"/assets/editorial/2027/http-tls-guide-2027-symptom-boundary-matrix.svg\" alt=\"Матрица симптомов HTTP и TLS: ошибка имени относится к TLS, 404 к HTTP-маршруту, а 503 требует проверки отвечающего компонента.\" loading=\"lazy\" /><figcaption>Симптом указывает на границу проверки, но не всегда на конкретного виновника: источник ответа подтверждается только доверенным логом.</figcaption></figure><p>Доверенный лог здесь важнее знакомого заголовка. Клиент может получить <code>Server: familiar-product</code> или <code>X-Request-Id: local-001</code>, но оба поля могли добавить или изменить по пути. Доказательство источника появляется, когда компонент под вашим контролем создал идентификатор и записал его вместе со временем, маршрутом и результатом.</p><h2>Как читать ошибку без догадки о виновнике</h2><p>Ошибка до HTTP-статуса ограничивает поиск транспортом. Сообщение о несовпадении имени сертификата требует проверить hostname и сертификат; оно не доказывает, что порт недоступен. Отказ TCP требует проверить маршрут, firewall, listener или балансировщик; он не говорит, какой HTTP-путь должен существовать. Таймаут без раздельных фаз оставляет место остановки неизвестным — не подставляйте туда удобную версию.</p><p>HTTP-ошибка уже принадлежит сообщению, которое кто-то сформировал. <code>404 Not Found</code> означает, что получен HTTP-ответ с таким статусом, но не называет внутренний маршрут и не доказывает origin. <code>503 Service Unavailable</code> сообщает о недоступности для данного отвечающего компонента; это может быть proxy, gateway или приложение. Ищите request id и запись того слоя, который действительно отправил ответ клиенту.</p><table><caption>Симптом и первый тест</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Гипотеза с ограниченной уверенностью</th><th scope=\"col\">Первый тест</th><th scope=\"col\">Не делать сразу</th></tr></thead><tbody><tr><td>Нет статуса, ошибка сертификата</td><td>Не прошла проверка имени, цепочки или срока</td><td>Сверить hostname, SAN, trust store и срок действия</td><td>Не добавлять <code>Host</code> и не включать <code>--insecure</code> постоянно</td></tr><tr><td>Имя разрешилось, connect отклонён</td><td>Порт, маршрут или listener не принимает соединение</td><td>Сохранить адрес, порт и тип TCP-ошибки</td><td>Не проверять URL и бизнес-логику как первопричину</td></tr><tr><td>TLS успешен, получен <code>404</code></td><td>Путь, метод, authority или версия маршрута не совпали</td><td>Сверить запрос с логом входного proxy и известным endpoint</td><td>Не перевыпускать сертификат без нового факта</td></tr><tr><td>Получен <code>503</code></td><td>Отвечающий компонент не обслуживает запрос или не дождался upstream</td><td>Сопоставить слой, время ответа, <code>Via</code> и upstream-лог</td><td>Не увеличивать timeout и не повторять запись вслепую</td></tr><tr><td>Повтор даёт другой ответ</td><td>Кэш, балансировка, редирект или меняющееся состояние</td><td>Сравнить адрес, <code>Age</code>, <code>ETag</code>, <code>Cache-Control</code> и время</td><td>Не объединять ответы разных попыток в одну причину</td></tr></tbody></table><h2>TLS: что именно проверяет клиент</h2><p>Для HTTPS недостаточно сказать «сертификат действующий». Клиент строит reference identity из имени или IP в URI и сопоставляет её с именами в сертификате. Затем он проверяет цепочку к доверенному anchor и период действия. Эти проверки отвечают на разные вопросы: сертификат может быть выдан правильным центром, но не для этого имени; имя может совпасть, но цепочка не доверена локальному клиенту; оба условия могут пройти, а срок — закончиться.</p><p>Подключение к IP вместо имени часто меняет проверяемую identity. Если сертификат выпущен для DNS-имени, сам факт, что IP указывает на тот же сервер, не делает IP подходящим именем. HTTP-поле <code>Host</code> не исправляет ошибку задним числом: оно появляется на уровне HTTP, после установления TLS. В HTTP/2 и HTTP/3 роль имени и порта в запросе обычно представляет <code>:authority</code>; при диагностике учитывайте фактическую версию протокола.</p><p>Флаг <code>--insecure</code> или его аналог может быть полезен в коротком разрешённом эксперименте: он показывает, что удалённая сторона способна отправить данные без обычной проверки identity. Но такой ответ не доказывает безопасность канала и не является исправлением. Результат эксперимента нужно явно пометить как полученный с отключённой проверкой и повторить обычным клиентом после исправления доверия.</p><h2>HTTP, proxy и кэш: ответ не всегда пришёл от origin</h2><p>HTTP-маршрут выбирается с учётом цели запроса. В HTTP/1.1 это видно через <code>Host</code>, а в HTTP/2 и HTTP/3 — через <code>:authority</code>. Один адрес может обслуживать несколько имён, поэтому несоответствие authority, пути или метода способно привести к <code>404</code> при полностью рабочем TLS. Уточняйте, какой компонент принимал соединение и какое значение он использовал для маршрутизации.</p><p>Посредник может завершить запрос сам, передать его дальше или вернуть результат из кэша. Поле <code>Via</code> по стандарту помогает увидеть промежуточные протоколы и получателей, но оно не является криптографической подписью тела. <code>Age</code> и валидаторы вроде <code>ETag</code> помогают заметить кэширование, а <code>Cache-Control</code> описывает правила хранения и повторного использования. Это признаки цепочки, а не самостоятельное доказательство конкретного origin.</p><p>Для расследования сохраняйте метод, нормализованный путь, authority, порт, момент, длительность, статус, размер тела и безопасный request id. Секреты и персональные данные в такой записи не нужны: удалите <code>Authorization</code>, cookie и чувствительные query-параметры. Если доступен только внешний ответ, напишите «источник не установлен». Такая формулировка полезнее уверенного, но неподтверждённого назначения владельца.</p><h2>Воспроизводимый пример: сначала HTTP, затем отрицательный путь</h2><p>Пример запускается локально и намеренно не моделирует TLS. Он показывает, что статус относится к HTTP-маршруту, а вручную заданный request id не подтверждает доверенное происхождение. Сохраните код в <code>check-http.mjs</code> и запустите на Node.js с поддержкой встроенного <code>fetch</code>.</p><pre><code>import { createServer } from 'node:http';\n\nconst server = createServer((request, response) => {\n const knownRoute = request.method === 'GET' && request.url === '/health';\n response.writeHead(knownRoute ? 200 : 404, {\n 'content-type': 'application/json; charset=utf-8',\n 'x-request-id': 'local-001',\n });\n response.end(JSON.stringify({\n method: request.method,\n path: request.url,\n route: knownRoute ? 'health' : 'missing',\n }));\n});\n\nserver.listen(0, '127.0.0.1', async () => {\n const { port } = server.address();\n for (const path of ['/health', '/missing']) {\n const response = await fetch('http://127.0.0.1:' + port + path);\n console.log(response.status, await response.json());\n }\n server.close();\n});</code></pre><p>Ожидаемый результат — <code>200</code> для <code>GET /health</code> и <code>404</code> для <code>GET /missing</code>. В обоих ответах будет <code>local-001</code>, потому что пример присваивает его сервером. Это воспроизводит локальный HTTP-контракт, но не доказывает, что аналогичное поле в production создано доверенным входным компонентом.</p><p>Отрицательный тест: замените путь на <code>/health?token=demo</code>. Сервер вернёт <code>404</code>, потому что демонстрационный контракт сравнивает полный URL. Это не универсальное правило маршрутизации, а специально видимое условие примера. После теста не переносите его буквально в приложение: реальный роутер может отдельно разбирать path и query. Ценность эксперимента в том, что изменение одного условия меняет наблюдаемый результат.</p><h2>Порядок диагностики</h2><ol><li>Запишите схему, hostname, порт, метод, безопасный путь, время и режим proxy. Замаскируйте токены, cookie и персональные параметры.</li><li>Проверьте DNS и сохраните выбранный адрес, тип записи и момент. Если адресов несколько, повторите наблюдение для каждого значимого маршрута.</li><li>Проверьте TCP connect к нужному порту. При отказе сначала разбирайте сеть, listener, firewall или балансировщик.</li><li>Для HTTPS обычным клиентом проверьте hostname или IP identity, SAN, цепочку доверия и срок действия. Запишите конкретное свойство, которое не прошло.</li><li>Только после успешного TLS снимите метод, path, authority, статус и ограниченный набор заголовков. Ошибке до TLS не приписывайте HTTP-статус.</li><li>Установите отвечающий слой: свяжите request id с доверенным логом proxy, gateway или handler. Если связи нет, оставьте источник ответа неизвестным.</li><li>Проверьте <code>Via</code>, <code>Age</code>, <code>ETag</code>, <code>Cache-Control</code> и длительность, если подозреваете intermediary или cache. Сравните холодный и повторный запрос.</li><li>Сформулируйте одну подтверждённую причину и один следующий тест. Для <code>POST</code> сначала проверьте идемпотентность или ключ дедупликации; ответ <code>503</code> сам по себе не делает повтор безопасным.</li></ol><h2>Ограничения применимости</h2><p>Модель описывает порядок доказательств, а не конкретную реализацию сети. Она не заменяет packet capture, распределённую трассировку, настройки CDN, корпоративного proxy, DNS-кэш, региональную балансировку или наблюдение внутри закрытого сегмента. У клиента могут быть скрыты отдельные фазы, поэтому итоговый timeout нельзя разложить на DNS, TCP и TLS без дополнительного измерения.</p><p>Локальный сервер не подтверждает работу реального origin, сертификата, балансировщика или кэша. Поле <code>Via</code> может быть скрыто политикой, а request id от внешней стороны может отсутствовать или быть перезаписан. Сопоставляйте только те записи, которым доверяете в своей цепочке. Нельзя переносить учебный порт, hostname, заголовок и ожидаемый статус в production без отдельного контракта.</p><p>Для операции, меняющей заказ, деньги, подписку или иной ресурс, сетевой сбой оставляет результат неопределённым: сервер мог применить действие и потерять ответ. RFC различает идемпотентные методы и остальные, но бизнес-операция может иметь дополнительные побочные эффекты. Без ключа операции, проверки состояния или явного правила сервиса безопаснее остановить автоматический повтор и передать решение владельцу контракта.</p><h2>Критерий готовности</h2><p>Диагностика готова, если для одного endpoint есть две очищенные записи: успешная и ошибочная. В каждой указаны время, hostname, порт, метод, безопасный путь, этап, статус или текст ошибки, длительность и ожидаемый следующий тест. Для HTTPS отдельно зафиксированы identity, цепочка и срок; для HTTP — authority, маршрут, источник ответа и признаки кэша, если они доступны.</p><p>Исправление считается подтверждённым после изменения одного условия и повторного запуска с обычной проверкой сертификата. Ожидаемый результат должен быть сформулирован заранее: например, известный маршрут возвращает <code>200</code>, отсутствующий — <code>404</code>, а ошибка до TLS не получает выдуманный HTTP-статус. Если логи не связывают ответ с доверенным компонентом, честный результат проверки — «источник не установлен», а не имя предполагаемого сервиса.</p><h2>Проверяемые источники</h2><ul><li><a href=\"https://www.rfc-editor.org/rfc/rfc9110.html\" target=\"_blank\" rel=\"noopener noreferrer\">IETF RFC 9110: HTTP Semantics</a> — схема <code>https</code>, проверка identity, <code>Host</code> и <code>:authority</code>, посредники, кэш, статусы и свойства методов.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc8446.html\" target=\"_blank\" rel=\"noopener noreferrer\">IETF RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3</a> — формат TLS 1.3, сообщения handshake, сертификаты и ограничения отключённой аутентификации.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc6125.html\" target=\"_blank\" rel=\"noopener noreferrer\">IETF RFC 6125: Representation and Verification of Domain-Based Application Service Identity</a> — правила сопоставления имени сервиса с идентичностью сертификата.</li></ul>"}
|