Как добавить сертификат в httpclient IHttpClientFactory вне установки внедрения зависимостей в. net core 3.1? - PullRequest
0 голосов
/ 25 марта 2020

У меня есть такой сценарий: у меня есть Azure функция, которая вызывает некоторые API REST из другого сервиса. Для этой другой службы требуется сертификат.

Если я пытаюсь добавить сертификат во время внедрения зависимостей в Startup.cs, сертификат не найден. Кажется, что Startup.Configure запускается до загрузки всех сертификатов (?).

Итак, мне нужно иметь возможность загрузить сертификат в httpclient внутри самой функции azure. Но IHttpClientFactory, похоже, не имеет никаких механизмов для изменения клиента, созданного CreateClient, кроме некоторых заголовков. Как я могу добавить сертификат (который был бы сделан через HttpClientHandler.ClientCertificates.Add ()) в какой-то момент позже в стеке вызовов?

    // Startup.cs::Configure

    public override void Configure(IFunctionsHostBuilder builder)
    {
        builder.Services.AddLogging();

        // My cert is stored in keyvault, so I go get it from there for the thumbprint
        string certThumbprint = CertificateUtils.GetCertificateThumbprintFromKeyVault();

        // This call tries to find the certificate with that thumbprint in CertStore.My
        X509Certificate2 cert = CertificateUtils.GetCertificate(certThumbprint);
        if (cert == null)
        {
            throw new MyException("Unable to retrieve your certificate from key vault");
        }

        using HttpClientHandler myApiHandler = new HttpClientHandler();
        MyApiHandler.ClientCertificates.Add(cert);

        builder.Services.AddHttpClient<IMyAPI, MyAPI>("MyApi", client => { client.BaseAddress = new Uri("<baseurl>"); })
            .ConfigurePrimaryHttpMessageHandler(() => myApiHandler);
    }


    // CertificateUtils:

    public static X509Certificate2 GetCertificate(
        string certId,
        StoreLocation storeLocation = StoreLocation.LocalMachine,
        StoreName storeName = StoreName.My,
        X509FindType findType = X509FindType.FindByThumbprint)
    {
        X509Certificate2Collection set = GetCertificates(storeLocation, storeName, findType, certId, false);

        if (set.Count != 1 || set[0] == null)
        {
            string exceptionDetails = set.Count != 1 ? "with certificate count of " + set.Count : "element at position 0 is null";

            throw new ConfigException($"Failed to retrieve certificate {certId} from store {storeLocation}\\{storeName}, {exceptionDetails}");
        }

        return set[0];
    }

    private static X509Certificate2Collection GetCertificates(
        StoreLocation storeLocation,
        StoreName storeName,
        X509FindType findType,
        string certId,
        bool validOnly)
    {
        X509Store certStore = new X509Store(storeName, storeLocation);
        certStore.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);

        try
        {
            return certStore.Certificates.Find(findType, certId, validOnly);
        }
        finally
        {
            certStore.Close();
        }
    }
}

// MyAPI

public class MyAPI : MyAPI
{
    private readonly HttpClient HttpClient;

    private IHttpClientFactory HttpClientFactory;

    public MyAPI(IHttpClientFactory httpClientFactory)
    {
        this.HttpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));

        this.HttpClient = httpClientFactory.CreateClient("MyApi");
    }

    public Data DoSomething(string name)
    {
        Uri uri = new Uri($"{this.HttpClient.BaseAddress}/Some/REST/API?name={name}");
        HttpResponseMessage response = this.HttpClient.GetAsync(uri).Result;
        response.EnsureSuccessStatusCode();

        string body = response.Content.ReadAsStringAsync().Result;
        return JsonConvert.DeserializeObject<Data>(body);
    }
}



public class MyAzureFunc
{
    private readonly IMyAPI MyAPI;

    private readonly ILogger Log;

    public MyAzureFunc(ILogger<MyAzureFunc> log, IMyAPI myApi)
    {
        this.Log = log;
        this.MyAPI = myApi;
    }

    [FunctionName("MyAzureFunc")]
    public async Task<HttpResponseMessage> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)]
        HttpRequest req)
    {
        // ... standard boilerplate stuff to read the name=Rusty part from the GET call ...

        // Call the 3rd party service that requires the cert to get some data
        MyData data = this.MyAPI.DoSomething(name);

        // ... then do something with the data
        return new HttpResponseMessage(HttpStatusCode.OK);
    }
}

При запуске моей функции azure в локальной отладке, это немедленно терпит неудачу в GetCertificate. Я успешно получил отпечаток сертификата из моего keyvault, но набор сертификатов, найденный в моем CertStore. Мой неполный (он возвращает 4 сертификата, ни один из которых не находится в моем личном магазине?!?). Это то, что заставляет меня поверить, что Startup.Configure происходит до того, как сертификаты загружаются функцией azure.

То, что у меня было до попытки использовать рекомендуемый подход Microsoft.Extensions.Http + DI + Polly было так, как показано ниже, в самой функции MyAPI.DoSomething:

        string certThumbprint = CertificateUtils.GetCertificateThumbprintFromKeyVault();
        X509Certificate2 cert = CertificateUtils.GetCertificate(certThumbprint, storeLocation: StoreLocation.CurrentUser);

        using HttpClientHandler handler = new HttpClientHandler();
        handler.ClientCertificates.Add(cert);

        using HttpClient client = new HttpClient(handler);
        response = await client.GetAsync(uri);
        response.EnsureSuccessStatusCode();

Это работало нормально.

Итак, я думаю, что сертификаты были, по-видимому, недоступны, когда Был вызван файл Startup.cs, для которого мне нужно добавить сертификат в тот момент, когда я инициализирую HttpClient через

this.HttpClient = httpClientFactory.CreateClient("MyApi");
// Get my certificate here and add it to the client via HttpClientHandler or such

Или, если это не тот случай, когда сертификаты не загружаются после запуска .Настроить, почему не все мои сертификаты в моем местном магазине были подобраны, и как мне go заставить это работать должным образом?

1 Ответ

0 голосов
/ 25 марта 2020

Этот пост решил мою проблему.

Как использовать универсальный ConfigurePrimaryHttpMessageHandler c

Что касается локальных сертификатов, я вызывал свой собственный API GetCertificate с неправильным StoreLocation.

...