没有列表权限 Azure Key Vault 'Forbidden' 错误

Without List permission Azure Key Vault 'Forbidden' error

TL;DR: 没有列表权限 Asp.Net Azure 中部署的核心 2.2 Web 应用程序失败并出现错误 - Microsoft.Azure.KeyVault.Models.KeyVaultErrorException: Operation returned an invalid status code 'Forbidden' 在启动时使用 [=16] =]

我正在研究 Asp.Net 核心 2.2 Web 应用程序,我知道 Azure Key Vault 的工作原理以及部署在 Azure 中的 Web 应用程序如何从密钥值访问密钥、机密和证书。

以下是我当前的配置:

我创建了一个 Azure Key Vault 来存储我所有客户的订阅信息:

然后我创建了 Azure Web App 并为其创建了身份:

稍后在 Azure Key Vault 访问策略中,我授予了此 App Get 和 List 秘密权限。

我不想在我的代码中硬编码任何秘密,因此我使用 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>();
    }
}

下面是我的简单 Startup.cs 文件,其中包含从 Azure Key Vault 访问 Secrets 的代码:

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.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) =>
            {
                string configValue = configuration["OneOfTheSecretKey"];
                StringBuilder sb = new StringBuilder();
                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());
            });
        }
    }
}

只要我授予 "List" 权限,一切正常。但是,通过授予 "List" 权限,我注意到可以访问整个 Secret 列表。这暴露了我不太高兴的所有其他客户端订阅信息。我可以为每个客户端创建一个 Key Vault,但这似乎有些矫枉过正。

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

更新:1

发现 GitHub 中已经记录了此问题: Handle No List Permission for Secrets and Azure Key Vault with no List permissions on Secrets fails

更新: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>();
    }
}

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());
            });
        }
    }
}

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

所以,如果你想得到具体的秘密来保证安全,你可以使用下面的代码。

var azureServiceTokenProvider = new AzureServiceTokenProvider();

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

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