在构建主机之前从 KeyVault 获取 Azure WebJobs 连接字符串

Get Azure WebJobs connection strings from KeyVault before host is built

我正在按照 https://docs.microsoft.com/en-us/azure/app-service/app-service-key-vault-references

的指示进行操作

本质上,我试图保护用于 AzureWebJobsDashboard 和 AzureWebJobsStorage 的存储连接字符串在 Azure Key vault 秘密后面。我无法使用注入的 KeyVault 服务来获取它,因为我的服务容器尚未构建。所以我发现(通过上面的 link)我可以在配置中使用“@Microsoft.KeyVault()”表达式来表达这个意图。这是一个示例,我将配置移动到内联代码以保持简洁:

.ConfigureHostConfiguration(configurationBuilder =>
{
    configurationBuilder
        .AddConfiguration(configuration)
        .AddInMemoryCollection(new Dictionary<string, string>
        {
            ["ConnectionStrings:AzureWebJobsDashboard"] = "@Microsoft.KeyVault(SecretUri=https://host.vault.azure.net/secrets/secret-name/ec545689445a40b199c0e0a956f16fca)",
            ["ConnectionStrings:AzureWebJobsStorage"] = "@Microsoft.KeyVault(SecretUri=https://host.vault.azure.net/secrets/secret-name/ec545689445a40b199c0e0a956f16fca)",
        });
})

如果我 运行 这个,我得到:

FormatException: No valid combination of account information found.

如果我将配置值从特殊注释更改为从 Key Vault 复制的秘密值('Show Secret Value' 按钮下方的蓝色复制按钮),一切正常。这向我证实了我使用的连接字符串是正确的。

此外,我手动使用 KeyVaultClient w/AzureServiceTokenProvider 来验证当 运行 在本地 Visual Studio 时进程应该工作,甚至在主机构建之前。我能够得到这个秘密就好了。这告诉我我有足够的权限来获取秘密。

所以现在我想知道这是否受支持。然而,有些页面暗示这是可能的,例如 https://medium.com/statuscode/getting-key-vault-secrets-in-azure-functions-37620fd20a0b。至少对于 Azure Functions。我正在使用 Azure Web Jobs,它被部署为一个带有 ASP.NET 核心服务的控制台应用程序,但我找不到该配置的示例。

任何人都可以澄清我所做的是否得到支持吗?如果不是,在构建 Azure Web 作业主机之前获取存储在 Azure Key Vault 中的连接字符串的建议过程是什么?

谢谢

我浏览了很多在线资源,一切似乎都表明特殊装饰的@Microsoft.KeyVault 设置仅在该值存在于 Azure 门户上的 AppSettings 中时才有效,而不是在本地配置中。如果这是一个不正确的评估,请有人告诉我。

所以为了解决这个问题,我提出了一个老实说感觉有点老套的解决方案,因为我依赖于连接字符串不是 read/cached 从本地配置到主机是 运行(不是在构建期间)。基本上,我的想法是构建一个配置提供程序,我可以在构建主机后为其设置一个值。例如:

public class DelayedConfigurationSource : IConfigurationSource
{
    private IConfigurationProvider Provider { get; } = new DelayedConfigurationProvider();

    public IConfigurationProvider Build(IConfigurationBuilder builder) => Provider;

    public void Set(string key, string value) => Provider.Set(key, value);

    private class DelayedConfigurationProvider : ConfigurationProvider
    {
        public override void Set(string key, string value)
        {
            base.Set(key, value);
            OnReload();
        }
    }
}

在主机生成器构造期间添加了对此类型的引用:

var delayedConfigurationSource = new DelayedConfigurationSource();

var hostBuilder = new HostBuilder()
    .ConfigureHostConfiguration(configurationBuilder =>
    {
        configurationBuilder
            .AddConfiguration(configuration)
            .Add(delayedConfigurationSource);
    })
    ...

并且确保在 运行 主机之前设置配置:

var host = hostBuilder.Build();

using (host)
{
    var secretProvider = host.Services.GetRequiredService<ISecretProvider>();
    var secret = await secretProvider.YourCodeToGetSecretAsync().ConfigureAwait(false);

    delayedConfigurationSource.Set("ConnectionStrings:AzureWebJobsStorage", secret.Value);

    await host.RunAsync().ConfigureAwait(false);
}

如果有更直观的方法来完成这个,请告诉我。如果不是,则连接字符串设计很愚蠢。