我是 Azure 和 Azure Key Vault 的新手。我已经阅读了许多有关如何设置 Web API 以利用 Azure Key Vault 的教程,但在成功存储机密并使用下面的代码设置我的
Program.cs
文件后,我无法弄清楚我在做什么接下来应该做的是在我的课堂上实际访问秘密。
我最初的目标是将数据库的连接字符串存储在 Key Vault 中,然后当 API 启动时,我将获取连接字符串并将其“存储”在本地(内存中)以供我的数据访问层类使用。
var builder = WebApplication.CreateBuilder(args);
var keyVaultEndpoint = new Uri(Environment.GetEnvironmentVariable("VaultUri"));
builder.Configuration.AddAzureKeyVault(keyVaultEndpoint,
new DefaultAzureCredential());
我有一个继承自
DbContext
的类,并希望获得设置该类的连接字符串的秘密 - 理想情况下不需要每次都返回到密钥库并将其拉到本地(但不将其存储在文件中)。我认为这只是我这边对于这个流程应该如何运作的一个差距。
任何帮助我走上正轨的帮助都会很棒!
我可以看到
builder.Configuration
确实包含了我想要的秘密。现在,我只是努力将该信息获取到可以从 DbContext 类访问的位置,而无需额外调用 Key Vault 也不会将它们存储在本地文件中。
我们正在使用自定义
IKeyVaultSecretManager
将应用程序设置文件中的秘密占位符替换为密钥保管库机密。
${secret-name}
形式的配置值将替换为同名的密钥保管库机密的值。当应用程序设置文件加载到 IConfiguration
实例时,这一切都发生在内存中。
自定义秘密管理器:
namespace Foo;
using Azure.Extensions.AspNetCore.Configuration.Secrets;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Configuration;
internal class KeyVaultSecretReplacer : KeyVaultSecretManager
{
private readonly Dictionary<string, string> _placeholders;
public KeyVaultSecretReplacer(IConfiguration config)
=> _placeholders = config
.AsEnumerable()
.Where(HasSecretPlaceholder)
.ToDictionary(
keySelector: GetSecretName,
elementSelector: GetSettingKey,
StringComparer.OrdinalIgnoreCase);
public override bool Load(SecretProperties secret) => _placeholders.ContainsKey(secret.Name);
public override Dictionary<string, string> GetData(IEnumerable<KeyVaultSecret> secrets)
{
var loadedSecrets = secrets.ToArray();
ValidateAllSecretsFound(loadedSecrets);
return base.GetData(loadedSecrets);
}
private void ValidateAllSecretsFound(IList<KeyVaultSecret> secrets)
{
var missing = _placeholders.Keys.Except(secrets.Select(s => s.Name)).ToArray();
if (!missing.Any())
{
return;
}
var vaultName = secrets.FirstOrDefault()?.Properties.VaultUri.Host.Replace(".vault.azure.net", "");
var notFoundMsg = string.Join(", ", missing.Select(s => $"'{s}' (setting '{_placeholders[s]}')"));
throw new InvalidOperationException(
$"Some secrets were not found in key vault{(vaultName is null ? "" : $" '{vaultName}'")}: {notFoundMsg}.");
}
public override string GetKey(KeyVaultSecret secret) => _placeholders[secret.Name];
internal static bool HasSecretPlaceholder(KeyValuePair<string, string?> c)
=> c.Value != null && c.Value.StartsWith("${") && c.Value.EndsWith("}");
private static string GetSecretName(KeyValuePair<string, string?> c) => c.Value![2..^1];
private static string GetSettingKey(KeyValuePair<string, string?> c) => c.Key;
}
部分用法:
private static void AddKeyVaultSecretReplacer(IConfigurationBuilder configBuilder, IConfigurationRoot config)
{
var settings = config.GetSection(nameof(SecretReplacement))?.Get<SecretReplacementSettings>()
?? throw new InvalidOperationException($"Secret placeholders found in app settings but {nameof(SecretReplacement)} has not been configured.");
configBuilder.AddAzureKeyVault(
new Uri(settings.AzureKeyVaultUrl),
new DefaultAzureCredential(new DefaultAzureCredentialOptions { TenantId = settings.AzureTenantId }),
new KeyVaultSecretReplacer(config));
}