2 lines
19 KiB
JSON
2 lines
19 KiB
JSON
{"index":23,"slug":"editorial-2027-05-mechanism-http-tls-guide","title":"Где ломается HTTPS: проверяем запрос по границам DNS, TCP, TLS и HTTP","excerpt":"Браузер показывает один итог, но ошибка возникает на конкретной границе. Разбираем порядок проверок, безопасную запись запроса и признаки, по которым можно отделить TLS, посредника и приложение.","contentHtml":"<p>Браузер сообщает: «не удалось подключиться». Сервисный клиент пишет <code>certificate verify failed</code>. В третьем месте тот же адрес возвращает <code>404</code>. Команда видит одно слово — «ошибка» — и меняет маршрут или отключает проверку сертификата. Цена такого решения — потерянное время, неверный владелец исправления и иногда открытое соединение без проверки имени сервера.</p><p>Тезис простой: диагностируйте запрос по границам. DNS отвечает за имя и адрес. TCP отвечает за соединение. TLS устанавливает защищённый канал и проверяет сертификат. HTTP передаёт метод, путь и заголовки. Приложение обрабатывает контракт endpoint. Пока предыдущая граница не подтверждена, следующая не даёт фактов.</p><h2>Механизм: пять границ одного запроса</h2><p>Клиент начинает с имени из URL. Resolver возвращает адрес. TCP открывает поток к порту. Для HTTPS клиент и сервер проводят TLS handshake, выбирают параметры и проверяют цепочку доверия и имя. Только после этого клиент отправляет HTTP-запрос. Сервер или intermediary возвращает статус, заголовки и тело.</p><p>Порядок важен. Если TLS завершился исключением, у приложения нет HTTP-статуса, который можно расследовать. Если TLS завершился успешно, но ответ равен <code>404</code>, сертификат уже не объясняет отсутствие маршрута. Если посредник вернул <code>503</code>, этот статус может описывать его собственное состояние, а не состояние origin.</p><div class=\"table-scroll\"><table><caption>Граница запроса и допустимое утверждение</caption><thead><tr><th scope=\"col\">Граница</th><th scope=\"col\">Что можно утверждать</th><th scope=\"col\">Что ещё не доказано</th></tr></thead><tbody><tr><td>DNS</td><td>Имя разрешилось в выбранный адрес</td><td>Порт принимает соединения, сертификат подходит имени</td></tr><tr><td>TCP</td><td>Соединение с адресом и портом установлено</td><td>TLS доверен, HTTP-маршрут существует</td></tr><tr><td>TLS</td><td>Защищённый канал принят клиентом</td><td>Запрос дошёл до нужного origin</td></tr><tr><td>HTTP</td><td>Получены статус и заголовки</td><td>Ответ сформировало ваше приложение</td></tr><tr><td>Приложение</td><td>Лог доверенного входа связывает запрос с handler</td><td>Другой регион или кэш ведёт себя так же</td></tr></tbody></table></div><h2>Симптом → причина → проверка → действие</h2><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>Нет HTTP-статуса, клиент сообщает о сертификате</td><td>Имя, срок или цепочка сертификата не прошли проверку</td><td>Сверить hostname, SAN, срок и локальное хранилище доверия</td><td>Исправить сертификат или доверенную цепочку; не оставлять отключённую проверку</td></tr><tr><td>Имя разрешается, connect завершается отказом</td><td>Порт закрыт, маршрут недоступен или адрес выбран неверно</td><td>Сравнить адрес DNS и время TCP connect</td><td>Проверить firewall, listener, балансировщик и выбранный адрес</td></tr><tr><td>TLS успешен, пришёл <code>404</code></td><td>Путь, метод или виртуальный хост не совпал с маршрутом</td><td>Сопоставить URL, метод, authority и лог входного proxy</td><td>Исправить маршрут или контракт; не менять сертификат</td></tr><tr><td>Приходит <code>503</code> от proxy</td><td>Посредник не получил рабочий upstream или отказал по лимиту</td><td>Проверить <code>Via</code>, время ответа и логи upstream</td><td>Разделить состояние proxy и origin, затем проверить соединения и лимиты</td></tr><tr><td>Заголовок <code>Server</code> указывает на знакомый продукт</td><td>Поле добавил intermediary или его можно переписать</td><td>Сопоставить request id с доверенным логом входа</td><td>Считать заголовок гипотезой, а не доказательством источника</td></tr><tr><td>Повторный запрос даёт другой статус</td><td>Кэш, балансировка, редирект или меняющееся состояние</td><td>Сравнить <code>Age</code>, <code>Cache-Control</code>, <code>ETag</code>, адрес и время</td><td>Проверить маршрут каждого ответа и не объединять их в один результат</td></tr></tbody></table><h2>Почему заголовки не подтверждают источник</h2><p><code>Server</code>, <code>Via</code> и <code>X-Request-Id</code> принадлежат HTTP-сообщению. Посредник может добавить, удалить или переписать их. Даже правильный на вид идентификатор не доказывает, что запрос обработал конкретный handler. Доказательство появляется только там, где доверенный компонент создал идентификатор и записал его вместе с маршрутом, временем и результатом.</p><p>Сохраняйте в диагностике логическое имя назначения, а не только строку <code>Host</code>. В HTTP/2 и HTTP/3 используется поле authority, и привычная проверка одного заголовка может дать неполную картину. Если есть proxy, отдельно фиксируйте имя proxy и имя origin. Сертификат проверяет имя, которое использовал TLS-клиент; это не всегда имя, которое позже увидел application handler.</p><p>Минимальная безопасная запись содержит метод, нормализованный путь, этап отказа, статус, длительность, размер тела и request id. Уберите <code>Authorization</code>, cookie и секретные query-параметры. Путь должен быть полезен для маршрутизации, но не обязан содержать персональные или платёжные данные.</p><h2>Учебный пример: отделяем TLS от HTTP</h2><p>Ниже — локальный HTTP-сервер. Он показывает только границу HTTP: сервер принимает запрос, возвращает статус и идентификатор, клиент читает тело. Сеть, TLS, proxy и production-маршрутизация в пример не входят. Число <code>local-001</code> не является доказательством доверенного источника.</p><pre><code>import { createServer } from 'node:http';\n\nconst server = createServer((request, response) => {\n response.writeHead(200, {\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 }));\n});\n\nserver.listen(0, 'localhost', async () => {\n const { port } = server.address();\n const response = await fetch(`http://localhost:${port}/orders`);\n console.log(response.status);\n console.log(response.headers.get('x-request-id'));\n console.log(await response.json());\n server.close();\n});</code></pre><p>Ожидаемый учебный результат — статус <code>200</code>, идентификатор <code>local-001</code> и тело с методом <code>GET</code> и путём <code>/orders</code>. Если убрать <code>x-request-id</code>, HTTP всё равно останется корректным. Это показывает границу поля: идентификатор помогает сопоставлять записи, но не является условием успешного запроса.</p><p>Отрицательный путь выглядит иначе. Если заменить URL на HTTPS и получить ошибку проверки сертификата до строки со статусом, код сервера не объясняет отказ. Если HTTPS проходит, а сервер возвращает <code>404</code>, нужно проверять метод, путь и authority. Флаг вроде <code>--insecure</code> может показать, что удалённая сторона отвечает, но он отключает важную проверку и не исправляет конфигурацию.</p><h2>TLS меняет порядок диагностики</h2><p>Для HTTPS проверяйте не «SSL вообще», а три независимых условия. Первое — сертификат выдан для целевого hostname: имя должно совпасть с одним из значений Subject Alternative Name. Второе — цепочка ведёт к центру сертификации, которому доверяет клиент. Третье — текущая дата попадает в срок действия сертификата. Неправильный SAN, неизвестный issuer и истёкший срок требуют разных исправлений.</p><p>Подключение к IP вместо имени часто ломает проверку имени, даже если IP ведёт к нужному серверу. Заголовок <code>Host</code> не исправляет это задним числом: TLS завершается раньше, чем клиент отправляет HTTP-заголовки. Через proxy добавляется ещё одна граница. Имя proxy и имя origin нужно проверять отдельно, иначе ошибку промежуточного соединения можно принять за ошибку конечного сервиса.</p><p>Кэш также меняет смысл ответа. <code>Age</code> может показать возраст объекта, <code>Cache-Control</code> — правила хранения, <code>ETag</code> — валидатор представления, а <code>Via</code> — участие intermediary. Ни одно поле само по себе не доказывает, кто создал тело. Сопоставляйте заголовки с логом доверенного входа и, если возможно, с ответом origin.</p><figure><img src=\"/assets/editorial/2027/http-tls-guide-2027-symptom-boundary-matrix.svg\" alt=\"Матрица границ запроса: DNS, TCP, TLS, HTTP и приложение с отдельным вопросом для каждой проверки.\" loading=\"lazy\" /><figcaption>Каждая граница отвечает только на свой вопрос. HTTP-статус не подтверждает сертификат, а DNS-ответ не подтверждает маршрут приложения.</figcaption></figure><h2>Порядок проверки</h2><ol><li>Запишите URL, метод, безопасное имя назначения и момент запроса. Уберите токены, cookie и секретные query-параметры.</li><li>Проверьте DNS: зафиксируйте выбранный A/AAAA-адрес и не делайте из этого вывода о доступности порта.</li><li>Проверьте TCP connect и порт. При отказе остановитесь на сети, listener или балансировщике.</li><li>Для HTTPS сверите hostname, SAN, срок действия и цепочку доверия обычным клиентом. Не используйте отключение проверки как исправление.</li><li>После успешного TLS снимите статус, метод, путь, authority и ограниченный набор заголовков. Для ошибки до этого шага HTTP-поля не используйте.</li><li>Сопоставьте request id с логом доверенного proxy или входного сервиса. Заголовок от удалённой стороны без такой записи оставьте гипотезой.</li><li>Проверьте кэш и посредников по <code>Age</code>, <code>Cache-Control</code>, <code>ETag</code>, <code>Via</code> и времени ответа. Сравните cold и повторный запрос.</li><li>Запишите одну подтверждённую причину и один следующий тест. Если граница не наблюдается, укажите «не доказано», а не назначайте виновника по косвенному полю.</li></ol><h2>Ограничения</h2><p>Эта модель не заменяет трассировку сети. Она не показывает потери пакетов, особенности HTTP/2 multiplexing, работу CDN, разницу между регионами, настройки корпоративного proxy или состояние локального DNS-кэша. Локальный сервер не доказывает поведение реального origin. Учебные значения статуса, порта и идентификатора нельзя переносить в конфигурацию без проверки вашей среды.</p><p>Некоторые клиенты скрывают отдельные фазы и возвращают только итоговую длительность. Не восстанавливайте DNS, TCP и TLS по догадке. Запишите известный факт: например, «клиент прекратил ожидание через 800 мс» или «TLS завершился с ошибкой имени». Для детализации нужен клиент с подходящей диагностикой или наблюдение на доверенном proxy.</p><p>Не отключайте проверку сертификата в постоянной конфигурации и не публикуйте полный verbose-вывод. Не принимайте внешний request id как надёжную связь с серверным логом. Не объявляйте origin виновником ответа, который мог создать кэш или proxy. Эти отрицательные правила защищают диагностику от ложной уверенности.</p><h2>Проверяемый критерий готовности</h2><p>Проверка готова, когда для выбранного endpoint есть две безопасные записи: успешная и ошибочная. В каждой видны имя назначения, этап, статус или текст ошибки, длительность и ожидаемый следующий шаг. Интеграционный тест или разрешённое наблюдение должны показать, что ошибка до TLS не получает выдуманный HTTP-статус, а ответ <code>404</code> после TLS ведёт к проверке маршрута.</p><p>Для запроса через proxy дополнительно видны границы proxy и origin, а request id находится в логе доверенного входа. Если ответ может прийти из кэша, запись содержит признаки его участия или честно отмечает, что источник не установлен. Критерий выполнен только тогда, когда команда может повторить проверку и получить тот же вывод о границе отказа.</p><h2>Проверяемые источники</h2><ul><li><a href=\"https://www.rfc-editor.org/rfc/rfc9110.html\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 9110: HTTP Semantics</a> — официальная спецификация семантики HTTP, статусов, полей и посредников.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc8446.html\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3</a> — официальная спецификация TLS 1.3 и его handshake.</li></ul>"}
|