Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update AzureOpenAIConfig validation to cater for reverse proxy endpoint #875

Merged
merged 8 commits into from
Nov 19, 2024
7 changes: 7 additions & 0 deletions extensions/AzureOpenAI/AzureOpenAI/AzureOpenAIConfig.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using Azure.Core;

#pragma warning disable IDE0130 // reduce number of "using" statements
Expand Down Expand Up @@ -83,6 +84,12 @@ public enum APITypes
/// </summary>
public int MaxRetries { get; set; } = 10;

/// <summary>
/// Thumbprints of certificates that should be trusted for HTTPS requests when SSL policy errors are detected.
/// This should only be used for local development when using a proxy to call the OpenAI endpoints.
/// </summary>
public HashSet<string> TrustedCertificateThumbprints { get; set; } = [];

/// <summary>
/// Set credentials manually from code
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Linq;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -32,6 +35,13 @@ internal static AzureOpenAIClient Build(
// See https://github.com/Azure/azure-sdk-for-net/issues/46109
options.AddPolicy(new SingleAuthorizationHeaderPolicy(), PipelinePosition.PerTry);

if (httpClient is null && config.TrustedCertificateThumbprints.Count > 0)
{
#pragma warning disable CA2000 // False Positive: https://github.com/dotnet/roslyn-analyzers/issues/4636
httpClient = BuildHttpClientWithCustomCertificateValidation(config);
#pragma warning restore CA2000
}

if (httpClient is not null)
{
options.Transport = new HttpClientPipelineTransport(httpClient);
Expand All @@ -57,6 +67,47 @@ internal static AzureOpenAIClient Build(
throw new ConfigurationException($"Azure OpenAI: authentication type '{config.Auth:G}' is not supported");
}
}

private static HttpClient BuildHttpClientWithCustomCertificateValidation(AzureOpenAIConfig config)
{
#pragma warning disable CA2000 // False Positive: https://github.com/dotnet/roslyn-analyzers/issues/4636
var handler = new HttpClientHandler();
#pragma warning restore CA2000

handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback =
(_, cert, _, policyErrors) =>
{
// Pass if there are no policy errors.
if (policyErrors == SslPolicyErrors.None) { return true; }

// Attempt to get the thumbprint of the remote certificate.
string? remoteCert;
try
{
remoteCert = cert?.GetCertHashString();
}
catch (CryptographicException)
{
// Fail if crypto lib is not working
return false;
}
catch (ArgumentException)
{
// Fail if thumbprint format is invalid
return false;
}

// Fail if no thumbprint available
if (remoteCert == null) { return false; }

// Success if the remote cert thumbprint matches any of the trusted ones.
return config.TrustedCertificateThumbprints.Any(
trustedCert => string.Equals(remoteCert, trustedCert, StringComparison.OrdinalIgnoreCase));
};

return new HttpClient(handler);
}
}

// Use only for local debugging - Usage:
Expand Down
10 changes: 8 additions & 2 deletions service/Service/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,10 @@
// See https://learn.microsoft.com/azure/ai-services/openai/reference#embeddings
"MaxEmbeddingBatchSize": 1,
// How many times to retry in case of throttling.
"MaxRetries": 10
"MaxRetries": 10,
// Thumbprints of certificates that should be trusted for HTTPS requests when SSL policy errors are detected.
// This should only be used for local development when using a proxy to call the OpenAI endpoints.
"TrustedCertificateThumbprints": []
},
"AzureOpenAIText": {
// "ApiKey" or "AzureIdentity"
Expand All @@ -355,7 +358,10 @@
// "ChatCompletion" or "TextCompletion"
"APIType": "ChatCompletion",
// How many times to retry in case of throttling.
"MaxRetries": 10
"MaxRetries": 10,
// Thumbprints of certificates that should be trusted for HTTPS requests when SSL policy errors are detected.
// This should only be used for local development when using a proxy to call the OpenAI endpoints.
"TrustedCertificateThumbprints": []
},
"AzureQueues": {
// "ConnectionString" or "AzureIdentity". For other options see <AzureQueueConfig>.
Expand Down
Loading