Um Guia para Gerenciar Segredos no .NET e Azure DevOps

No mundo do desenvolvimento de software, uma das jornadas mais comuns é levar um projeto da nossa máquina local para o mundo, seja através de um repositório público no GitHub ou por meio de um pipeline de implantação contínua (CI/CD) no Azure DevOps. No entanto, precisamos ter uma atenção especial quando se trata de segurança. Este artigo nasce de uma experiência real e de um desafio que enfrentei recentemente: Como gerenciar um projeto .NET que usa chaves de API secretas, mantendo um repositório público limpo e, ao mesmo tempo, garantindo que o build automatizado no Azure DevOps funcione perfeitamente? Uma Chave Secreta no Lugar Errado Minha jornada começou com um projeto de estudos e meu primeiro contato com o Azure. Decidi utilizar o Service Bus para implementar uma funcionalidade de exclusão de usuários de forma assíncrona, via mensageria. Pense no Microsoft Azure Service Bus como um carteiro digital superconfiável. É um mediador de mensagens na nuvem que serve para desacoplar aplicações, permitindo que elas troquem dados de forma segura e assíncrona. Ele utiliza filas (para comunicação um-para-um, seguindo o padrão FIFO - "primeiro a entrar, primeiro a sair") e tópicos (para comunicação um-para-muitos). Para conectar minha aplicação a uma fila do Service Bus, o Azure me forneceu uma connection string — uma chave de acesso vital para o funcionamento do serviço. A solução mais óbvia? Colocá-la diretamente no arquivo de configuração appsettings.json. E foi aí que o dilema surgiu: como manter meu repositório público no GitHub sem expor essa chave? A primeira ideia que pode vir à mente é a "solução manual": deixar a chave no arquivo appsettings.json durante o desenvolvimento e removê-la antes de cada commit. Mas essa abordagem é uma receita para o desastre. Em um trabalho de equipe (ou mesmo sozinho, num dia de pressa), é quase certo que alguém esquecerá de remover o segredo. Uma vez vazado, teríamos que invalidar e regenerar todas as chaves expostas, sem contar que o segredo antigo permaneceria para sempre visível no histórico de commits. É para evitar essa armadilha que o ecossistema .NET nos oferece uma solução segura: o User Secrets. Nunca Comite Segredos O ponto de partida para qualquer projeto profissional é um princípio inegociável: nunca, jamais, armazene segredos diretamente no código-fonte. Segredos incluem: Chaves de API Connection Strings de bancos de dados ou serviços (como o Azure Service Bus) Senhas Tokens de autenticação Quando um segredo é "comitado" no Git, ele fica registrado no histórico do repositório para sempre. Mesmo que você o remova em um commit futuro, qualquer pessoa com acesso ao histórico pode encontrá-lo. Em um repositório público, isso é o mesmo que entregar as chaves do seu sistema para qualquer um. A solução não é criar lógicas complexas para enviar códigos diferentes para repositórios diferentes, mas sim externalizar a configuração, ou seja, separar o código da configuração sensível. A Segurança Local com User Secrets Para resolver o problema localmente, precisamos entender como o .NET gerencia as configurações. O sistema é baseado em uma hierarquia de "provedores" que alimentam uma interface central, a IConfiguration. Imagine que sua aplicação precisa de uma configuração. Ela pergunta à IConfiguration: "Qual é o valor para Settings:ServiceBus:DeleteUserAccount?". A IConfiguration, por sua vez, consulta seus provedores em uma ordem específica: Arquivos appsettings.json: Primeiro, ela olha o appsettings.json e depois o appsettings.Development.json (que o sobrepõe). User Secrets: Em seguida, ela consulta os segredos do usuário. Variáveis de Ambiente: Depois, verifica se existe uma variável de ambiente com esse nome. Linha de Comando: Por fim, olha os argumentos de linha de comando. A regra é simples: o último provedor a ser lido, vence. Se o User Secrets tiver um valor para a chave, ele irá sobrepor o que estiver no appsettings.Development.json. Setup Inicial Com base nessa hierarquia, podemos usar o appsettings.Development.json como um "molde" para as configurações que a aplicação precisa, mas deixando os valores sensíveis em branco. // Em appsettings.Development.json { "Settings": { "ServiceBus": { "DeleteUserAccount": "" // Valor deixado em branco de propósito } } } Movendo o Segredo para o User Secrets. A ferramenta User Secrets não criptografa os segredos, apenas os armazena em um arquivo secrets.json, localizado fora da pasta do seu projeto (geralmente no diretório de perfil do sistema operacional). Isso torna impossível que ele seja enviado acidentalmente para o Git. Passo 1: Habilitar o User Secrets No terminal, na raiz do seu projeto, execute: dotnet user-secrets init Esse comando adiciona uma tag ao seu arquivo .csproj. É esse ID único que conecta seu projeto ao arquivo secrets.json correspondente. Passo 2: Armazenar o Segredo Agora, vamos definir o va

Jun 19, 2025 - 16:40
 0
Um Guia para Gerenciar Segredos no .NET e Azure DevOps

No mundo do desenvolvimento de software, uma das jornadas mais comuns é levar um projeto da nossa máquina local para o mundo, seja através de um repositório público no GitHub ou por meio de um pipeline de implantação contínua (CI/CD) no Azure DevOps. No entanto, precisamos ter uma atenção especial quando se trata de segurança.
Este artigo nasce de uma experiência real e de um desafio que enfrentei recentemente: Como gerenciar um projeto .NET que usa chaves de API secretas, mantendo um repositório público limpo e, ao mesmo tempo, garantindo que o build automatizado no Azure DevOps funcione perfeitamente?

Uma Chave Secreta no Lugar Errado

Minha jornada começou com um projeto de estudos e meu primeiro contato com o Azure. Decidi utilizar o Service Bus para implementar uma funcionalidade de exclusão de usuários de forma assíncrona, via mensageria.

Pense no Microsoft Azure Service Bus como um carteiro digital superconfiável. É um mediador de mensagens na nuvem que serve para desacoplar aplicações, permitindo que elas troquem dados de forma segura e assíncrona. Ele utiliza filas (para comunicação um-para-um, seguindo o padrão FIFO - "primeiro a entrar, primeiro a sair") e tópicos (para comunicação um-para-muitos).

Para conectar minha aplicação a uma fila do Service Bus, o Azure me forneceu uma connection string — uma chave de acesso vital para o funcionamento do serviço. A solução mais óbvia? Colocá-la diretamente no arquivo de configuração appsettings.json.
E foi aí que o dilema surgiu: como manter meu repositório público no GitHub sem expor essa chave?
A primeira ideia que pode vir à mente é a "solução manual": deixar a chave no arquivo appsettings.json durante o desenvolvimento e removê-la antes de cada commit. Mas essa abordagem é uma receita para o desastre. Em um trabalho de equipe (ou mesmo sozinho, num dia de pressa), é quase certo que alguém esquecerá de remover o segredo. Uma vez vazado, teríamos que invalidar e regenerar todas as chaves expostas, sem contar que o segredo antigo permaneceria para sempre visível no histórico de commits.
É para evitar essa armadilha que o ecossistema .NET nos oferece uma solução segura: o User Secrets.

Nunca Comite Segredos

O ponto de partida para qualquer projeto profissional é um princípio inegociável: nunca, jamais, armazene segredos diretamente no código-fonte.
Segredos incluem:

  • Chaves de API
  • Connection Strings de bancos de dados ou serviços (como o Azure Service Bus)
  • Senhas
  • Tokens de autenticação

Quando um segredo é "comitado" no Git, ele fica registrado no histórico do repositório para sempre. Mesmo que você o remova em um commit futuro, qualquer pessoa com acesso ao histórico pode encontrá-lo. Em um repositório público, isso é o mesmo que entregar as chaves do seu sistema para qualquer um.
A solução não é criar lógicas complexas para enviar códigos diferentes para repositórios diferentes, mas sim externalizar a configuração, ou seja, separar o código da configuração sensível.

A Segurança Local com User Secrets

Para resolver o problema localmente, precisamos entender como o .NET gerencia as configurações. O sistema é baseado em uma hierarquia de "provedores" que alimentam uma interface central, a IConfiguration.
Imagine que sua aplicação precisa de uma configuração. Ela pergunta à IConfiguration: "Qual é o valor para Settings:ServiceBus:DeleteUserAccount?". A IConfiguration, por sua vez, consulta seus provedores em uma ordem específica:

  1. Arquivos appsettings.json: Primeiro, ela olha o appsettings.json e depois o appsettings.Development.json (que o sobrepõe).
  2. User Secrets: Em seguida, ela consulta os segredos do usuário.
  3. Variáveis de Ambiente: Depois, verifica se existe uma variável de ambiente com esse nome.
  4. Linha de Comando: Por fim, olha os argumentos de linha de comando.

A regra é simples: o último provedor a ser lido, vence. Se o User Secrets tiver um valor para a chave, ele irá sobrepor o que estiver no appsettings.Development.json.

Setup Inicial

Com base nessa hierarquia, podemos usar o appsettings.Development.json como um "molde" para as configurações que a aplicação precisa, mas deixando os valores sensíveis em branco.

// Em appsettings.Development.json
{
  "Settings": {
    "ServiceBus": {
      "DeleteUserAccount": "" // Valor deixado em branco de propósito
    }
  }
}

Movendo o Segredo para o User Secrets.

A ferramenta User Secrets não criptografa os segredos, apenas os armazena em um arquivo secrets.json, localizado fora da pasta do seu projeto (geralmente no diretório de perfil do sistema operacional). Isso torna impossível que ele seja enviado acidentalmente para o Git.
Passo 1: Habilitar o User Secrets
No terminal, na raiz do seu projeto, execute:

dotnet user-secrets init

Esse comando adiciona uma tag ao seu arquivo .csproj. É esse ID único que conecta seu projeto ao arquivo secrets.json correspondente.

Passo 2: Armazenar o Segredo
Agora, vamos definir o valor da nossa connection string. Usamos dois-pontos (:) para navegar na hierarquia do JSON:

dotnet user-secrets set "Settings:ServiceBus:DeleteUserAccount" "SuaConnectionStringSuperSecretaAqui"

E pronto! Quando você rodar sua aplicação em modo de desenvolvimento, o .NET irá ler o valor vazio do appsettings.Development.json e, em seguida, o sobreporá com o valor real guardado no User Secrets. Problema local resolvido de forma segura e elegante.

Testando a Portabilidade

E se outro desenvolvedor baixar o projeto? Ao fazer git pull em uma nova máquina, todo o código virá, incluindo o .csproj com o UserSecretsId. No entanto, o arquivo secrets.json não virá, pois ele é local ao primeiro computador.
Ao tentar rodar o projeto, ele falhará por não encontrar a chave. Este é um comportamento esperado e desejado, pois prova que o sistema é seguro. A solução é simples: cada desenvolvedor, em cada nova máquina, precisa configurar o segredo localmente apenas uma vez, executando o comando dotnet user-secrets set....

Automatizando com Segurança no Azure DevOps

Logo do Azure Devops
Com o desenvolvimento local resolvido, é hora de automatizar o processo de build e teste com um pipeline de CI/CD.

Conceitos Fundamentais de CI/CD

Antes de criar o pipeline, vamos entender os blocos de construção do Azure DevOps:

  • CI/CD (Integração/Implantação Contínua): É a prática de automatizar as fases do desenvolvimento. A CI garante que o novo código de vários desenvolvedores seja integrado e testado automaticamente. A CD leva essa automação adiante, publicando o projeto em um ambiente (teste ou produção). O objetivo é entregar valor de forma mais rápida e confiável.
  • Pipeline: É o fluxo de trabalho que define todas as etapas da sua automação. Ele diz ao Azure DevOps o que fazer e em que ordem.
  • Agent (Agente): É a máquina (geralmente virtual e temporária, fornecida pela Microsoft) que executa os passos do seu pipeline.
  • Job (Trabalho): Um conjunto de passos que rodam juntos em um mesmo agente.
  • Task (Tarefa): A menor unidade de trabalho. É uma ação individual e pré-empacotada, como dotnet build, dotnet test ou a execução de um script.

Criando um Pipeline Simples com o Editor Clássico

No Azure DevOps, você pode criar um pipeline usando um arquivo YAML (código) ou o Editor Clássico (interface gráfica). Para quem está começando, o Editor Clássico é uma ótima porta de entrada. O processo geralmente segue estes passos:

  1. Conexão: Você aponta o pipeline para o seu repositório de código.
  2. Template: Você escolhe um template inicial. Para um projeto .NET, o template "ASP.NET Core" já adiciona as tarefas essenciais:
    • dotnet restore (baixa as dependências)
    • dotnet build (compila o código)
    • dotnet test (executa os testes)
    • dotnet publish (prepara os arquivos para implantação)
  3. Execução: Você salva e executa o pipeline, que rodará em um agente na nuvem.

O Desafio e a Solução no Pipeline

É aqui que um novo problema surge. Ao executar o pipeline, ele falha com um erro genérico: The process 'C:\Program Files\dotnet\dotnet.exe' failed with exit code 1.
Após investigar os logs, a causa fica clara: os testes estão falhando. Assim como a "nova máquina" do exemplo anterior, o agente de build do Azure DevOps é um ambiente limpo. Ele não tem acesso aos seus User Secrets. Ao executar os testes que dependem da connection string do Service Bus, o valor está vazio, causando a falha que interrompe todo o pipeline.
A solução é fornecer o segredo de forma segura durante a execução.

Variáveis de Pipeline Seguras

Primeiro, armazenamos o segredo de forma centralizada e segura no próprio pipeline.

  1. No modo de edição do pipeline, vá para a aba Variables.
  2. Crie uma Pipeline Variable com um nome claro, como ServiceBusConnectionString.
  3. Cole o valor da sua chave e, o mais importante, clique no ícone de cadeado para marcar o valor como secreto. Ele será mascarado e não aparecerá nos logs.
  4. Salve as variáveis.

Injetando Segredos com PowerShell

A solução universal e mais robusta é usar uma tarefa de PowerShell para definir a variável de ambiente.

  1. Adicione a Tarefa: Na aba Tasks, adicione uma nova tarefa "PowerShell" à sua lista.
  2. Posicione Corretamente: Arraste a tarefa PowerShell para que ela seja executada imediatamente antes da sua tarefa de dotnet test. A ordem é crucial.
  3. Configure o Script:

    • Clique na tarefa PowerShell e defina o Type como Inline.
    • No campo Script, insira o seguinte comando:

      Write-Host "Setting the Service Bus connection string as an environment variable."
      Write-Host "##vso[task.setvariable variable=Settings__ServiceBus__DeleteUserAccount]$(ServiceBusConnectionString)"
      

Este comando ##vso[task.setvariable...] é uma instrução especial para o agente do Azure DevOps, ordenando que ele crie uma variável de ambiente (Settings__ServiceBus__DeleteUserAccount) com o valor da nossa variável de pipeline segura ($(ServiceBusConnectionString)).
A tarefa de teste, ao ser executada logo em seguida, herdará essa variável de ambiente. O sistema de configuração do .NET a detectará, seus testes encontrarão a chave necessária e o pipeline será executado com sucesso!

Azure Key Vault

Embora as variáveis de pipeline sejam ótimas para CI/CD, ambientes de produção exigem uma camada extra de segurança e gerenciamento. Para isso, a ferramenta definitiva é o Azure Key Vault.
Pense no Key Vault como um cofre centralizado e ultra-seguro na nuvem. Em vez de armazenar segredos no pipeline, sua aplicação, ao ser executada em produção, se autentica de forma segura no Key Vault para buscar as configurações de que precisa. Isso oferece vantagens imensas, como gerenciamento centralizado de todos os segredos, políticas de acesso granulares, rotação automática de chaves e logs de auditoria detalhados.

Conclusão

  1. Separe Código e Configuração: O código deve ser o mesmo em todos os ambientes; a configuração sensível deve ser externa e específica de cada ambiente.
  2. Trate Cada Ambiente de Forma Independente: O modo como você fornece um segredo para sua máquina local é diferente de como você o faz para um pipeline de CI ou para o ambiente de produção.
  3. Use a Ferramenta Certa para o Trabalho: O ecossistema .NET e o Azure nos fornecem as ferramentas ideais para cada cenário: User Secrets para desenvolvimento, Variáveis de Pipeline para automação e Azure Key Vault para produção.

Ao seguir esses princípios, você garante que seu projeto não apenas funcione, mas que seja seguro. Para mim, essa jornada que começou com um simples connection string foi um aprendizado imenso, e espero que compartilhá-la possa ajudar outros desenvolvedores a trilhar um caminho mais seguro.
Para referência, o código-fonte do projeto que inspirou este artigo pode ser encontrado neste link do GitHub.