<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 "nativo" 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='http://127.0.0.1:8200'
export VAULT_TOKEN='s.xxxxxxxxxxxxxxxx' # 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="https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT" \
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 - <<EOF
path "secret/data/myapp/*" {
capabilities = ["read", "list"]
}
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: "true"
vault.hashicorp.com/role: "myapp"
vault.hashicorp.com/agent-inject-secret-database: "secret/data/myapp/database"
vault.hashicorp.com/agent-inject-template-database: |
{{- with secret "secret/data/myapp/database" -}}
export DB_USER="{{ .Data.data.username }}"
export DB_PASSWORD="{{ .Data.data.password }}"
export DB_HOST="{{ .Data.data.host }}"
export DB_PORT="{{ .Data.data.port }}"
{{- end }}
spec:
serviceAccountName: myapp
containers:
- name: myapp
image: myapp:latest
command: ["/bin/sh", "-c"]
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('DB_USER')
DB_PASSWORD = os.getenv('DB_PASSWORD')
DB_HOST = os.getenv('DB_HOST')
DB_PORT = int(os.getenv('DB_PORT', 5432))
def get_db_connection():
conn = psycopg2.connect(
host=DB_HOST,
user=DB_USER,
password=DB_PASSWORD,
port=DB_PORT,
database='mydb'
)
return conn
@app.route('/health')
def health():
try:
conn = get_db_connection()
conn.close()
return {'status': 'healthy'}, 200
except Exception as e:
return {'status': 'unhealthy', 'error': str(e)}, 500
if __name__ == '__main__':
app.run(host='0.0.0.0', 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: "http://vault.vault.svc.cluster.local:8200"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "myapp"
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: ["/bin/sh", "-c"]
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><!-- FIM --></p>