Проверка на SSL сертификати, използваща минимални API на ASP.NET

Често ми се налага бързо да извличам подробности за SSL сертификат за сценарии за наблюдение и автоматизация, така че реших да опитам да напиша прост API, за да помогна.

API може да се използва за програмен достъп до подробности за сертификат, като общо име или SAN, и също така да бъде интегриран с проверки на здравето на приложението, за да предоставя предупреждения, когато датата на изтичане наближава.

Извличане на сертификата

В света на .NET общоприетият начин за извличане на TLS сертификат е използването на HttpClient с персонализиран HttpClientHandler. Най-бързият и надежден начин за отправяне на заявка за иницииране на манипулатора е използването на HEAD заявка към уебсайт, който се хоства с помощта на сертификата:

Има няколко проблема с този код, които не го правят идеален за използване в мащаб.

Създаването на много HttpClients е лоша идея

Дори ако изхвърляте HttpClient обектите във вашия код, пулът от нишки поддържа връзките отворени, в случай че някой пакет се върне след изхвърлянето. Ако правите това във висок мащаб, тогава можете бързо да изчерпите наличните WebSockets. Има много по-добро обяснение на този проблем „тук“.

За да се заобиколи този проблем, беше въведен HttpClientFactory за управление на живота на вашите екземпляри на HttpClient.

Безопасност на резбата

Ако трябваше да поправим горния код, като използваме споделен HttpClient екземпляр, тогава ще се натъкнем на проблеми с безопасността на нишката, тъй като един и същ HttpClientHandler екземпляр ще свикне да проверява сертификата при различни заявки.

Ако два различни домейна бяха проверени едновременно, тогава не бихме могли да сме сигурни коя дата на изтичане се извлича за всеки сертификат.

Производителност и дроселиране

Като цяло сертификатите не се променят много често, така че не е необходимо да обработваме заявки за един и същ хост за кратко време. Може също така да бъдем ограничени или блокирани от сървъра надолу по веригата, ако изпълняваме твърде много заявки.

В следващия раздел ще обясня как кеширането може да се използва за решаване на двата последни два проблема.

Решението

Ще бъде създадено хранилище за сертификати, за да кешира сертификатите, след като бъдат извлечени от персонализиран манипулатор за извличане на сертификати. Манипулаторът за извличане на сертификати разширява HttpClientHandler и е конфигуриран чрез HttpClient, който изпраща HEAD заявки до хоста, който се проверява.

Заявките HEAD се изпращат чрез услуга за проверка на сертификати, която първо ще провери хранилището на сертификати, за да види дали вече има наличен сертификат и ще извика сървъра надолу по веригата, ако няма.

Магазин за сертификати

Първият проблем, който исках да разреша, беше как сертификатите могат да бъдат съхранени след извличане и извлечени преди/след извършване на проверка. Един прост MemoryCache е бърз безопасен за нишки начин за задържане на сертификатите.

Хостът се извлича от requestUri, така че проверките на сертификати да могат да се използват повторно, ако преди това е бил използван различен път за същия хост.

Манипулатор за извличане на сертификати

Създава се персонализирана реализация HttpClientHandler за капсулиране на извличането на X509Certificate след заявка и запазването му в хранилището на сертификати.

TLS сертификат

Исках да извлека някои от подробностите, които ме интересуваха от X509Certificate. Реших да създам клас TlsCertificate, за да извлека следното:

  • Издател
  • Предмет
  • Общо име (Това може да бъде извлечено от Темата)
  • Алтернативно име на субект/SAN (Това може да бъде извлечено от разширенията на сертификата)

Кодът за извличане на тези подробности може да бъде намерен тук.

Услуга за проверка на сертификати

Последната част от пъзела беше услуга за оркестриране на извличането на извлечените сертификати от хранилището на сертификати и изпращане на HTTP HEAD заявките до хоста, който се проверява.

Услугата проверява дали сертификат вече е бил извлечен наскоро за хоста, който се проверява. Ако вече няма наличен сертификат, тогава услугата изпраща HEAD заявка до хоста и извлича извлечения сертификат.

Сглобяване на всичко

Сега, когато компонентите за проверка на сертификатите са създадени, е време да създадете някои крайни точки на API отгоре.

POST: /проверете крайната точка

Тази крайна точка винаги ще връща отговор 200, дори ако сертификатът не е валиден или е изтекъл.

GET: /валидиране на крайна точка

Тази крайна точка използва низ на заявка, така че да може да бъде извикана с GET заявка вместо това. Крайната точка ще върне 502 Bad Gateway, ако сертификатът е невалиден или изтекъл. Тъй като крайната точка използва проста GET заявка, тя може лесно да бъде включена в проверка на здравето, за да наблюдава сертификат.

Тъй като използвам минимални API, трябва да се създаде персонализиран IResult, който да обработва връщането на резултат за Bad Gateway. Създадох прост клас Check Result, който обработва сериализирането на резултата и задаването на HTTP Status Code в зависимост от валидността на сертификата.

Заключителни думи

Крайният резултат е REST API, който извлича информацията за сертификата и валидира изтичането и имената на домейни.

Надявам се да намерите това малко приложение за полезно. Ако искате да проверите пълния код, той може да бъде намерен тук.