diff --git a/tools/sdk-ai-bots/AzureSdkQaBot/.editorconfig b/tools/sdk-ai-bots/AzureSdkQaBot/.editorconfig index 07973a0fa89..39908a37706 100644 --- a/tools/sdk-ai-bots/AzureSdkQaBot/.editorconfig +++ b/tools/sdk-ai-bots/AzureSdkQaBot/.editorconfig @@ -338,3 +338,6 @@ csharp_style_prefer_primary_constructors = true:suggestion # CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. dotnet_diagnostic.CS8618.severity = none + +# CS8604: Possible null reference argument. +dotnet_diagnostic.CS8604.severity = none diff --git a/tools/sdk-ai-bots/AzureSdkQaBot/AzureSdkQaBot.csproj b/tools/sdk-ai-bots/AzureSdkQaBot/AzureSdkQaBot.csproj index 72994083af8..168e63ad3d5 100644 --- a/tools/sdk-ai-bots/AzureSdkQaBot/AzureSdkQaBot.csproj +++ b/tools/sdk-ai-bots/AzureSdkQaBot/AzureSdkQaBot.csproj @@ -13,7 +13,9 @@ + + diff --git a/tools/sdk-ai-bots/AzureSdkQaBot/Config.cs b/tools/sdk-ai-bots/AzureSdkQaBot/Config.cs index 07523dd9982..7d2ab0f581f 100644 --- a/tools/sdk-ai-bots/AzureSdkQaBot/Config.cs +++ b/tools/sdk-ai-bots/AzureSdkQaBot/Config.cs @@ -5,6 +5,8 @@ public class ConfigOptions public string? BOT_ID { get; set; } public string? BOT_PASSWORD { get; set; } public string? GITHUB_TOKEN { get; set; } + public string? KeyVaultUrl { get; set; } + public string? CertificateName { get; set; } public OpenAIConfigOptions? OpenAI { get; set; } public AzureConfigOptions? Azure { get; set; } public CognitiveSearchOptions? Search { get; set; } diff --git a/tools/sdk-ai-bots/AzureSdkQaBot/Program.cs b/tools/sdk-ai-bots/AzureSdkQaBot/Program.cs index 95e32fb65b5..ba8a38c8dd3 100644 --- a/tools/sdk-ai-bots/AzureSdkQaBot/Program.cs +++ b/tools/sdk-ai-bots/AzureSdkQaBot/Program.cs @@ -12,6 +12,8 @@ using Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch; using AzureSdkQaBot.Model; using Octokit; +using Azure.Identity; +using Azure.Security.KeyVault.Certificates; var builder = WebApplication.CreateBuilder(args); @@ -21,9 +23,44 @@ // Prepare Configuration for ConfigurationBotFrameworkAuthentication var config = builder.Configuration.Get()!; -builder.Configuration["MicrosoftAppType"] = "MultiTenant"; -builder.Configuration["MicrosoftAppId"] = config.BOT_ID; -builder.Configuration["MicrosoftAppPassword"] = config.BOT_PASSWORD; + + +// Access key vault +if (string.IsNullOrEmpty(config.KeyVaultUrl)) +{ + throw new Exception("KeyVaultUrl is not set in the configuration."); +} + +System.Security.Cryptography.X509Certificates.X509Certificate2? certificate = null; +try +{ + CertificateClient client = new(vaultUri: new Uri(config.KeyVaultUrl), credential: new DefaultAzureCredential()); + if (client == null) + { + throw new Exception($"Failed to create KeyVault client for {config.KeyVaultUrl}"); + } + + + //Get certificate in X509Certificate format + if (string.IsNullOrEmpty(config.CertificateName)) + { + throw new Exception("CertificateName is not set in the configuration."); + } + string certificateName = config.CertificateName; + certificate = client.DownloadCertificate(certificateName).Value; + + if (certificate == null) + { + throw new Exception($"Certificate {certificateName} not found in KeyVault {config.KeyVaultUrl}"); + } +} +catch (Exception ex) +{ + throw new Exception($"Failed to get certificate {config.CertificateName} from KeyVault {config.KeyVaultUrl}", ex); +} + +// Create the ClientCredentialsFactory to user certificate authentication +builder.Services.AddSingleton((e) => new CertificateServiceClientCredentialsFactory(certificate, config.BOT_ID, "72f988bf-86f1-41af-91ab-2d7cd011db47")); // Create the Bot Framework Authentication to be used with the Bot Adapter. builder.Services.AddSingleton(); @@ -86,7 +123,7 @@ IPlanner planner = new AzureOpenAIPlanner(sp.GetService(), loggerFactory.CreateLogger>()); //IModerator moderator = new AzureContentSafetyModerator(sp.GetService(), loggerFactory.CreateLogger>()); - ApplicationOptions applicationOptions = new ApplicationOptions() + ApplicationOptions applicationOptions = new() { AI = new AIOptions(planner, promptManager) { diff --git a/tools/sdk-ai-bots/AzureSdkQaBot/appsettings.json b/tools/sdk-ai-bots/AzureSdkQaBot/appsettings.json index 87f2b059d2b..ee239315750 100644 --- a/tools/sdk-ai-bots/AzureSdkQaBot/appsettings.json +++ b/tools/sdk-ai-bots/AzureSdkQaBot/appsettings.json @@ -10,6 +10,8 @@ "BOT_PASSWORD": "${botPassword}", "GITHUB_TOKEN": "", "APPLICATIONINSIGHTS_CONNECTION_STRING": "", + "KeyVaultUrl": "https://kv-sdk-tools-test.vault.azure.net", + "CertificateName": "AzureSDK-AI-Bot", "OpenAI": { "ApiKey": "" }, diff --git a/tools/sdk-ai-bots/AzureSdkQaBot/infra/azure.bicep b/tools/sdk-ai-bots/AzureSdkQaBot/infra/azure.bicep index 4658455f8df..2c1080501d7 100644 --- a/tools/sdk-ai-bots/AzureSdkQaBot/infra/azure.bicep +++ b/tools/sdk-ai-bots/AzureSdkQaBot/infra/azure.bicep @@ -36,6 +36,8 @@ param location string = resourceGroup().location param azureEmbeddingModelDeploymentName string param azureChatModelDeploymentName string +param keyVaultUrl string +param certificateName string @secure() param searchServiceUrl string @@ -77,6 +79,10 @@ resource webApp 'Microsoft.Web/sites@2021-02-01' = { name: 'RUNNING_ON_AZURE' value: '1' } + { + name: 'WEBSITE_LOAD_USER_PROFILE' + value: '1' + } { name: 'BOT_ID' value: botAadAppClientId @@ -129,6 +135,14 @@ resource webApp 'Microsoft.Web/sites@2021-02-01' = { name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' value: appInsightConnectionString } + { + name: 'KeyVaultUrl' + value: keyVaultUrl + } + { + name: 'CertificateName' + value: certificateName + } ] ftpsState: 'FtpsOnly' } diff --git a/tools/sdk-ai-bots/AzureSdkQaBot/infra/azure.parameters.json b/tools/sdk-ai-bots/AzureSdkQaBot/infra/azure.parameters.json index 80902e56789..7c7a824446a 100644 --- a/tools/sdk-ai-bots/AzureSdkQaBot/infra/azure.parameters.json +++ b/tools/sdk-ai-bots/AzureSdkQaBot/infra/azure.parameters.json @@ -49,6 +49,12 @@ }, "appInsightConnectionString": { "value": "${{SECRET_APP_INSIGHT_CONNECTION_STRING}}" + }, + "keyVaultUrl": { + "value": "${{KEYVAULT_URL}}" + }, + "certificateName": { + "value": "${{CERTIFICATE_NAME}}" } } } \ No newline at end of file