我有一个 .NET Core 6 Worker 应用程序,我已将其配置为使用 Azure KayVault(KeyVault 新手)
注册服务时,我需要从 Vault 获取密钥,但我发现与 keyVault 服务的连接尚未启动,所以这是典型的先有鸡还是先有蛋的情况。
在ConfigureServices中注册服务时如何访问密钥?
下面的代码示例显示我需要获取使用 LiteDB 的连接字符串,但我还有其他用例需要类似的解决方案:
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
// See notes in appsettings.josn file.
// See https://learn.microsoft.com/en-us/azure/azure-monitor/app/worker-service
var aiOptions = new ApplicationInsightsServiceOptions();
aiOptions.ConnectionString = configuration["APPINSIGHTS_CONNECTIONSTRING"];
aiOptions.EnableQuickPulseMetricStream = configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableQuickPulseMetricStream");
aiOptions.EnableEventCounterCollectionModule = configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableEventCounterCollectionModule");
aiOptions.EnableAppServicesHeartbeatTelemetryModule = configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableAppServicesHeartbeatTelemetryModule");
aiOptions.EnableAzureInstanceMetadataTelemetryModule = configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableAzureInstanceMetadataTelemetryModule");
aiOptions.EnableDependencyTrackingTelemetryModule = configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableDependencyTrackingTelemetryModule");
aiOptions.EnableEventCounterCollectionModule = configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableEventCounterCollectionModule");
aiOptions.EnableAdaptiveSampling = configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableAdaptiveSampling");
aiOptions.EnableHeartbeat = configuration.GetSection("ApplicationInsights").GetValue<bool>("EnableHeartbeat");
aiOptions.AddAutoCollectedMetricExtractor = configuration.GetSection("ApplicationInsights").GetValue<bool>("AddAutoCollectedMetricExtractor");
services.AddApplicationInsightsTelemetryWorkerService(aiOptions);
// ------------------------
// ----- Lite DB START-----
// ------------------------
// *This doesnt work becuase the keyvault client hasnt started up yet!!!*
var connectionString = configuration.GetSection("LiteDB").GetValue<string>("ConnectionString");
// HOW DO I GET THE KEY VAUT KEY HERE???
services.AddSingleton<ILiteDatabase, LiteDatabase>(x => new LiteDatabase(connectionString));
// -----------------------
// ----- Lite DB END-----
// -----------------------
// Repository used for our own logging events throughout the business logic code base.
services.AddTransient<ILogExtension, LogExtension>();
// Add the Background Services
services.AddHostedService<AzureSignalRService>();
})
.ConfigureAppConfiguration((context, config) => // Azure KeyVault Configuration
{
if (context.HostingEnvironment.IsDevelopment() | context.HostingEnvironment.IsProduction())
{
// See https://learn.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-6.0
// See https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/Startup.cs
var root = config.Build();
using var x509Store = new X509Store(StoreLocation.CurrentUser);
x509Store.Open(OpenFlags.ReadOnly);
var x509Certificate = x509Store.Certificates
.Find(
X509FindType.FindByThumbprint,
root["AzureADCertThumbprint"],
validOnly: false)
.OfType<X509Certificate2>()
.Single();
config.AddAzureKeyVault(
new Uri($"https://{root["KeyVaultName"]}.vault.azure.net/"),
new ClientCertificateCredential(
root["AzureADDirectoryId"],
root["AzureADApplicationId"],
x509Certificate));
}
})
.Build();
解决这个问题的方法是创建一个服务集合扩展类并在 Program.cs 中指向它,而不是在 ConfigureServices 中注册服务。
从 appsettings.json 获取属性时需要解析 KeyVault 密钥的任何代码现在都可以正常工作。这对我来说似乎很幸运,这有效,我只能假设服务扩展以某种方式知道在启动的第一阶段后返回并获取任何丢失的密钥。
程序.cs
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
// ------------------------
// ----- Lite DB START-----
// ------------------------
// ----- BAD -----
// *This doesnt work becuase the keyvault client hasnt started up yet!!!*
var connectionString = configuration.GetSection("LiteDB").GetValue<string>("Password");
// Simply returns the native value in app settings, rather than the actual key value stored in Azure KeyVault
Console.WriteLine(connectionString);
// ---------------------------------
// ----- Solution -----
// Add the IService Collection Extension, see seperate class further down...
services.AddDatabase();
// -----------------------
// ----- Lite DB END-----
// -----------------------
// Repository used for our own logging events throughout the business logic code base.
services.AddTransient<ILogExtension, LogExtension>();
// Add the Background Services
services.AddHostedService<AzureSignalRService>();
})
.ConfigureAppConfiguration((context, config) => // Azure KeyVault Configuration
{
if (context.HostingEnvironment.IsDevelopment() | context.HostingEnvironment.IsProduction())
{
// See https://learn.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-6.0
// See https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/Startup.cs
var root = config.Build();
using var x509Store = new X509Store(StoreLocation.CurrentUser);
x509Store.Open(OpenFlags.ReadOnly);
var x509Certificate = x509Store.Certificates
.Find(
X509FindType.FindByThumbprint,
root["AzureADCertThumbprint"],
validOnly: false)
.OfType<X509Certificate2>()
.Single();
config.AddAzureKeyVault(
new Uri($"https://{root["KeyVaultName"]}.vault.azure.net/"),
new ClientCertificateCredential(
root["AzureADDirectoryId"],
root["AzureADApplicationId"],
x509Certificate));
}
})
.Build();
单独的 IServiceCollection 类:
internal static class ServiceCollectionDatabaseExtensions
{
public static IServiceCollection AddDatabase(this IServiceCollection services)
{
var config = services.BuildServiceProvider().GetService<IConfiguration>();
var password = config!.GetSection("LiteDB").GetValue<string>("Password");
Console.WriteLine(password); // This now returns the correct key vaue pulled from Azure Key Vault
var connectionString = "Filename=C:\'database.db;Connection=shared;Password=" + password;
Console.WriteLine(connectionString);
services.AddSingleton<ILiteDatabase, LiteDatabase>(x => new LiteDatabase(connectionString));
// Other services here...
return services;
}
}
ConfigureAppConfiguration
在 ConfigureServices
之前运行,因此当您配置服务时,配置已经加载。从逻辑上讲,iy 应该是这样的:
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
config.AddAzureKeyVault(...);
})
.ConfigureServices(services =>
{
...
})
.Build();
在您的
ConfigureServices
方法中,您将重新创建仅包含 appsettings.json 文件的配置,因此它永远不会从密钥保管库配置中获取机密。还有另一个接受 HostbuilderContext
的重载
.ConfigureServices((context, services) =>
{
// Get the configuration
var configuration = context.Configuration;
...
})