Docker & Kubernetes

Guia Completo de Gerenciamento de Secrets em Kubernetes: Vault Agent e External Secrets

12 min de leitura

Guia Completo de Gerenciamento de Secrets em Kubernetes: Vault Agent e External Secrets

O Problema: Por Que Gerenciar Secrets em Kubernetes? Quando você trabalha com Kubernetes em produção, rapidamente percebe que armazenar senhas, chaves de API e certificados diretamente no código ou em ConfigMaps é um risco de segurança inaceitável. O Kubernetes oferece o recurso nativo de Secrets, mas este é apenas o ponto de partida: os dados são armazenados em base64 (não é criptografia real) no etcd, e qualquer pessoa com acesso ao cluster pode decodificar facilmente. Empresas sérias precisam de uma solução que: rotacione secrets automaticamente, mantenha um histórico de auditoria, integre-se com diversos provedores (AWS Secrets Manager, Azure Key Vault, Google Secret Manager), e injete credenciais de forma segura nas aplicações sem que elas precisem conhecer detalhes de conexão. É aqui que entram HashiCorp Vault Agent e External Secrets Operator — duas abordagens diferentes para o mesmo problema crítico. Entendendo as Duas Abordagens HashiCorp Vault Agent: Injeção via Sidecar O Vault Agent funciona como um sidecar (container adicional no Pod)

<h2>O Problema: Por Que Gerenciar Secrets em Kubernetes?</h2>

<p>Quando você trabalha com Kubernetes em produção, rapidamente percebe que armazenar senhas, chaves de API e certificados diretamente no código ou em ConfigMaps é um risco de segurança inaceitável. O Kubernetes oferece o recurso nativo de Secrets, mas este é apenas o ponto de partida: os dados são armazenados em base64 (não é criptografia real) no etcd, e qualquer pessoa com acesso ao cluster pode decodificar facilmente.</p>

<p>Empresas sérias precisam de uma solução que: rotacione secrets automaticamente, mantenha um histórico de auditoria, integre-se com diversos provedores (AWS Secrets Manager, Azure Key Vault, Google Secret Manager), e injete credenciais de forma segura nas aplicações sem que elas precisem conhecer detalhes de conexão. É aqui que entram <strong>HashiCorp Vault Agent</strong> e <strong>External Secrets Operator</strong> — duas abordagens diferentes para o mesmo problema crítico.</p>

<h2>Entendendo as Duas Abordagens</h2>

<h3>HashiCorp Vault Agent: Injeção via Sidecar</h3>

<p>O Vault Agent funciona como um sidecar (container adicional no Pod) que se conecta ao Vault, autentica-se e injeta secrets diretamente no filesystem do seu container principal. A aplicação lê o arquivo gerado, não precisa conhecer Vault e os secrets nunca passam pelas variáveis de ambiente (que são visíveis em logs de diagnóstico).</p>

<p>O fluxo é simples: Vault Agent acorda periodicamente, valida sua autenticação com o Vault, busca os secrets atualizados e reescreve os arquivos. A aplicação pode ser notificada via signal para recarregar as credenciais. Isso é especialmente útil se você já usa Vault internamente ou precisa de rotação muito frequente.</p>

<h3>External Secrets Operator: Sincronização de Secrets Nativos</h3>

<p>O External Secrets Operator (ESO) é um controller Kubernetes que cria Secrets nativos do Kubernetes sincronizados com um backend externo. Você define um recurso YAML chamado <code>SecretStore</code> (ou <code>ClusterSecretStore</code>) que aponta para seu provedor (Vault, AWS Secrets Manager, etc.), e então cria um <code>ExternalSecret</code> que especifica quais secrets buscar. O ESO faz o trabalho pesado e mantém o Secret do Kubernetes sempre sincronizado.</p>

<p>A vantagem aqui é que sua aplicação continua usando Secrets normais do Kubernetes — sem mudança de código. É mais &quot;nativo&quot; do ecossistema, mas você perde o controle fino que o Vault Agent oferece em rotações muito rápidas. External Secrets é ideal quando você quer abstrair o backend sem mudar sua aplicação.</p>

<h2>Vault Agent: Implementação Passo a Passo</h2>

<h3>Instalando e Configurando o Vault</h3>

<p>Primeiro, você precisa de um Vault rodando. Para desenvolvimento, pode ser local:</p>

<pre><code class="language-bash"># Instalar Vault (macOS com Homebrew)

brew install vault

Iniciar servidor em modo desenvolvimento

vault server -dev

Em outro terminal, exportar a variável de ambiente

export VAULT_ADDR=&#039;http://127.0.0.1:8200&#039;

export VAULT_TOKEN=&#039;s.xxxxxxxxxxxxxxxx&#039; # Token exibido acima</code></pre>

<p>Agora crie um secret no Vault:</p>

<pre><code class="language-bash"># Habilitar o backend KV v2

vault secrets enable -path=secret kv-v2

Armazenar um secret

vault kv put secret/myapp/database \

username=dbuser \

password=supersecret123 \

host=postgres.default.svc.cluster.local \

port=5432</code></pre>

<h3>Configurar Autenticação Kubernetes no Vault</h3>

<p>Para que Vault Agent autentique Pods automaticamente, configure a autenticação Kubernetes:</p>

<pre><code class="language-bash"># Habilitar o método de autenticação kubernetes

vault auth enable kubernetes

Configurar o kubernetes auth

vault write auth/kubernetes/config \

kubernetes_host=&quot;https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT&quot; \

kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \

token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token

Criar uma policy para o app

vault policy write myapp-policy - &lt;&lt;EOF

path &quot;secret/data/myapp/*&quot; {

capabilities = [&quot;read&quot;, &quot;list&quot;]

}

EOF

Criar um role Kubernetes

vault write auth/kubernetes/role/myapp \

bound_service_account_names=myapp \

bound_service_account_namespaces=default \

policies=myapp-policy \

ttl=24h</code></pre>

<h3>Declarar o Pod com Vault Agent</h3>

<p>Agora configure seu Pod para usar Vault Agent. O Vault Agent é injetado via mutating webhook ou manualmente:</p>

<pre><code class="language-yaml">apiVersion: v1

kind: ServiceAccount

metadata:

name: myapp

namespace: default

---

apiVersion: v1

kind: Pod

metadata:

name: myapp

namespace: default

annotations:

vault.hashicorp.com/agent-inject: &quot;true&quot;

vault.hashicorp.com/role: &quot;myapp&quot;

vault.hashicorp.com/agent-inject-secret-database: &quot;secret/data/myapp/database&quot;

vault.hashicorp.com/agent-inject-template-database: |

{{- with secret &quot;secret/data/myapp/database&quot; -}}

export DB_USER=&quot;{{ .Data.data.username }}&quot;

export DB_PASSWORD=&quot;{{ .Data.data.password }}&quot;

export DB_HOST=&quot;{{ .Data.data.host }}&quot;

export DB_PORT=&quot;{{ .Data.data.port }}&quot;

{{- end }}

spec:

serviceAccountName: myapp

containers:

  • name: myapp

image: myapp:latest

command: [&quot;/bin/sh&quot;, &quot;-c&quot;]

args:

- |

source /vault/secrets/database

exec python3 app.py

ports:

  • containerPort: 8080</code></pre>

<p>A anotação <code>vault.hashicorp.com/agent-inject-secret-database</code> diz ao Vault Agent qual secret buscar. O campo <code>agent-inject-template-database</code> define como formatar o output (pode ser JSON, variáveis de ambiente, arquivo de configuração, etc.).</p>

<h3>Aplicação Python Consumindo o Secret</h3>

<p>Sua aplicação não precisa saber que veio do Vault — apenas lê variáveis de ambiente:</p>

<pre><code class="language-python">import os

import psycopg2

from flask import Flask

app = Flask(__name__)

Variáveis injetadas pelo Vault Agent

DB_USER = os.getenv(&#039;DB_USER&#039;)

DB_PASSWORD = os.getenv(&#039;DB_PASSWORD&#039;)

DB_HOST = os.getenv(&#039;DB_HOST&#039;)

DB_PORT = int(os.getenv(&#039;DB_PORT&#039;, 5432))

def get_db_connection():

conn = psycopg2.connect(

host=DB_HOST,

user=DB_USER,

password=DB_PASSWORD,

port=DB_PORT,

database=&#039;mydb&#039;

)

return conn

@app.route(&#039;/health&#039;)

def health():

try:

conn = get_db_connection()

conn.close()

return {&#039;status&#039;: &#039;healthy&#039;}, 200

except Exception as e:

return {&#039;status&#039;: &#039;unhealthy&#039;, &#039;error&#039;: str(e)}, 500

if __name__ == &#039;__main__&#039;:

app.run(host=&#039;0.0.0.0&#039;, port=8080)</code></pre>

<h2>External Secrets Operator: Implementação Passo a Passo</h2>

<h3>Instalação do ESO</h3>

<p>External Secrets Operator é instalado como um Helm chart:</p>

<pre><code class="language-bash"># Adicionar repositório Helm

helm repo add external-secrets https://charts.external-secrets.io

helm repo update

Instalar o operador

helm install external-secrets \

external-secrets/external-secrets \

-n external-secrets-system \

--create-namespace \

--set installCRDs=true</code></pre>

<h3>Configurar SecretStore (Backend Vault)</h3>

<p>Crie um <code>SecretStore</code> que aponta para seu Vault:</p>

<pre><code class="language-yaml">apiVersion: external-secrets.io/v1beta1

kind: SecretStore

metadata:

name: vault-backend

namespace: default

spec:

provider:

vault:

server: &quot;http://vault.vault.svc.cluster.local:8200&quot;

path: &quot;secret&quot;

version: &quot;v2&quot;

auth:

kubernetes:

mountPath: &quot;kubernetes&quot;

role: &quot;myapp&quot;

serviceAccountRef:

name: myapp</code></pre>

<p>Se estiver usando AWS Secrets Manager em vez de Vault:</p>

<pre><code class="language-yaml">apiVersion: external-secrets.io/v1beta1

kind: SecretStore

metadata:

name: aws-backend

namespace: default

spec:

provider:

aws:

service: SecretsManager

region: us-east-1

auth:

jwt:

serviceAccountRef:

name: myapp</code></pre>

<h3>Criar um ExternalSecret</h3>

<p>Agora defina qual secret deve ser sincronizado:</p>

<pre><code class="language-yaml">apiVersion: external-secrets.io/v1beta1

kind: ExternalSecret

metadata:

name: myapp-database

namespace: default

spec:

refreshInterval: 1h

secretStoreRef:

name: vault-backend

kind: SecretStore

target:

name: myapp-database-secret

creationPolicy: Owner

template:

engineVersion: v2

data:

database.conf: |

DB_USER={{ .username }}

DB_PASSWORD={{ .password }}

DB_HOST={{ .host }}

DB_PORT={{ .port }}

data:

  • secretKey: username

remoteRef:

key: myapp/database

property: username

  • secretKey: password

remoteRef:

key: myapp/database

property: password

  • secretKey: host

remoteRef:

key: myapp/database

property: host

  • secretKey: port

remoteRef:

key: myapp/database

property: port</code></pre>

<p>O ESO lê essas configurações, conecta ao Vault usando a ServiceAccount <code>myapp</code>, e cria um Secret nativo do Kubernetes chamado <code>myapp-database-secret</code>. Cada 1 hora (refreshInterval), ele sincroniza novamente.</p>

<h3>Consumir o Secret no Pod</h3>

<p>Como é um Secret nativo, sua aplicação usa normalmente:</p>

<pre><code class="language-yaml">apiVersion: v1

kind: Pod

metadata:

name: myapp

namespace: default

spec:

serviceAccountName: myapp

containers:

  • name: myapp

image: myapp:latest

command: [&quot;/bin/sh&quot;, &quot;-c&quot;]

args:

- |

cat /etc/secrets/database.conf

exec python3 app.py

volumeMounts:

  • name: db-secret

mountPath: /etc/secrets

readOnly: true

volumes:

  • name: db-secret

secret:

secretName: myapp-database-secret</code></pre>

<p>A aplicação lê do arquivo montado em <code>/etc/secrets/database.conf</code>, exatamente como faria com qualquer Secret do Kubernetes.</p>

<h2>Comparação e Escolha da Abordagem</h2>

<div class="table-wrap"><table><thead><tr><th>Aspecto</th><th>Vault Agent</th><th>External Secrets</th></tr></thead><tbody><tr><td><strong>Curva de aprendizado</strong></td><td>Mais alta (Vault é complexo)</td><td>Mais baixa (integrado com Kubernetes)</td></tr><tr><td><strong>Rotação de secrets</strong></td><td>Muito rápida (minutos)</td><td>Mais lenta (depende de refreshInterval)</td></tr><tr><td><strong>Mudança na aplicação</strong></td><td>Nenhuma (lê arquivos ou env vars)</td><td>Nenhuma (usa Secrets nativos)</td></tr><tr><td><strong>Compatibilidade</strong></td><td>Apenas com Vault</td><td>Vault, AWS, Azure, Google, etc.</td></tr><tr><td><strong>Auditoria</strong></td><td>Excellent (logs do Vault)</td><td>Boa (logs do ESO + backend)</td></tr><tr><td><strong>Consumo de recursos</strong></td><td>Baixo (sidecar leve)</td><td>Médio (controller + watchers)</td></tr></tbody></table></div>

<p><strong>Minha recomendação prática:</strong> Se você já investe em Vault e precisa de rotação frequente, use Vault Agent. Se quer flexibilidade multi-provider e seus secrets não mudam constantemente, use External Secrets. Muitos ambientes usam ambos — ESO para a maioria dos casos e Vault Agent apenas para aplicações críticas com rotação agressiva.</p>

<h2>Conclusão</h2>

<p>O gerenciamento de secrets em Kubernetes evoluiu para ir além de Secrets nativos. <strong>Vault Agent</strong> oferece controle fino, rotação rápida e uma experiência centrada em Vault, ideal para organizações que já adotaram Vault como solução de segurança principal. <strong>External Secrets Operator</strong> traz flexibilidade, abstração do backend e integração nativa com o Kubernetes, perfeito para ambientes multi-cloud ou que precisam mudar de provedor no futuro.</p>

<p>A escolha certa depende do seu contexto: complexidade aceitável, frequência de rotação, número de backends diferentes e se sua equipe já domina Vault. O importante é não deixar secrets em base64 no etcd — ambas as soluções resolvem isso de forma segura e profissional.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.vaultproject.io/docs/platform/k8s/injector" target="_blank" rel="noopener noreferrer">HashiCorp Vault Agent Injector Documentation</a></li>

<li><a href="https://external-secrets.io/" target="_blank" rel="noopener noreferrer">External Secrets Operator Official Docs</a></li>

<li><a href="https://kubernetes.io/docs/concepts/configuration/secret/" target="_blank" rel="noopener noreferrer">Kubernetes Secrets Best Practices</a></li>

<li><a href="https://learn.hashicorp.com/tutorials/vault/kubernetes-minikube" target="_blank" rel="noopener noreferrer">HashiCorp Learn: Vault and Kubernetes</a></li>

<li><a href="https://github.com/external-secrets/external-secrets" target="_blank" rel="noopener noreferrer">External Secrets Operator GitHub</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Docker & Kubernetes

Prometheus em Kubernetes: Operator, ServiceMonitor e PromQL Avançado na Prática
Prometheus em Kubernetes: Operator, ServiceMonitor e PromQL Avançado na Prática

Introdução ao Prometheus em Kubernetes Prometheus é uma solução open-source d...

O que Todo Dev Deve Saber sobre Progressive Delivery com Argo Rollouts: Canary e Blue-Green Automatizados
O que Todo Dev Deve Saber sobre Progressive Delivery com Argo Rollouts: Canary e Blue-Green Automatizados

O que é Progressive Delivery? Progressive Delivery é um modelo de implantação...

Horizontal Pod Autoscaler: Métricas, Thresholds e Comportamento: Do Básico ao Avançado
Horizontal Pod Autoscaler: Métricas, Thresholds e Comportamento: Do Básico ao Avançado

Horizontal Pod Autoscaler: Visão Geral e Arquitetura O Horizontal Pod Autosca...