Как мы столкнулись с неожиданной ошибкой в .
NET 5, исследовали проблему и что из этого вышло.
В один прекрасный день было принято решение о переносе рабочего проекта с .
NET Core 3.1 на .
NET 5. Миграция оказалась проще, чем, например, при переходе с .
NET Core 2.1 на .
NET Core 3 из-за меньшего количества модификации.
По сути, речь шла всего лишь о смене TargetFramework на net5.0, обновлении нескольких библиотек и исправлении пары мест в коде, которые стали устаревшими, чтобы это не было так больно делать в будущем.
Команда тестирования проверила обновление вместе с другими задачами, а затем релиз был запущен в производство.
Но счастье было недолгим.
Судя по метрикам, вдруг выяснилось, что одна из интеграций отвалилась.
Логи показали, что несколько минут после выкатки релиза все работало отлично, но затем HTTPS-запросы к шине одного из клиентов почему-то стали проваливаться с ошибкой авторизации (401).
Мы выкатили релиз еще раз, но это не помогло — проблема повторилась.
Поскольку быстро понять, что именно пошло не так, не удалось, пришлось откатиться обратно на .
NET Core 3.1, а затем начать разбираться с проблемой в фоновом режиме, чтобы потом перейти на .
NET 5.
Пусть расследование начнется
Во-первых, стоит воспроизвести проблему изолированно, чтобы исключить влияние других факторов.Для этого коллега написал консольное приложение, которое в цикле создает HTTP-обработчик с боевым сертификатом в поле Сертификаты, клиент на основе этого обработчика, а затем стучится в API и выполняет одну из безобидных идемпотентных операций.
Все запросы были выполнены успешно, что довольно странно.
Возможно, что-то изменилось на стороне сервера, куда отправляются запросы? На всякий случай аналогичные манипуляции я провожу с помощью Curl, консольных приложений на Python и Go. Но в данном случае проблема не воспроизводится.
Что еще может отличаться при производстве и при локальном воспроизведении? Разработка ведется на Windows, а производство — на Linux (опущу вопрос, почему при разработке не используется хотя бы Docker).
Ладно, попробую сделать то же самое, но через Docker, и вуаля, первый запрос проходит успешно, а все остальные завершаются с ошибкой.
Но на .
NET Core 3.1 все работало, может еще что-то поменяли? Я запускаю то же консольное приложение, но меняю TargetFramework на netcoreapp3.1, и все работает как в Windows, так и в Linux. Возникает вопрос, почему в продакшене ошибки начинаются не с первого запроса, а только через несколько минут? Ответ прост: используется HttpClientFactory , который кэширует HttpClientHandler для клиентов на некоторое время и повторно использует его для запросов, а когда приходит время создавать новый обработчик, вот тут всё и ломается.
Отлично.
Поскольку запросы, отправленные с использованием второго и последующих клиентов, терпят неудачу, означает ли это, что запросы от первого клиента всегда будут успешными? При тестировании в консольном приложении кажется, что это так и можно сделать клиент статической переменной и не волноваться.
Но причина ошибки пока остаётся неизвестной, и искать её в продакшене не очень хочется.
Что, если мы немного проверим это более сложный сценарий ? Для этого в консольном приложении я создам два обработчика и два соответствующих клиента, и запросы будут выполняться один за другим: первые два запроса будут отправлены с помощью первого клиента, вторые два - с помощью второго и последний - снова используя первый.
Схема выполнения запроса в несколько более сложном сценарии Это создаст странную картину: все запросы, сделанные с использованием второго клиента, будут отправляться с неправильной аутентификацией, но в остальном все будет в порядке.
Примечательно, что если вы начнете отправлять запросы с помощью второго клиента по той же схеме, то запросы первого будут некорректными.
Кстати, на последней macOS все пять запросов заканчиваются ошибкой.
Похоже на проблему с повреждением состояния программы, возможно, связанную с кэшированием.
Почему проблема не была обнаружена во время тестирования релиза? Все просто: в тестовых средах вместо аутентификации по сертификату использовалась аутентификация по логину и паролю.
Сразу после обнаружения ошибки мы сделали аутентификацию на тестовой и производственной средах одинаковой — с использованием сертификата.
В результате получаем странную ситуацию: вроде бы ошибка связана с новой версией .
NET, но уже вышли два минорных релиза (на момент возникновения проблемы актуальной была версия 5.0.2), и исправления до сих пор нет. Если бы ошибка произошла при выполнении какого-либо запроса с аутентификацией по сертификату, то, вероятно, проблема уже была бы замечена и устранена.
Это значит, что нам необходимо понять, почему используемая в проекте конфигурация такая особенная и в чем именно заключается ошибка.
Ищем ошибку
Внешняя отладка
Для повседневной разработки я использую Jetbrains Rider. Имеет поддержку так называемой внешней отладки (External source debug).В этом режиме отладчик может покопаться в недрах .
NET, что позволит более детально изучить, что происходит при выполнении проблемного HTTP-запроса, а затем попытаться найти ошибку самостоятельно.
Поскольку ошибку невозможно воспроизвести в Windows, отладку необходимо проводить в среде Linux, чего можно добиться двумя способами:
- Установите Linux на свой рабочий компьютер;
- Воспользуйтесь преимуществом ВСЛ 2 или Докер.
В результате отладки обнаружены потенциальные жертвы: классы безопасный канал И SslStreamCertificateContext .
Последний имеет разные реализации для разных операционных систем, что достигается с помощью условной компиляции соответствующих частичных классов: в Windows используется нативный модуль для работы с сертификатами, а в Linux — OpenSSL. После выполнения первого запроса сеанс SSL кэшируется в SslSessionsCache. При проблемном запросе сессия повторно используется из кеша, и по какой-то причине отправляется меньший массив данных, чем при обычном запросе.
Если вы используете отражение для очистки кэша сеанса SSL перед проблемным вызовом, HTTP-запрос будет успешно завершен:
Теги: #Сетевые технологии #github #программирование #Go #C++ #.var assembly = AppDomain.CurrentDomain.GetAssemblies()
NET #Visual Studio #отладка #SSL #миграция проекта #.
net 5.0 #сертификаты x.509 #bugfix
-
Программа Двенадцать Шагов
19 Oct, 24 -
Ансамбль Слоистых Медиа
19 Oct, 24 -
Считаем На Пальцах
19 Oct, 24 -
Web Of Trust (Wot) – Безопасный Интернет
19 Oct, 24