O segredo que ninguém quer rotacionar
TL;DR: Este artigo analisa a implementação do Workload Identity Federation (WIF) para substituir chaves de acesso estáticas (ARM_CLIENT_SECRET) por tokens de curta duração em pipelines de CI/CD no Azure. A conclusão é que o uso de Managed Identities federadas elimina o risco de vazamento de credenciais, reduz o overhead operacional de rotação de segredos e garante uma segmentação RBAC mais precisa, sendo uma recomendação de segurança essencial para qualquer infraestrutura cloud moderna.
A maioria dos nossos clientes ainda autentica pipelines de Terraform usando o mesmo método de três anos atrás: um ARM_CLIENT_SECRET de longa duração armazenado no GitHub Actions ou Azure DevOps. Você insere uma vez, copia para lá e para cá, e só rotaciona quando o sistema quebra.
Esta é a credencial mais ignorada da nuvem e, estatisticamente, a mais propensa a vazamentos. Um desenvolvedor tira um print do grupo de variáveis, um log de pipeline ecoa o valor, um fork herda o segredo ou, pior, a chave expira numa sexta-feira à noite, paralisando os deployments de produção.
O Workload Identity Federation (WIF) resolve esse problema. O pipeline gera um token de curta duração em tempo de execução, troca por um access token do Azure via Microsoft Entra ID e, o mais importante, nunca toca em um segredo. GitHub Actions suporta WIF desde 2021; Azure DevOps, desde fevereiro de 2024. O provider azurerm do Terraform já está pronto para isso desde a v3.7.
Como a troca funciona na prática?
Antes de olhar o YAML, entenda o fluxo lógico:
- O sistema de CI (GitHub/ADO) assina um JWT de curta duração descrevendo exatamente o que está rodando: repositório, branch, ambiente e service connection.
- O pipeline envia esse JWT para o Microsoft Entra ID.
- O Entra verifica o payload contra uma federated identity credential configurada em uma managed identity ou app registration. As claims
iss,subeauddevem corresponder exatamente. - Se tudo validar, o Entra retorna um Azure access token válido apenas para a duração daquele trabalho.
- O Terraform utiliza o token, o job termina e o acesso expira. Nada persiste.
O token é vinculado a um subject específico (ex: repo:contoso/platform:environment:prod). Ele não pode ser reutilizado por outro repo, branch ou pipeline.
Arquitetura recomendada
Abaixo, as escolhas que funcionam melhor em produção:
| Decisão | Escolha |
|---|---|
| Tipo de identidade | User-assigned managed identity (UAMI) |
| Granularidade | Uma UAMI por ambiente (não por pipeline) |
| Escopo de confiança | Pinned ao claim environment |
| Escopo RBAC | Resource group, não subscription |
| Remote state | OIDC + use_azuread_auth = true, sem chaves compartilhadas |
Part 1 - GitHub Actions
Passo 1: Criar a identidade e federar
Dois comandos por ambiente. Só isso.
az identity create -g rg-platform-identity -n id-tf-prod -l eastus
az identity federated-credential create \
--name github-prod \
--identity-name id-tf-prod \
--resource-group rg-platform-identity \
--issuer https://token.actions.githubusercontent.com \
--subject repo:contoso/platform:environment:prod \
--audiences api://AzureADTokenExchange
Passo 2: Configurar o GitHub
Em Settings → Environments, crie os ambientes nonprod e prod. Adicione três variáveis de ambiente (não secrets!): AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID.
O workflow:
permissions:
id-token: write
contents: read
jobs:
apply:
runs-on: ubuntu-latest
environment: prod
env:
ARM_USE_OIDC: "true"
ARM_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}
ARM_TENANT_ID: ${{ vars.AZURE_TENANT_ID }}
ARM_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }}
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init && terraform apply -auto-approve
Part 2 - Azure DevOps
A lógica é igual, mas a mecânica muda. Para times de plataforma, a via manual + UAMI é superior para governança.
- No ADO, crie uma nova ARM service connection → Workload Identity Federation (manual). Salve como draft.
- No Azure, adicione a federated credential na UAMI com os valores informados pelo ADO.
- Em pipelines, use a tarefa
AzureCLI@2para carregar a conexão.
Cuidados com o State File
O arquivo de estado é o seu blast radius. Com OIDC, você pode bloquear o storage sem usar access keys:
backend "azurerm" {
resource_group_name = "rg-tfstate"
storage_account_name = "sttfstateprodeastus"
container_name = "platform-prod"
key = "platform.tfstate"
use_oidc = true
use_azuread_auth = true
}
Dê à UAMI a role Storage Blob Data Contributor apenas no container e desabilite o shared key access na conta de armazenamento.
Migração sem janela de manutenção
Você não precisa de um hard cutover:
- Crie a nova UAMI com as mesmas permissões do SP antigo.
- Federe um pipeline "canário" e valide.
- Migre os outros ambientes em ondas.
- Após um ciclo de release estável, desabilite o secret antigo do SP.
- Remova o SP após o segundo ciclo.
Perguntas Frequentes
-
Por que abandonar o uso de ARM_CLIENT_SECRET?
Credenciais longas são o vetor de ataque mais provável em pipelines: podem vazar em logs, screenshots ou ser expostas em forks. Além disso, a rotação manual é um processo falho que gera interrupções operacionais. -
O que o Workload Identity Federation (WIF) resolve na prática?
O WIF permite que o pipeline solicite tokens de curta duração em runtime via Microsoft Entra ID. Isso elimina a necessidade de armazenar qualquer segredo fixo no CI/CD, garantindo que o acesso expire automaticamente após o job. -
Qual a recomendação de arquitetura para identidades no Azure?
Utilize User-assigned managed identities (UAMIs) — uma por ambiente — em vez de App Registrations genéricas. Isso alinha a identidade ao ciclo de vida do recurso e simplifica a governança de permissões. -
É possível realizar essa migração sem downtime?
Sim. Você pode manter o Service Principal antigo e o novo UAMI ativos simultaneamente, migrando os pipelines em ondas de menor risco e realizando o corte definitivo apenas após validar a estabilidade dos fluxos.
Artigo originalmente publicado em Azure Updates - Latest from Azure Charts.