Article image
Vinicius Fernandes
Vinicius Fernandes09/12/2021 19:27
Share

Gerenciamento de segredos em aplicações .NET

  • #.NET Core
  • #.NET

Olá pessoal,

Recentemente no trabalho me deparei com a seguinte vulnerabilidade : Senhas, strings de conexão e chaves de API escritas diretamente no código. O que certamente é um perigo já que caso alguém má intencionado obtenha acesso a isso as consequências são inimagináveis.

A aplicação em questão é estruturada utilizando ASP .NET Core, mas acredito que as ideias apresentadas aqui podem ser úteis para outros casos também. Dessa forma vamos discutir a seguir algumas maneiras de lidar com essa situação

Utilizando o arquivo de configurações

O .NET nos possibilita utilizar um arquivo para gerenciar as configurações da nossa aplicação. Dessa forma podemos deixar nossos segredos separados do código fonte.

Considere o seguinte arquivo appsettings.json da nossa aplicação:

{
"ConnectionStrings":{
"DefaultConnection":"stringDeConexaoComOBD",
},
"Logging": {
  "LogLevel": {
    "Default": "Error"
  }
},
"SMTP":{
"Password":"123"
}
}

Basicamente nesse arquivo de configuração determinamos o nível de logging , também inserimos nossa string de conexão para o banco de dados e a senha de um e-mail utilizado para envios de e-mail pela aplicação.

Nosso Program.cs se encontra da seguinte forma:

namespace PostDIO
{
  public class Program
  {
      public static void Main(string[] args)
      {
          CreateHostBuilder(args).Build().Run();
      }

      public static IHostBuilder CreateHostBuilder(string[] args) =>
          Host.CreateDefaultBuilder(args)
              .ConfigureWebHostDefaults(webBuilder =>
              {
                  webBuilder.UseStartup<Startup>();
              });
  }
}

O método CreateDeafultBuild é responsável por carregar as opções de configuração do arquivo appsettings.json.

Abaixo temos o Startup.cs da nossa aplicação.

namespace PostDIO
{
  public class Startup
  {
      public Startup(IConfiguration configuration)
      {
          Configuration = configuration;
      }

      public IConfiguration Configuration { get; }
      
      public void ConfigureServices(IServiceCollection services)
      {
          services.AddAuthentication(options =>
          {
              options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
              options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
              options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
          }).AddCookie()

      var DefaultConnString = Configuration.GetConnectionString("DefaultConnection");
var StmpPass= Configuration["SMTP:Password"];



              services.AddControllersWithViews();
          }
      
          // ...existing code...
      }
  }

Observe que nosso construtor Startup() tem o parâmetro configuration que implementa a interface IConfiguration (ref https://docs.microsoft.com/pt-br/dotnet/api/microsoft.extensions.configuration.iconfiguration?view=dotnet-plat-ext-6.0). A partir da propriedade Configuration podemos acessar então os nossos segredos que estão no appsettings, os segredos são armazenados nas variáveis DefaultConnString e StmpPass e poderiam então ser utilizada para configurar serviços que necessitam delas.

O arquivo de configurações resolve o problema de armazenar segredos diretamente no código fonte. Porém você corre um risco aqui de fazer o commit desse arquivo para um repositório, no qual mais pessoas teriam acesso a esses segredos. Pensando em uma aplicação que é mantida por diversos desenvolvedores isso representa um certo risco.

O gerenciador de segredos

Uma maneira mais segura de armazenar os segredos ao menos no ambiente de desenvolvimento é utilizar o gerenciador de segredos. Essa ferramenta está disponível através de um comando CLI.

O objetivo do gerenciador de segredos é armazenar os segredos da sua aplicação em um local diferente da pasta do projeto, o que evita que eles sejam commitados para o seu repositório.

Primeiro inicialize a ferramenta.

dotnet user-secrets init

Após a inicialização você pode armazenar os segredos da sua aplicação como mostrado a seguir:

dotnet user-secrets set "ConnectionStrings:DefaultConnect" "stringDeConexaoComOBD"
dotnet user-secrets set "SMTP:Password" "123"

O comando acima recebe dois parâmetros sendo eles a chave do segredo e o valor do segredo. Observe que eles utilizam a mesma sintaxe utilizada para mapear as propriedades do appsettings. Após executar os comandos você já pode remover os segredos do appsettings. O comando set também pode ser utilizado para atualizar o segredo poderiamos atualizar a string de conexão com o seguinte comando:

dotnet user-secrets set "ConnectionStrings:DefaultConnect" "stringDeConexaoComOBDupdate"

Você pode usar o comando:

dotnet user-secrets list

Para listar os segredos cadastrados.

Também podemos remover um segredo com:

dotnet user-secrets remove "ConnectionStrings:DefaultConnect"

Ou então todos os segredos:

dotnet user-secrets clear

Como utilizamos o método CreateDefaultBuild() não precisamos alterar nada em nosso código.

O único lado ruim dessa abordagem é que ela é recomendada apenas para o ambiente de desenvolvimento.

Utilizando variáveis de ambiente

Armazenar os segredos em um armazenamento volátil como as variáveis de ambiente parece uma abordagem mais interessante que as anteriores.

As variáveis de ambiente são locais para o processo com os quais elas são definidas, então um invasor teria que possuir os mesmo acessos daquele processo ou um acesso superior.

Observe que as variáveis de ambiente apenas diminuem o risco de expor os segredos, mas não são a solução, pois se elas forem definidas sem nenhuma criptografia os valores ainda podem ser acessados.

Nesse caso não precisamos mudar nada no código também pois o CreateDefaultBuilder cuida disso pra gente. Com a configuração padrão dele as configurações são carregadas na seguinte ordem:

  1. Arquivo appsettings.json
  2. Arquivo appsettings.<ambiente>.json onde o ambiente pode ser de produção, desenvolvimento ou algum outro ambiente.
  3. Segredos do gerenciador de segredos
  4. Variáveis de ambiente
  5. Valores de argumentos da linha de comando

Atente-se que nem todo sistema operacional suporta o caractere : , mas possibilitam o uso de _ então podemos utilizar __ no lugar de : para descrever nossa estrutura hierárquica.

Outro ponto é que nossa aplicação deve rodar no mesmo terminal que as variáveis de ambiente forem definidas então para nosso caso, utilizando o PowerShell temos:

$Env:ConnectionStrings__DefaultConnect = "stringDeConexaoComOBD"
$Env:SMTP__Password ="123"
dotnet run

Melhores práticas para gerenciar segredos

  1. Não compartilhe segredos entre ambientes diferentes.
  2. Utilize serviços de gerenciamento de segredos, no meu caso optamos por utilizar o Azure Key Vault que possui fácil integração com o serviço de aplicativos da Azure no qual nossa aplicação está. Porém há outras alternativas da Amazon, Google e outras empresas.
  3. Mude os segredos periodicamente.

Conclusão

Um ponto fundamental para aplicações que são expostas para o público no geral é garantir a segurança da aplicação. Diversos outros aspectos além do citado aqui devem ser levados em conta, mas acredito que dentre eles o gerenciamento de segredos é um dos mais críticos.

Fonte : Secrets Management in .NET Applications (auth0.com)

É isso pessoal espero que o artigo tenha sido útil para vocês, ao menos pessoalmente era um ponto que eu não tinha noção de quão critico era e que é um problema que de certa forma pode ser facilmente resolvido.

Qualquer erro encontrado no artigo ou sugestão por favor compartilhem nos comentários!
Share
Comments (2)
Vanderson Pereira
Vanderson Pereira - 09/12/2021 22:44

Ótimo artigo cara! Parabens!!

Gilbert Lettiere
Gilbert Lettiere - 09/12/2021 22:09

Parabéns Vinícius pelo artigo. Muito bom. Esclarecedor. Bem escrito!