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

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)

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) 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).

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.

External Secrets Operator: Sincronização de Secrets Nativos

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 SecretStore (ou ClusterSecretStore) que aponta para seu provedor (Vault, AWS Secrets Manager, etc.), e então cria um ExternalSecret que especifica quais secrets buscar. O ESO faz o trabalho pesado e mantém o Secret do Kubernetes sempre sincronizado.

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.

Vault Agent: Implementação Passo a Passo

Instalando e Configurando o Vault

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

# 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

Agora crie um secret no Vault:

# 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

Configurar Autenticação Kubernetes no Vault

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

# 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

Declarar o Pod com Vault Agent

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

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

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

Aplicação Python Consumindo o Secret

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

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)

External Secrets Operator: Implementação Passo a Passo

Instalação do ESO

External Secrets Operator é instalado como um Helm chart:

# 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

Configurar SecretStore (Backend Vault)

Crie um SecretStore que aponta para seu Vault:

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

Se estiver usando AWS Secrets Manager em vez de Vault:

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

Criar um ExternalSecret

Agora defina qual secret deve ser sincronizado:

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

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

Consumir o Secret no Pod

Como é um Secret nativo, sua aplicação usa normalmente:

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

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

Comparação e Escolha da Abordagem

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

Minha recomendação prática: 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.

Conclusão

O gerenciamento de secrets em Kubernetes evoluiu para ir além de Secrets nativos. Vault Agent 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. External Secrets Operator 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.

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.

Referências


Artigos relacionados