O armazenamento de configurações em que estão presentes informações sensíveis e utilizando arquivos como appsettings.json constitui uma prática bastante difundida. Haverá ocasiões, entretanto, nas quais mecanismos externos precisarão ser empregados a fim de dificultar o acesso a tais definições por parte de um usuário qualquer (que as obteria rapidamente pela simples leitura de um arquivo).
Considerando o desenvolvimento de projetos Web com ASP.NET Core, como proteger segredos de nossas aplicações de maneira simples e eficiente?
Uma boa resposta para esse questionamento está no Azure Key Vault, uma solução de armazenamento de configurações sensíveis oferecida pela plataforma de cloud computing da Microsoft. Possuindo integração com o Azure Active Directory, a manipulação (leitura, escrita) de segredos definidos no Key Vault acontece mediante a concessão prévia de acesso a uma aplicação.
Neste novo artigo demonstrarei o uso do Azure Key Vault em uma API REST criada com o ASP.NET Core 2.2. O exemplo em questão foi disponibilizado também no GitHub:
https://github.com/renatogroffe/ASPNETCore2.2_API-REST_AzureKeyVault
Registrando uma aplicação para utilização do Azure Key Vault
Neste primeiro momento acessaremos o Portal do Microsoft Azure:
Na sequência acionar na barra lateral à esquerda o item Azure Active Directory:
Clicar agora na opção App registrations:
Aparecerão então eventuais aplicações já registradas no painel. Clicar na opção New registration, a qual permitirá que registremos a API REST empregada nos testes detalhados neste artigo:
Em Register an Application preencher:
- O nome da aplicação (campo Name);
- Manter selecionada a opção Accounts in this organizational directory only no campo Supported account types;
- Em Redirect URI informar o valor https://localhost:5001 (neste artigo será feito um teste local apontando para o Azure Key Vault).
Confirmar este procedimento acionando o botão Register.
O registro da aplicação será exibido neste momento. O valor da configuração Application (client) ID deverá ser utilizado posteriormente, ao se configurar a comunicação da aplicação com o recurso do Key Vault:
Acionar agora a opção Certificates & secrets; o botão New client secretpermitirá a geração de uma chave, a ser empregada pela aplicação para validar seu acesso ao Azure Key Vault:
Em Add a client secret preencher o campo Description, selecionando ainda em Expires o valor Never. Confirmar as ações clicando no botão Add:
O valor indicado no campo VALUE do segredo deverá ser COPIADO IMEDIATAMENTE (um aviso no alto do painel alerta que tal configuração não estará mais disponível quando o usuário deixar esta funcionalidade no Portal do Azure):
E aproveito este espaço e o grande interesse por Docker também para um convite.
Tem interesse em conhecer mais sobre Docker? Que tal então fazer um curso completo, cobrindo desde fundamentos a diferentes possibilidades de uso de containers com tecnologias em alta no mercado? Adquira conhecimentos profundos sobre Docker, evolua e se diferencie no mercado, seja você um profissional DevOps, um Desenvolvedor ou um Arquiteto de Software!
Acompanhe o portal Docker Definitivo para ficar por dentro de novidades a serem anunciadas em breve!
Criando um recurso do Azure Key Vault
Ao solicitar a criação de um novo recurso no Portal do Azure pesquisar por Azure Key Vault, selecionado a opção destacada a seguir:
Clicar agora em Create:
Em Create key vault informar o nome do recurso (campo Name), um grupo de recursos (campo Resource Group) e o data center em que o mesmo será criado (Location). Acionar em seguida a opção Access policies:
Clicar em Add new, a fim de vincular ao recurso do Key Vault a aplicação registrada no Azure Active Directory:
No painel Add access policy selecionar em Configure from template (optional) a opção Secret Management, clicando na sequência em Select principal:
Em Principal localizar e selecionar a opção API REST Cotações, confirmando isso através do botão Select:
Já em Secret permissions habilitar apenas as opções Get e List, de forma que a API REST vinculada a este recurso do Azure Key Vault possua somente acesso de leitura às configurações cadastradas:
Concluir este procedimento clicando no botão OK:
Aparecerá agora em Access policies o item API REST Cotações registrado como APPLICATION; confirmar este ajuste por meio do botão OK:
Retornando ao painel Create key vault concluir o processo de criação clicando no botão Create. Após alguns segundos o recurso groffeartigoestará disponível; o endereço indicado em DNS NAME também será também utilizado para configurar o acesso na API REST de testes:
Adicionando configurações sensíveis no Azure Key Vault
Na seção Secrets do recurso do Key Vault serão cadastradas configurações que não deverão constar no arquivo appsettings.json. Acionar para isto a opção Generate/Import (destacada em vermelho):
Em Create a secret:
- Manter o campo Upload options com o valor Manual selecionado;
- Em Name informar o nome da configuração de forma similar à que a mesma estaria no appsettings.json. No caso de uma string de conexão, é normal que um item correspondente ao nome (BaseCotacoes) seja precedido pelo elemento ConnectionStrings. O uso de “–” (2 hífens)permite separar os níveis que existiriam em um arquivo JSON;
- Preencher o campo Value com a Connection String;
- Em Content type informar o valor Connection String.
Concluir esta ação pressionando o botão Create. O segredo ConnectionStrings–BaseCotacoes estará então disponível para uso:
Acessando o item ConnectionStrings–BaseCotacoes será exibida uma tela similar àquela apresentada na imagem a seguir:
Um clique na definição que está em CURRENT VERSION trará a próxima tela:
Com um simples clique em Show Secret Value podemos consultar o valor da Connection String armazenada no recurso groffeartigo:
Configurando o uso do Azure Key Vault em uma aplicação ASP.NET Core
O package Microsoft.Extensions.Configuration.AzureKeyVault deverá ser adicionado ao projeto que terá seus segredos armazenados no Key Vault:
Incluir agora a seção AzureKeyVault no arquivo appsettings.json, preenchendo as seguintes configurações:
- Em DNS informar o conteúdo associado ao campo DNS NAME do recurso do Azure Key Vault criado na seção anterior;
- Em ClientId preencher o valor do campo Application (client) IDcorrespondente à aplicação registrada anteriormente no Azure Active Directory;
- O campo ClientSecret receberá a chave gerada durante a criação do segredo (em Certificates & secrets) para a aplicação API REST Cotação no Azure Active Directory.
Conforme é possível observar, não há qualquer menção a uma string de conexão neste arquivo de configurações:
{
"AzureKeyVault": {
"DNS": "https://groffeartigo.vault.azure.net/",
"ClientId": "02d882bb-e1c9-4daa-a972-c56dbf6657ae",
"ClientSecret": "aR[b-Gf/ksA*cJqFXVxrjuzo0nF3Ex47"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
A única alteração a ser realizada neste projeto acontecerá na classe Program:
- Logo após a chamada ao método CreateDefaultBuilder da classe WebHost em CreateWebHostBuilder acrescentar uma chamada ao método ConfigureAppConfiguration (o acionamento do método UseStartup ficará logo na sequência);
- Instanciar um novo builder de configurações em ConfigureAppConfiguration, invocando no objeto resultante o método AddAzureKeyVault e informando como parâmetros as chaves definidos para o elemento AzureKeyVault no arquivo appsettings.json (DNS, ClientId e ClientSecret).
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.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace APICotacoes
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
var builtConfig = config.Build();
config.AddAzureKeyVault(
builtConfig["AzureKeyVault:DNS"],
builtConfig["AzureKeyVault:ClientId"],
builtConfig["AzureKeyVault:ClientSecret"]);
})
.UseStartup<Startup>();
}
}
Testes
A seguir temos a implementação da classe Startup, com o uso normal do objeto Configuration. Isto inclui um acesso à chave ConnectionStrings:BaseCotacoes (linha 35), a qual não existe em appsettings.json e que numa situação normal implicaria na existência de dois níveis de propriedades no arquivo de configuração:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Net.Mime;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Newtonsoft.Json;
namespace APICotacoes
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// Adicionando a verificação de disponibilidade
// do banco de dados
services.AddHealthChecks()
.AddSqlServer(Configuration["ConnectionStrings:BaseCotacoes"], name: "sql1");
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
// Ativando o middlweare de Health Check
app.UseHealthChecks("/status",
new HealthCheckOptions()
{
ResponseWriter = async (context, report) =>
{
var result = JsonConvert.SerializeObject(
new
{
statusApplication = report.Status.ToString(),
healthChecks = report.Entries.Select(e => new
{
check = e.Key,
status = Enum.GetName(typeof(HealthStatus), e.Value.Status)
})
});
context.Response.ContentType = MediaTypeNames.Application.Json;
await context.Response.WriteAsync(result);
}
});
app.UseHttpsRedirection();
app.UseMvc();
}
}
}
Efetuando o debugging da aplicação é possível constatar que o ajuste na classe Program vinculou a string de conexão que está no recurso do Azure Key Vault (o segredo ConnectionStrings–BaseCotacoes) ao objeto Configuration. Tal fato permite a diferentes partes da aplicação acessar configurações sem grandes complicações, esteja uma determinada definição na nuvem ou no arquivo appsettings.json:
Um segundo teste envolverá a classe CotacoesController. Uma chamada a config.GetConnectionString(“BaseCotacoes”) – linha 18 – permitirá acessar o segredo que está no Azure Key Vault, como se a definição correspondente fosse um item chamado BaseCotacoes e estivesse vinculada ao elemento ConnectionStrings em appsettings.json:
using System.Data.SqlClient;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Dapper;
namespace APICotacoes.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CotacoesController : ControllerBase
{
[HttpGet]
public ContentResult Get(
[FromServices]IConfiguration config)
{
string valorJSON;
using (SqlConnection conexao = new SqlConnection(
config.GetConnectionString("BaseCotacoes")))
{
valorJSON = conexao.QueryFirst<string>(
"SELECT Sigla " +
",NomeMoeda " +
",UltimaCotacao " +
",ValorComercial AS 'Cotacoes.Comercial' " +
",ValorTurismo AS 'Cotacoes.Turismo' " +
"FROM dbo.Cotacoes " +
"ORDER BY NomeMoeda " +
"FOR JSON PATH, ROOT('Moedas')");
}
return Content(valorJSON, "application/json");
}
}
}
É o que demonstra o exemplo de debugging na próxima imagem: