.NET Azure Segurança DevOps Cloud CI/CD

Gerenciamento de segredos no .NET com Azure Key Vault: nunca mais secrets no código

Guia completo para usar Azure Key Vault no .NET: integração com IConfiguration, identidade gerenciada, rotação automática de segredos.

N
Neryx Digital Architects
18 de setembro de 2025
12 min de leitura
200 profissionais leram
Categoria: .NET Público: Times de plataforma e operação Etapa: Decisão

Connection strings no appsettings.json, chaves de API em variáveis de ambiente, senhas hardcoded em código-fonte — esses problemas aparecem em praticamente todo projeto .NET que não tem uma estratégia de segredos. O Azure Key Vault resolve isso de forma centralizada, auditável e sem que nenhum desenvolvedor precise conhecer os valores reais em produção.

Por que variáveis de ambiente não bastam

Variáveis de ambiente são melhores que código-fonte, mas ainda têm problemas sérios em produção:

  • Visíveis para qualquer processo rodando na mesma máquina
  • Aparecem em dumps de processo e logs de sistema
  • Sem auditoria de acesso (quem leu qual segredo, quando)
  • Rotação exige redeploy da aplicação
  • Difíceis de revogar em caso de vazamento

O Key Vault resolve todos esses pontos: segredos ficam criptografados em repouso e em trânsito, acesso é auditado, rotação é automática e a aplicação busca os valores em runtime.

Configuração inicial

dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
dotnet add package Azure.Identity
// Program.cs — integração com IConfiguration
var builder = WebApplication.CreateBuilder(args);

// Adiciona Key Vault como fonte de configuração
if (!builder.Environment.IsDevelopment())
{
    var keyVaultUri = builder.Configuration["KeyVault:Uri"]
        ?? throw new InvalidOperationException("KeyVault:Uri não configurado");

    builder.Configuration.AddAzureKeyVault(
        new Uri(keyVaultUri),
        new DefaultAzureCredential()); // Usa identidade gerenciada em produção
}

// Em desenvolvimento, usa User Secrets (ver seção abaixo)
// Os segredos do Key Vault ficam disponíveis via IConfiguration
// ex: builder.Configuration["ConnectionStrings--Default"]
//     (hífens duplos = separador de seção)

Autenticação: Identidade Gerenciada (o jeito certo)

A autenticação com Key Vault nunca deve usar client secret ou certificado hardcoded. Use Managed Identity — a aplicação se autentica automaticamente usando sua identidade no Azure, sem nenhuma credencial no código:

# Habilitar System-Assigned Managed Identity na App Service / AKS
az webapp identity assign \
  --name minha-api \
  --resource-group meu-rg

# Obter o principal ID gerado
az webapp identity show \
  --name minha-api \
  --resource-group meu-rg \
  --query principalId

# Dar permissão de leitura de segredos ao identity
az keyvault set-policy \
  --name meu-keyvault \
  --object-id <principalId> \
  --secret-permissions get list

# Para Azure RBAC (preferível ao Key Vault Access Policies):
az role assignment create \
  --role "Key Vault Secrets User" \
  --assignee <principalId> \
  --scope /subscriptions/<subId>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vaultName>
// DefaultAzureCredential tenta múltiplas formas de autenticação na ordem:
// 1. EnvironmentCredential (CI/CD com variáveis AZURE_CLIENT_ID etc.)
// 2. WorkloadIdentityCredential (Kubernetes com AAD Pod Identity)
// 3. ManagedIdentityCredential (App Service, AKS, VM)
// 4. VisualStudioCredential (desenvolvedor autenticado no VS)
// 5. AzureCliCredential (desenvolvedor autenticado via az login)

builder.Configuration.AddAzureKeyVault(
    new Uri(keyVaultUri),
    new DefaultAzureCredential(new DefaultAzureCredentialOptions
    {
        // Especificar o Managed Identity client ID (se houver múltiplos)
        ManagedIdentityClientId = builder.Configuration["Azure:ManagedIdentityClientId"],

        // Excluir credenciais não aplicáveis ao ambiente para acelerar o fallback
        ExcludeSharedTokenCacheCredential = true,
        ExcludeVisualStudioCodeCredential = builder.Environment.IsProduction(),
        ExcludeAzureCliCredential = builder.Environment.IsProduction()
    }));

Nomenclatura de segredos: convenção para múltiplos ambientes

# Convenção no Key Vault (hífens em vez de : ou __)
# O provider converte automaticamente -- para : no IConfiguration

ConnectionStrings--Default          → ConnectionStrings:Default
ConnectionStrings--Redis            → ConnectionStrings:Redis
Jwt--SecretKey                      → Jwt:SecretKey
Jwt--Issuer                         → Jwt:Issuer
ExternalApis--PaymentGateway--ApiKey → ExternalApis:PaymentGateway:ApiKey
Brevo--ApiKey                       → Brevo:ApiKey

# Para múltiplos ambientes, use Key Vaults separados por ambiente:
# neryx-keyvault-dev
# neryx-keyvault-staging
# neryx-keyvault-prod
# Criar segredos via CLI
az keyvault secret set \
  --vault-name neryx-keyvault-prod \
  --name "ConnectionStrings--Default" \
  --value "Server=prod-db;Database=neryx;..."

az keyvault secret set \
  --vault-name neryx-keyvault-prod \
  --name "Jwt--SecretKey" \
  --value "$(openssl rand -base64 64)"

# Listar todos os segredos (sem os valores)
az keyvault secret list \
  --vault-name neryx-keyvault-prod \
  --query "[].name" -o table

Acessando segredos via IConfiguration e IOptions

// Os segredos do Key Vault ficam transparentes no IConfiguration
// Não há diferença no código entre segredos locais e do Key Vault

// Via IConfiguration diretamente
public class OrderService
{
    private readonly string _connectionString;

    public OrderService(IConfiguration configuration)
    {
        // Busca de appsettings.json em dev, Key Vault em produção
        _connectionString = configuration.GetConnectionString("Default")
            ?? throw new InvalidOperationException("ConnectionString não configurada");
    }
}

// Via IOptions (preferível para configurações estruturadas)
public class JwtSettings
{
    public string SecretKey { get; set; } = string.Empty;
    public string Issuer { get; set; } = string.Empty;
    public string Audience { get; set; } = string.Empty;
    public int ExpirationMinutes { get; set; } = 60;
}

// Program.cs
builder.Services.Configure<JwtSettings>(
    builder.Configuration.GetSection("Jwt"));

// Uso no serviço
public class TokenService
{
    private readonly JwtSettings _jwt;

    public TokenService(IOptions<JwtSettings> jwtOptions)
        => _jwt = jwtOptions.Value;

    public string GenerateToken(ClaimsIdentity identity)
    {
        var key = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(_jwt.SecretKey)); // Vem do Key Vault em produção
        // ...
    }
}

User Secrets em desenvolvimento (sem Key Vault)

# Inicializar User Secrets no projeto (armazena em ~/.microsoft/usersecrets/)
dotnet user-secrets init --project src/Api

# Adicionar segredos locais
dotnet user-secrets set "ConnectionStrings:Default" \
  "Host=localhost;Database=neryx_dev;Username=dev;Password=devpassword" \
  --project src/Api

dotnet user-secrets set "Jwt:SecretKey" \
  "dev-secret-key-nao-use-em-producao-32chars" \
  --project src/Api

# Listar segredos locais
dotnet user-secrets list --project src/Api

# No .csproj — User Secrets são carregados automaticamente em Development
# <UserSecretsId>guid-do-projeto</UserSecretsId>
// .gitignore — NUNCA commitar esses arquivos
appsettings.Development.json  // Se contiver segredos reais
*.pfx
*.p12
.env
secrets.json

Cache e prefixos de segredos

// Por padrão, o provider recarrega segredos periodicamente
// Configure o intervalo de reload e filtros para otimizar

builder.Configuration.AddAzureKeyVault(
    new Uri(keyVaultUri),
    new DefaultAzureCredential(),
    new AzureKeyVaultConfigurationOptions
    {
        // Recarrega segredos a cada 5 minutos (padrão: nunca)
        ReloadInterval = TimeSpan.FromMinutes(5),

        // Filtrar segredos por prefixo (útil quando o vault é compartilhado)
        Manager = new PrefixKeyVaultSecretManager("NeryxApi")
    });

// PrefixKeyVaultSecretManager filtra apenas segredos que começam com "NeryxApi--"
// e remove o prefixo ao mapear para IConfiguration
public class PrefixKeyVaultSecretManager : KeyVaultSecretManager
{
    private readonly string _prefix;

    public PrefixKeyVaultSecretManager(string prefix)
        => _prefix = $"{prefix}--";

    public override bool Load(SecretProperties secret)
        => secret.Name.StartsWith(_prefix, StringComparison.OrdinalIgnoreCase);

    public override string GetKey(KeyVaultSecret secret)
        => secret.Name[_prefix.Length..].Replace("--", ConfigurationPath.KeyDelimiter);
}

Rotação automática de segredos

# Criar nova versão de um segredo (rotação)
az keyvault secret set \
  --vault-name neryx-keyvault-prod \
  --name "ConnectionStrings--Default" \
  --value "Server=prod-db;Password=nova-senha-rotacionada;..."

# Desativar versão antiga após confirmar que a nova está funcionando
az keyvault secret set-attributes \
  --vault-name neryx-keyvault-prod \
  --name "ConnectionStrings--Default" \
  --version <versao-antiga> \
  --enabled false

# A aplicação buscará automaticamente a versão mais recente ativa
# no próximo ciclo de reload (ReloadInterval configurado acima)

GitHub Actions: usando Key Vault no CI/CD

# .github/workflows/deploy.yml
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # Necessário para OIDC com Azure
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Login no Azure via OIDC (sem client secret!)
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Buscar segredos do Key Vault para o pipeline
        uses: azure/get-keyvault-secrets@v1
        with:
          keyvault: neryx-keyvault-prod
          secrets: "ConnectionStrings--Default, Jwt--SecretKey"
        id: keyvault

      - name: Build e testes de integração com segredos reais
        env:
          ConnectionStrings__Default: ${{ steps.keyvault.outputs.ConnectionStrings--Default }}
          Jwt__SecretKey: ${{ steps.keyvault.outputs.Jwt--SecretKey }}
        run: dotnet test --configuration Release

      - name: Deploy para App Service
        uses: azure/webapps-deploy@v3
        with:
          app-name: neryx-api-prod
          # A App Service usa Managed Identity para acessar Key Vault em runtime
          # Não precisa passar segredos aqui

Auditoria de acesso

# Ver quem acessou quais segredos e quando
az monitor activity-log list \
  --resource-group meu-rg \
  --resource-type Microsoft.KeyVault/vaults \
  --resource neryx-keyvault-prod \
  --query "[?operationName.value=='Microsoft.KeyVault/vaults/secrets/read']" \
  --output table

# Configurar alertas para acesso suspeito (ex: muitos acessos em pouco tempo)
az monitor alert create \
  --name "KeyVault-HighAccess" \
  --resource neryx-keyvault-prod \
  --condition "count > 1000" \
  --window-size 5m \
  --evaluation-frequency 1m

Checklist de segurança de segredos

  • Nunca commitar segredos — use .gitignore + git-secrets ou gitleaks no CI
  • User Secrets em desenvolvimento — não appsettings.Development.json com senhas reais
  • Managed Identity em produção — nunca client secret ou certificado no código
  • Key Vaults separados por ambiente — dev/staging/prod com permissões diferentes
  • Princípio do menor privilégio — cada aplicação acessa apenas os segredos que precisa
  • Rotação automática — configure alertas 30 dias antes da expiração de certificados
  • Auditoria ativa — revise logs de acesso mensalmente
  • ReloadInterval configurado — para rotações sem redeploy

Vazamento de credenciais é uma das causas mais comuns de incidentes de segurança em produção. Se você quer implementar uma estratégia de segredos robusta na sua stack .NET e Azure, fale com a Neryx.

Quer sair do modo reativo e priorizar o que mais importa?

O diagnóstico de maturidade ajuda a transformar sintomas operacionais em um plano mais claro de evolução.

Avaliar maturidade

Newsletter

Receba artigos como este no seu e-mail

Conteúdo técnico sobre arquitetura de software, .NET, IA e gestão de produto. Sem spam.