TL; DR: Без разрешения списка Веб-приложение Asp.Net Core 2.2, развернутое в Azure, завершается с ошибкой - Microsoft.Azure.KeyVault.Models.KeyVaultErrorException: Operation returned an invalid status code 'Forbidden'
при запуске при использовании AzureServiceTokenProvider
, над которым я работаюВеб-приложение Asp.Net Core 2.2, и я знаю, как работает хранилище ключей Azure и как веб-приложение, развернутое в Azure, может получать доступ к ключам, секретам и сертификатам из ключевого кода.
Ниже приведена текущая конфигурация:
Я создал хранилище ключей Azure для хранения информации о подписке всех моих клиентов:
Затем я создал веб-приложение Azure и создал для него удостоверение:
Позже в политиках доступа к хранилищу ключей 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>();
}
}
Ниже приведен простой файл Startup.cs с кодом для доступа к секретам из хранилища ключей Azure:
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());
});
}
}
}
Всеработает нормально, пока я дал разрешение «Список»Однако, предоставив разрешение «Список», я заметил, что весь секретный список доступен.Это раскрывает всю другую информацию о подписке клиента, которая меня не устраивает.Я могу пойти и создать одно хранилище ключей для каждого клиента, но это кажется излишним.
Возможно, я делаю глупую ошибку и не вижу ее, или вполне возможно, что вы не можете удалить разрешение «Список».В любом случае я был бы признателен, если бы кто-то, обладающий большими знаниями, мог пролить свет на то, могу ли я использовать AzureServiceTokenProvider
без предоставления разрешения для Списка или нет?
Обновление: 1
Обнаружено, что в GitHub уже есть проблема, зарегистрированная для этого: Ошибка обработки разрешения списка для секретов и Сбой ключа Azure без списка разрешений на секреты
Обновление: 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());
});
}
}
}