Tirar segredos do Git em GitOps
GitOps exige que o estado desejado esteja no Git. Segredo não pode estar. O External Secrets Operator resolve a contradição: o repositório guarda uma referência, e o operador busca o valor no cofre e mantém o Secret sincronizado no cluster.
1. Entenda por que segredo cifrado no Git incomoda
Seção intitulada “1. Entenda por que segredo cifrado no Git incomoda”Sealed Secrets e SOPS funcionam e são melhores que texto em claro. Mas:
- O ciphertext fica no histórico para sempre; se a chave vazar depois, todo o histórico é legível.
- Rotacionar exige commit, PR e deploy — o que faz ninguém rotacionar.
- A auditoria de “quem leu este segredo” não existe.
- Recuperação de desastre depende de guardar bem a chave mestra, que vira o novo problema.
Com cofre + operador, você ganha rotação sem commit, trilha de auditoria por acesso e controle de permissão centralizado.
2. Instale o operador
Seção intitulada “2. Instale o operador”helm repo add external-secrets https://charts.external-secrets.iohelm upgrade --install external-secrets external-secrets/external-secrets \ -n external-secrets --create-namespace --version 0.10.4
kubectl get pods -n external-secrets3. Conecte ao cofre, com identidade de workload
Seção intitulada “3. Conecte ao cofre, com identidade de workload”O ponto crítico: não use uma credencial estática para o operador falar com o cofre — isso só mudaria o segredo de lugar. Use a identidade da ServiceAccount.
# AWS Secrets Manager via IRSAapiVersion: external-secrets.io/v1kind: ClusterSecretStoremetadata: { name: aws-prod }spec: provider: aws: service: SecretsManager region: sa-east-1 auth: jwt: serviceAccountRef: { name: external-secrets, namespace: external-secrets }# Vault com autenticação KubernetesapiVersion: external-secrets.io/v1kind: ClusterSecretStoremetadata: { name: vault-prod }spec: provider: vault: server: https://vault.exemplo.com.br path: kv version: v2 auth: kubernetes: mountPath: kubernetes role: external-secrets serviceAccountRef: { name: external-secrets, namespace: external-secrets }kubectl get clustersecretstore aws-prod -o jsonpath='{.status.conditions}' | jq# Ready=True antes de seguir4. Referencie o segredo no manifest versionado
Seção intitulada “4. Referencie o segredo no manifest versionado”Este arquivo vai para o Git. Ele não contém valor nenhum.
apiVersion: external-secrets.io/v1kind: ExternalSecretmetadata: { name: loja-db, namespace: loja }spec: refreshInterval: 1h secretStoreRef: { name: aws-prod, kind: ClusterSecretStore } target: name: loja-db # Secret criado no cluster creationPolicy: Owner # apagou o ExternalSecret, apaga o Secret template: type: Opaque data: # monte a string de conexão sem expor as partes DATABASE_URL: "postgres://{{ .usuario }}:{{ .senha }}@postgres.loja:5432/pedidos" data: - secretKey: usuario remoteRef: { key: prod/loja/postgres, property: usuario } - secretKey: senha remoteRef: { key: prod/loja/postgres, property: senha }kubectl get externalsecret loja-db -n loja # SYNCED=Truekubectl get secret loja-db -n loja # criado pelo operador, não pelo Git5. Rotacione sem redeploy
Seção intitulada “5. Rotacione sem redeploy”Rotacionar passa a ser uma operação no cofre. O operador propaga em até refreshInterval.
aws secretsmanager put-secret-value --secret-id prod/loja/postgres \ --secret-string '{"usuario":"loja","senha":"NOVA_SENHA"}'Falta um detalhe que costuma ser esquecido: a aplicação precisa reler o valor. Variável de ambiente é lida uma vez no boot. Duas saídas:
# 1. o Reloader (ou equivalente) reinicia o Deployment quando o Secret mudametadata: annotations: { reloader.stakater.com/auto: "true" }# 2. melhor: monte como arquivo e faça a aplicação relervolumeMounts: [{ name: credenciais, mountPath: /etc/segredos, readOnly: true }]volumes: [{ name: credenciais, secret: { secretName: loja-db } }]Secret montado como volume é atualizado no Pod sem reinício — mas a aplicação precisa reabrir o arquivo.
6. Feche o resto
Seção intitulada “6. Feche o resto”O operador tira o segredo do Git; ele não protege o cluster sozinho:
- RBAC: negue
get secretspor padrão; quem lê o Secret tem a senha do banco. - Criptografia em repouso do etcd, com KMS externo.
- Cofre com permissão mínima: o
ClusterSecretStorede produção não deve enxergar os caminhos de outros ambientes; considere umSecretStorepor namespace. - Auditoria: alerta em leituras fora do padrão no cofre.
Se der errado
Seção intitulada “Se der errado”| Sintoma | Causa provável |
|---|---|
SecretSyncedError |
Caminho ou property errado no remoteRef |
InvalidProviderConfig |
ServiceAccount sem permissão no cofre (IRSA/role) |
| Secret criado vazio | property inexistente no JSON armazenado |
| Aplicação usa a senha antiga | Faltou reiniciar ou reler o arquivo |
Argo CD marca OutOfSync |
O Secret gerado não está no Git: ignore-o na diff |
| Segredo some ao remover o manifest | creationPolicy: Owner — é o comportamento esperado |
Checklist de pronto
Seção intitulada “Checklist de pronto”- Operador instalado e
ClusterSecretStorecomReady=True. - Autenticação por identidade de workload, sem credencial estática.
- Nenhum valor de segredo no repositório (varredura confirma).
-
ExternalSecretsincronizando, comrefreshIntervaldefinido. - Estratégia de recarga da aplicação escolhida e testada.
- RBAC restringindo leitura de Secrets.
- Rotação exercitada de ponta a ponta.