C# 未经列表许可,Azure Key Vault';禁止';错误

C# 未经列表许可,Azure Key Vault';禁止';错误,c#,azure,azure-web-app-service,azure-keyvault,C#,Azure,Azure Web App Service,Azure Keyvault,TL;DR:未经列表许可,在Azure中部署的Asp.Net Core 2.2 Web应用程序失败,出现错误-Microsoft.Azure.KeyVault.Models.KeyVault错误异常:使用AzureServiceTokenProvider时,操作在启动时返回无效的状态代码“禁止” 我正在开发Asp.Net Core 2.2 Web应用程序,我知道Azure Key Vault是如何工作的,以及部署在Azure中的Web应用程序是如何从Key valut访问密钥、机密和证书的 以下

TL;DR:未经列表许可,在Azure中部署的Asp.Net Core 2.2 Web应用程序失败,出现错误-
Microsoft.Azure.KeyVault.Models.KeyVault错误异常:使用
AzureServiceTokenProvider时,操作在启动时返回无效的状态代码“禁止”

我正在开发Asp.Net Core 2.2 Web应用程序,我知道Azure Key Vault是如何工作的,以及部署在Azure中的Web应用程序是如何从Key valut访问密钥、机密和证书的

以下是我当前的配置:

我已创建Azure Key Vault以存储我所有客户端的订阅信息:

然后我创建了Azure Web App并为其创建了标识:

稍后在Azure密钥库访问策略中,我授予此应用程序“获取和列出机密”权限

我不想在我的代码中硬编码任何机密,因此我使用
AzureServiceTokenProvider
连接并获取机密,下面是我的Program.cs文件代码:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureKeyVault;
using Microsoft.Extensions.Logging;
using NLog.Common;
using NLog.Web;

namespace AzureSecretsTest
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
            try
            {
                InternalLogger.LogFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "nlog-internals.txt");
                var host = CreateWebHostBuilder(args).Build();
                host.Run();
            }
            catch (Exception ex)
            {
                logger.Error(ex, "Stopped program because of exception");
                throw;
            }
            finally
            {
                // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
                NLog.LogManager.Shutdown();
            }
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration((ctx, builder) =>
                {
                    //https://anthonychu.ca/post/secrets-aspnet-core-key-vault-msi/
                    var keyVaultEndpoint = Environment.GetEnvironmentVariable("KEYVAULT_ENDPOINT");
                    if (!string.IsNullOrEmpty(keyVaultEndpoint))
                    {
                        var azureServiceTokenProvider = new AzureServiceTokenProvider();
                        var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
                        builder.AddAzureKeyVault(keyVaultEndpoint, keyVaultClient, new DefaultKeyVaultSecretManager());
                    }
                })
                .UseStartup<Startup>();
    }
}
只要我授予“列表”权限,一切正常。然而,通过授予“列表”权限,我注意到可以访问整个秘密列表。这暴露了我不太满意的所有其他客户端订阅信息。我可以为每个客户创建一个密钥库,但这似乎有点过头了

可能是我犯了一个愚蠢的错误,但我没有看到它,或者很可能是您无法删除“列表”权限。无论是哪种方式,如果有更多知识的人能够说明我是否可以在不授予列表权限的情况下使用
AzureServiceTokenProvider
,我将不胜感激

更新:1

发现GitHub中已记录此问题: 及

更新:2 根据答案,这是最终工作代码:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.AzureKeyVault;
using Microsoft.Extensions.Logging;
using NLog.Common;
using NLog.Web;

namespace AzureSecretsTest
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
            try
            {
                InternalLogger.LogFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "nlog-internals.txt");
                var host = CreateWebHostBuilder(args).Build();
                host.Run();
            }
            catch (Exception ex)
            {
                logger.Error(ex, "Stopped program because of exception");
                throw;
            }
            finally
            {
                // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
                NLog.LogManager.Shutdown();
            }
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration((ctx, builder) =>
                {
                    //https://anthonychu.ca/post/secrets-aspnet-core-key-vault-msi/
                    //var keyVaultEndpoint = Environment.GetEnvironmentVariable("KEYVAULT_ENDPOINT");
                    //if (!string.IsNullOrEmpty(keyVaultEndpoint))
                    //{
                    //    var azureServiceTokenProvider = new AzureServiceTokenProvider();
                    //    var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
                    //    keyVaultClient.GetSecretAsync(keyVaultEndpoint, "").GetAwaiter().GetResult();
                    //    //builder.AddAzureKeyVault(keyVaultEndpoint, keyVaultClient, new DefaultKeyVaultSecretManager());
                    //}
                })
                .UseStartup<Startup>();
    }
}

正如Thomas所说,当您使用
AddAzureKeyVault
扩展添加
KeyVault提供程序时。此时,中间件有足够的信息去提取所有KeyVault数据。我们可以立即开始使用配置API提取秘密值

因此,如果您想获得特定的机密以保持安全性,可以使用以下代码

var azureServiceTokenProvider = new AzureServiceTokenProvider();

var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));

var scret = keyVaultClient.GetSecretAsync(keyvaultEndpoint, SecretName).GetAwaiter().GetResult();

这里的问题是,使用
AddAzureKeyVault
扩展从密钥库加载所有机密,使其在应用程序中可用。您可以手动检索机密,也可以为每个客户设置密钥库
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace AzureSecretsTest
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, IConfiguration configuration)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.Run(async (context) =>
            {
                var keyVaultEndpoint = Environment.GetEnvironmentVariable("KEYVAULT_ENDPOINT");
                StringBuilder sb = new StringBuilder();
                if (!string.IsNullOrEmpty(keyVaultEndpoint))
                {
                    var azureServiceTokenProvider = new AzureServiceTokenProvider();
                    var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
                    var secret = await keyVaultClient.GetSecretAsync(keyVaultEndpoint, "OneOfTheSecretKey");
                    sb.AppendLine($"Is null or whitespace: {string.IsNullOrWhiteSpace(secret.Value)}, Value: '{secret.Value}'");
                }

                //string configValue = configuration["OneOfTheSecretKey"];
                //var children = configuration.GetChildren();

                //sb.AppendLine($"Is null or whitespace: {string.IsNullOrWhiteSpace(configValue)}, Value: '{configValue}'");

                //foreach (IConfigurationSection item in children)
                //{
                //    sb.AppendLine($"Key: {item.Key}, Value: {item.Value}");
                //}
                await context.Response.WriteAsync(sb.ToString());
            });
        }
    }
}
var azureServiceTokenProvider = new AzureServiceTokenProvider();

var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));

var scret = keyVaultClient.GetSecretAsync(keyvaultEndpoint, SecretName).GetAwaiter().GetResult();