Files
progcode/editorial/agent-rewrites/023.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

2 lines
19 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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) =&gt; {\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 () =&gt; {\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>"}