Gerenciamento de Secrets DevOps GitOps: 6 Padrões de Produção do Sealed Secrets ao External Secrets Operator
Enviando Secrets para o Git? Você Está Sentado sobre uma Bomba Relógio
GitOps gira em torno de "Git como a Única Fonte de Verdade", mas os Secrets do Kubernetes são meramente codificados em Base64 — não criptografados. Enviar Secrets codificados em Base64 para um repositório Git é equivalente a armazenar senhas de banco de dados, chaves API e certificados TLS em texto plano. Uma vez que o repositório é vazado (configuração incorreta interna, violação na cadeia de suprimentos de terceiros, limpeza esquecida em um repositório forkado), todos os secrets são instantaneamente expostos.
Em 2026, o gerenciamento de secrets DevOps GitOps não é mais opcional — é um requisito obrigatório para implantações em produção. Este artigo cobre 6 padrões de gerenciamento de secrets de nível produção, desde o armazenamento criptografado com Sealed Secrets até a injeção dinâmica com External Secrets Operator, ajudando você a resolver completamente a segurança de secrets em fluxos de trabalho GitOps.
Pontos Principais
- Compreender 3 padrões arquiteturais para gerenciamento de secrets GitOps: armazenamento criptografado, injeção externa e híbrido
- Dominar a implantação e configuração completa do Sealed Secrets, External Secrets Operator e SOPS
- Implementar rotação automática de secrets e sincronização multi-cluster
- Evitar as 5 armadilhas mais comuns em produção e 10 erros frequentes
- Obter uma matriz de decisão comparativa para seleção de ferramentas
Sumário
- Conceitos Fundamentais de Gerenciamento de Secrets GitOps
- Padrão 1: Armazenamento Criptografado com Sealed Secrets
- Padrão 2: External Secrets Operator + Vault
- Padrão 3: Criptografia SOPS + Age/GPG
- Padrão 4: Rotação Automática de Secrets
- Padrão 5: Sincronização de Secrets Multi-Cluster
- Padrão 6: Auditoria e Conformidade
- 5 Armadilhas Comuns e Soluções
- 10 Soluções para Erros Comuns
- Dicas Avançadas de Otimização
- Análise Comparativa
- Ferramentas Online Recomendadas
Conceitos Fundamentais de Gerenciamento de Secrets GitOps
Três Padrões Arquiteturais
┌─────────────────────────────────────────────────────────────┐
│ Gerenciamento de Secrets GitOps: 3 Padrões │
├─────────────────┬──────────────────┬────────────────────────┤
│ Armazenamento │ Injeção │ Híbrido │
│ Criptografado │ Externa │ (SOPS + ESO) │
│ (Sealed Secrets)│ (ESO + Vault) │ │
├─────────────────┼──────────────────┼────────────────────────┤
│ Git armazena │ Git armazena │ Git armazena valores │
│ texto cifrado │ apenas referên- │ criptografados + │
│ Descriptografia │ cias │ referências │
│ no cluster │ Cluster obtém │ Descriptografia no │
│ Auditoria │ texto plano │ cluster ou obtenção │
│ offline │ Deps externas │ Combinação flexível │
│ Re-criptografar │ Suporte nativo │ Suporte parcial de │
│ para rotacionar │ de rotação │ rotação │
└─────────────────┴──────────────────┴────────────────────────┘
O Problema Fundamental com os Secrets do K8s
# Criar um Secret padrão
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password='SuperSecret123!'
# A "criptografia" é apenas codificação Base64 — qualquer um pode decodificar
kubectl get secret db-credentials -o yaml
# Decodificar leva um comando
kubectl get secret db-credentials \
-o jsonpath='{.data.password}' | base64 -d
# Saída: SuperSecret123!
# Verificar com o codificador/decodificador Base64 do ToolsKu
# https://toolsku.com/pt-BR/encode/base64
Princípios Fundamentais do Gerenciamento de Secrets DevOps GitOps
| Princípio | Descrição | Consequência da Violação |
|---|---|---|
| Zero Texto Plano | Nunca armazenar secrets em texto plano no Git | Vazamento do repositório = comprometimento total |
| Menor Privilégio | Cada aplicação acessa apenas seus próprios secrets | Vazamento lateral de secrets |
| Rotação Automática | Secrets são atualizados automaticamente periodicamente | Chaves de longa duração forçadas por força bruta |
| Auditável | Cada acesso a secrets é registrado | Não é possível rastrear a origem do vazamento |
| Recuperável | Secrets podem ser restaurados rapidamente após perda | Interrupção do negócio |
Padrão 1: Armazenamento Criptografado com Sealed Secrets
Sealed Secrets é uma solução de gerenciamento de secrets GitOps de código aberto da Bitnami. A ideia central: criptografar Secrets localmente com kubeseal, gerar recursos SealedSecret commitados no Git, e o Sealed Secrets Controller no cluster os descriptografa automaticamente de volta para Secrets do K8s.
Arquitetura
┌──────────────────────────────────────────────────────┐
│ Estação de Trabalho do Desenvolvedor │
│ ┌────────────┐ ┌────────────┐ │
│ │ secret.yaml │───▶│ kubeseal │ │
│ │ (texto plano,│ │ (ferramenta │ │
│ │ não commitar)│ │ de cifragem)│ │
│ └────────────┘ └─────┬──────┘ │
│ │ criptografar │
│ ┌─────▼──────┐ │
│ │sealed-secret│ │
│ │.yaml(cifrado)│ │
│ └─────┬──────┘ │
└──────────────────────────┼───────────────────────────┘
│ git push
┌──────────────────────────▼───────────────────────────┐
│ Repositório Git │
│ (armazena apenas SealedSecrets criptografados) │
└──────────────────────────┬───────────────────────────┘
│ git pull (ArgoCD/Flux)
┌──────────────────────────▼───────────────────────────┐
│ Cluster K8s │
│ ┌──────────────────────────────┐ │
│ │ Sealed Secrets Controller │ │
│ │ (descriptografa com chave │ │
│ │ privada) │ │
│ └──────────────┬───────────────┘ │
│ │ descriptografar │
│ ┌─────▼──────┐ │
│ │ K8s Secret │ │
│ │ (texto plano,│ │
│ │ no cluster) │ │
│ └────────────┘ │
└───────────────────────────────────────────────────────┘
Instalando Sealed Secrets
# Instalar Controller no cluster
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.27.0/controller.yaml
# Aguardar o Controller estar pronto
kubectl wait --for=condition=available --timeout=120s \
deployment/sealed-secrets-controller -n kube-system
# Instalar CLI kubeseal
KUBESEAL_VERSION=0.27.0
curl -sLO "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-linux-amd64"
chmod +x kubeseal-linux-amd64
sudo mv kubeseal-linux-amd64 /usr/local/bin/kubeseal
# Verificar instalação
kubeseal --version
Criptografando Secrets
# Criptografar a partir de arquivo Secret em texto plano
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password='SuperSecret123!' \
--dry-run=client -o yaml | \
kubeseal --format yaml > sealed-secret.yaml
# Criar a partir de arquivos
kubectl create secret generic tls-cert \
--from-file=tls.crt=server.crt \
--from-file=tls.key=server.key \
--dry-run=client -o yaml | \
kubeseal --format yaml > sealed-tls.yaml
# Especificar namespace e nome (modo strict)
kubectl create secret generic api-key \
--from-literal=key=abc123xyz \
--namespace production \
--dry-run=client -o yaml | \
kubeseal --format yaml \
--scope namespace-wide > sealed-api-key.yaml
Exemplo de Recurso SealedSecret
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-credentials
namespace: production
spec:
encryptedData:
username: AgBfj8k2mN3pQ7sT9vWxYz...
password: AgCdH5lM6nO8qR0tU2wXyZa...
template:
metadata:
name: db-credentials
namespace: production
type: Opaque
Controle de Escopo de Criptografia
# strict: só pode descriptografar com o mesmo namespace e nome (padrão)
kubeseal --scope strict
# namespace-wide: pode renomear dentro do mesmo namespace
kubeseal --scope namespace-wide
# cluster-wide: utilizável em qualquer namespace do cluster
kubeseal --scope cluster-wide
| Escopo | Nível de Segurança | Flexibilidade | Caso de Uso |
|---|---|---|---|
| strict | Mais alto | Mais baixa | Secrets de produção |
| namespace-wide | Médio | Médio | Multi-aplicação no mesmo namespace |
| cluster-wide | Mais baixo | Mais alta | Recursos compartilhados como certificados |
Backup e Restauração da Chave Mestra
# Backup da chave mestra (todos os SealedSecrets tornam-se indecifráveis se perdidos!)
kubectl get secret -n kube-system \
sealed-secrets-key -o yaml > sealed-secrets-key-backup.yaml
# Restaurar chave mestra em novo cluster
kubectl apply -f sealed-secrets-key-backup.yaml
# Rotacionar chave mestra
kubectl delete secret -n kube-system sealed-secrets-key
# O Controller gera automaticamente uma nova chave, SealedSecrets antigos continuam funcionando
Padrão 2: External Secrets Operator + Vault
External Secrets Operator (ESO) integra sistemas externos de gerenciamento de secrets (Vault, AWS Secrets Manager, GCP Secret Manager, etc.) com Kubernetes. Repositórios Git armazenam apenas referências SecretStore, e o ESO Controller no cluster obtém os secrets de sistemas externos e cria Secrets do K8s.
Arquitetura
┌──────────────────────────────────────────────────────┐
│ Repositório Git │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ SecretStore │ │ ExternalSecret│ │
│ │ (config de │ │ (mapeamento │ │
│ │ conn Vault) │ │ de ref secret)│ │
│ └──────────────┘ └──────────────┘ │
└──────────────────────┬───────────────────────────────┘
│ git pull
┌──────────────────────▼───────────────────────────────┐
│ Cluster K8s │
│ ┌──────────────────────────────┐ │
│ │ External Secrets Operator │ │
│ │ (monitora ExternalSecrets) │ │
│ └──────────────┬───────────────┘ │
│ │ buscar secrets │
│ ┌────────────▼────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ K8s │ │ HashiCorp│ │
│ │ Secret │ │ Vault │ │
│ └──────────┘ └──────────┘ │
└───────────────────────────────────────────────────────┘
Instalando ESO
# Instalar com Helm
helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm install external-secrets \
external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace \
--set installCRDs=true \
--set replicaCount=2 \
--set leaderElect=true
# Verificar
kubectl get pods -n external-secrets
Configurando HashiCorp Vault SecretStore
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: production
spec:
provider:
vault:
server: "https://vault.internal.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "eso-role"
serviceAccountRef:
name: "external-secrets-sa"
Configuração de Política do Vault
# Política do Vault: restringir ESO a caminhos específicos
path "secret/data/production/*" {
capabilities = ["read"]
}
path "secret/data/production/database/*" {
capabilities = ["read", "list"]
}
# Negar acesso a outros ambientes
path "secret/data/staging/*" {
capabilities = ["deny"]
}
path "secret/data/development/*" {
capabilities = ["deny"]
}
Recurso ExternalSecret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 15m
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: db-credentials
creationPolicy: Owner
template:
type: Opaque
data:
DATABASE_URL: "postgresql://{{ .username }}:{{ .password }}@db.internal:5432/mydb"
data:
- secretKey: username
remoteRef:
key: secret/data/production/database
property: username
- secretKey: password
remoteRef:
key: secret/data/production/database
property: password
Multi-Fonte: ClusterSecretStore
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: eso-aws-sa
namespace: external-secrets
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: aws-api-keys
namespace: production
spec:
refreshInterval: 5m
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: aws-api-keys
dataFrom:
- extract:
key: production/api-keys
Padrão 3: Criptografia SOPS + Age/GPG
SOPS (Secrets OPerationS) é uma ferramenta de criptografia de secrets desenvolvida pela Mozilla, que suporta múltiplos backends de criptografia incluindo AES, PGP e Age. Diferente do Sealed Secrets, o SOPS criptografa o arquivo em si — pode criptografar formatos YAML/JSON/ENV e apenas criptografa valores, não chaves. Isso significa que você pode usar Git diff para ver mudanças estruturais nos secrets.
Arquitetura
┌──────────────────────────────────────────────────────┐
│ Estação de Trabalho do Desenvolvedor │
│ ┌────────────┐ ┌────────────┐ │
│ │ secret.yaml │───▶│ SOPS │ │
│ │ (texto plano,│ │ + Age/GPG │ │
│ │ não commitar)│ │ │ │
│ └────────────┘ └─────┬──────┘ │
│ │ criptografar │
│ ┌─────▼──────┐ │
│ │secret.enc. │ │
│ │yaml(cifrado)│ │
│ └─────┬──────┘ │
└──────────────────────────┼───────────────────────────┘
│ git push
┌──────────────────────────▼───────────────────────────┐
│ Repositório Git │
│ (armazena arquivos criptografados, chaves │
│ visíveis, valores criptografados) │
└──────────────────────────┬───────────────────────────┘
│ git pull
┌──────────────────────────▼───────────────────────────┐
│ Pipeline CI/CD │
│ ┌──────────────────────────────┐ │
│ │ sops --decrypt + kubectl apply│ │
│ │ ou integração Flux │ │
│ │ Kustomization SOPS │ │
│ └──────────────────────────────┘ │
└───────────────────────────────────────────────────────┘
Instalando SOPS e Age
# Instalar SOPS
curl -sLO https://github.com/getsops/sops/releases/download/v3.9.0/sops-v3.9.0.linux.amd64
chmod +x sops-v3.9.0.linux.amd64
sudo mv sops-v3.9.0.linux.amd64 /usr/local/bin/sops
# Instalar Age (backend de criptografia recomendado, mais simples que GPG)
curl -sLO https://github.com/FiloSottile/age/releases/download/v1.2.0/age-v1.2.0-linux-amd64.tar.gz
tar xzf age-v1.2.0-linux-amd64.tar.gz
sudo mv age/age /usr/local/bin/
sudo mv age/age-keygen /usr/local/bin/
# Gerar par de chaves Age
age-keygen -o age.key
# Chave pública: age1abc123xyz...
# Verificar
sops --version
age --version
Criptografando Arquivos de Secrets
# secret.yaml (antes da criptografia)
apiVersion: v1
kind: Secret
metadata:
name: app-config
namespace: production
type: Opaque
stringData:
DB_HOST: "db.internal.example.com"
DB_PASSWORD: "SuperSecret123!"
API_KEY: "sk-proj-abc123xyz456"
REDIS_URL: "redis://redis.internal:6379"
# Criptografar com chave pública Age
sops --encrypt \
--age age1abc123xyz456... \
--encrypted-regex '^(DB_PASSWORD|API_KEY)$' \
--in-place secret.yaml
# secret.yaml (depois da criptografia)
apiVersion: v1
kind: Secret
metadata:
name: app-config
namespace: production
type: Opaque
stringData:
DB_HOST: "db.internal.example.com"
DB_PASSWORD: ENC[AES256_GCM,data:Wk5kPQ==,tag:abc123==,type:str]
API_KEY: ENC[AES256_GCM,data:Zm9vYmFy,tag:def456==,type:str]
REDIS_URL: "redis://redis.internal:6379"
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1abc123xyz456...
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA...
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-06-16T10:00:00Z"
mac: ENC[AES256_GCM,data:abc==,tag:xyz==,type:str]
pgp: []
encrypted_regex: ^(DB_PASSWORD|API_KEY)$
version: 3.9.0
Integração SOPS com Flux CD
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: app-secrets
namespace: flux-system
spec:
interval: 10m
path: ./clusters/production
prune: true
sourceRef:
kind: GitRepository
name: flux-system
decryption:
provider: sops
secretRef:
name: sops-age-key
# Criar Secret com chave privada Age para descriptografia do Flux
kubectl create secret generic sops-age-key \
--namespace flux-system \
--from-file=age.agekey=age.key \
--dry-run=client -o yaml | \
kubeseal --format yaml > sealed-sops-key.yaml
Criptografia Multi-Chave (Colaboração em Equipe)
# Arquivo de configuração .sops.yaml — configuração de criptografia a nível de projeto
cat > .sops.yaml << 'EOF'
creation_rules:
- path_regex: ^clusters/production/.*\.yaml$
key_groups:
- age:
- age1abc123xyz456 # chave de produção
- age1def789uvw012 # chave da equipe SRE
- path_regex: ^clusters/staging/.*\.yaml$
key_groups:
- age:
- age1ghi345rst678 # chave de staging
- path_regex: ^clusters/.*\.yaml$
key_groups:
- age:
- age1abc123xyz456 # chave padrão
EOF
Padrão 4: Rotação Automática de Secrets
A rotação de secrets é o aspecto mais negligenciado do gerenciamento de secrets DevOps GitOps. Secrets estáticos que nunca mudam criam uma janela de ataque infinita uma vez vazados. A melhor prática de 2026: rotacionar todos os secrets de produção a cada 90 dias, e secrets de alta sensibilidade a cada 7 dias.
Arquitetura de Rotação
┌──────────────────────────────────────────────────────┐
│ Arquitetura de Auto-Rotação de Secrets │
│ │
│ ┌──────────┐ disparador ┌──────────────┐ │
│ │ CronJob │─────────────▶│ Vault Rotate │ │
│ │ (agendamento)│ │ (gerar novo) │ │
│ └──────────┘ └──────┬───────┘ │
│ │ novo secret │
│ ┌──────▼───────┐ │
│ │ ExternalSecret│ │
│ │ (auto refresh)│ │
│ └──────┬───────┘ │
│ │ atualizar │
│ ┌──────▼───────┐ │
│ │ K8s Secret │ │
│ │ (auto atualizar)│ │
│ └──────┬───────┘ │
│ │ reinício rolling │
│ ┌──────▼───────┐ │
│ │ Pods │ │
│ │ (ler nova │ │
│ │ chave) │ │
│ └──────────────┘ │
└──────────────────────────────────────────────────────┘
Configuração de Auto-Rotação ESO
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: rotating-api-key
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: api-key
creationPolicy: Owner
template:
type: Opaque
metadata:
annotations:
reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
data:
- secretKey: api-key
remoteRef:
key: secret/data/production/api
property: key
Secrets Dinâmicos do Vault
# Configuração de credenciais dinâmicas de banco de dados Vault
resource "vault_database_secret_backend_connection" "postgresql" {
backend = "database"
name = "postgresql-production"
allowed_roles = ["app-readonly", "app-readwrite"]
postgresql {
connection_url = "postgresql://{{username}}:{{password}}@db.internal:5432/mydb?sslmode=require"
username = "vault_admin"
password = "VaultAdminPassword123!"
}
}
resource "vault_database_secret_backend_role" "app_readwrite" {
backend = "database"
name = "app-readwrite"
db_name = vault_database_secret_backend_connection.postgresql.name
default_ttl = 3600
max_ttl = 86400
creation_statements = [
"CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';",
"GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";"
]
}
Detecção Automática de Mudanças de Secrets em Aplicações
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
annotations:
secret.reloader.stakater.com/reload: "api-key,db-credentials"
spec:
containers:
- name: api-server
image: registry.example.com/api-server:v2.1.0
envFrom:
- secretRef:
name: api-key
- secretRef:
name: db-credentials
# Instalar Reloader — reinício rolling automático de Pods ao mudar Secrets
helm repo add stakater https://stakater.github.io/stakater-charts
helm repo update
helm install reloader stakater/reloader \
--namespace reloader \
--create-namespace \
--set reloader.watchGlobally=false
Padrão 5: Sincronização de Secrets Multi-Cluster
Em cenários multi-cluster, os secrets precisam ser sincronizados de forma segura entre clusters. O gerenciamento de secrets DevOps GitOps requer: cada cluster tem seu próprio ciclo de vida de secrets enquanto mantém a consistência.
Arquitetura Multi-Cluster
┌──────────────────────────────────────────────────────┐
│ Arquitetura de Sincronização de Secrets │
│ Multi-Cluster │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ HashiCorp Vault (Central) │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │prod-east/│ │prod-west/│ │ │
│ │ │ secrets │ │ secrets │ │ │
│ │ └──────────┘ └──────────┘ │ │
│ └──────────┬───────────────┬──────────┘ │
│ │ │ │
│ ┌────────▼──────┐ ┌──────▼────────┐ │
│ │ Cluster East │ │ Cluster West │ │
│ │ ┌──────────┐ │ │ ┌──────────┐ │ │
│ │ │ ESO │ │ │ │ ESO │ │ │
│ │ │Controller│ │ │ │Controller│ │ │
│ │ └────┬─────┘ │ │ └────┬─────┘ │ │
│ │ ▼ │ │ ▼ │ │
│ │ ┌──────────┐ │ │ ┌──────────┐ │ │
│ │ │ Secrets │ │ │ │ Secrets │ │ │
│ │ └──────────┘ │ │ └──────────┘ │ │
│ └───────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────┘
SecretStore Independente por Cluster
# Cluster East
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-east
namespace: production
spec:
provider:
vault:
server: "https://vault.internal.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes/east"
role: "eso-east-role"
serviceAccountRef:
name: "external-secrets-sa"
# Cluster West
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-west
namespace: production
spec:
provider:
vault:
server: "https://vault.internal.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes/west"
role: "eso-west-role"
serviceAccountRef:
name: "external-secrets-sa"
Distribuição Multi-Cluster de Certificados TLS
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: wildcard-tls
namespace: ingress-nginx
spec:
refreshInterval: 24h
secretStoreRef:
name: vault-east
kind: SecretStore
target:
name: wildcard-tls
creationPolicy: Owner
template:
type: kubernetes.io/tls
data:
- secretKey: tls.crt
remoteRef:
key: secret/data/shared/tls/wildcard
property: cert
- secretKey: tls.key
remoteRef:
key: secret/data/shared/tls/wildcard
property: key
Sincronização de Chave Mestra Sealed Secrets Multi-Cluster
# Exportar chave mestra
kubectl get secret -n kube-system sealed-secrets-key \
-o yaml > sealed-secrets-master-key.yaml
# Importar para o cluster destino
kubectl apply -f sealed-secrets-master-key.yaml -n kube-system
# Reiniciar Controller para carregar a chave
kubectl rollout restart deployment/sealed-secrets-controller -n kube-system
Padrão 6: Auditoria e Conformidade
O gerenciamento de secrets DevOps GitOps deve satisfazer requisitos de auditoria: quem acessou qual secret e quando. Os padrões de conformidade de 2026 (SOC2, ISO 27001, MLPS 2.0) exigem acesso rastreável a secrets.
Logs de Auditoria do Vault
# Habilitar log de auditoria do Vault
audit {
type = "file"
options = {
file_path = "/vault/audit/audit.log"
mode = "0600"
}
}
# Habilitar auditoria Syslog
audit {
type = "syslog"
options = {
facility = "AUTH"
tag = "vault"
address = "syslog.internal:514"
}
}
Log de Acesso ESO
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: audited-vault
annotations:
audit.external-secrets.io/enabled: "true"
audit.external-secrets.io/log-access: "true"
spec:
provider:
vault:
server: "https://vault.internal.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "eso-audited-role"
serviceAccountRef:
name: "external-secrets-sa"
Política de Auditoria do Kubernetes
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
resources:
- group: ""
resources: ["secrets"]
namespaces: ["production", "staging"]
omitStages:
- RequestReceived
- level: Metadata
resources:
- group: ""
resources: ["secrets"]
namespaces: ["default"]
omitStages:
- RequestReceived
- level: RequestResponse
resources:
- group: "external-secrets.io"
resources: ["externalsecrets", "secretstores"]
omitStages:
- RequestReceived
Monitoramento de Acesso a Secrets
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-secret-access
spec:
validationFailureAction: Audit
background: true
rules:
- name: block-default-sa-secret-access
match:
resources:
kinds: [Pod]
validate:
message: "Acessar secrets com o ServiceAccount padrão é proibido"
pattern:
spec:
serviceAccountName: "!default"
- name: require-secret-annotations
match:
resources:
kinds: [Secret]
names: ["db-*", "api-*", "tls-*"]
validate:
message: "Secrets de produção devem ter anotações de proprietário e validade"
pattern:
metadata:
annotations:
owner: "?*"
expiry: "?*"
5 Armadilhas Comuns e Soluções
Armadilha 1: Perda da Chave Mestra do Sealed Secrets
Sintoma: Após reconstruir o cluster, todos os SealedSecrets não podem ser descriptografados. O Controller reporta failed to unseal.
Causa Raiz: Sealed Secrets usa criptografia assimétrica. A chave privada só existe no cluster. Destruir o cluster perde a chave privada.
Solução:
# 1. Backup regular da chave mestra (automatizado)
kubectl get secret -n kube-system \
sealed-secrets-key -o yaml | \
sops --encrypt --age age1abc123xyz456... \
/dev/stdin > sealed-secrets-key.enc.yaml
# 2. Armazenar chave criptografada em outro repositório Git
git add sealed-secrets-key.enc.yaml
git commit -m "backup: sealed secrets master key $(date +%Y%m%d)"
# 3. Processo de recuperação
sops --decrypt sealed-secrets-key.enc.yaml | \
kubectl apply -f -
Armadilha 2: Atraso na Atualização do ExternalSecret Causando Falha no Início do Pod
Sintoma: Pods recém-implantados falham ao iniciar porque o Secret ainda não existe — ESO ainda não o obteve do Vault.
Solução:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
spec:
refreshInterval: 5m
target:
creationPolicy: Owner
template:
type: Opaque
dataFrom:
- extract:
key: production/app-secrets
# Garantir que ESO sincronizou antes de implantar a aplicação
# Método 1: Usar ArgoCD Sync Hooks
# Método 2: Aguardar o Secret existir no CI
kubectl wait --for=condition=Ready \
externalsecret/app-secrets -n production --timeout=120s
Armadilha 3: Rotação de Chaves SOPS Quebra a Descriptografia de Textos Cifrados Antigos
Sintoma: Após a equipe rotacionar as chaves Age, arquivos criptografados antigos não podem ser descriptografados.
Solução:
# Usar criptografia multi-chave — coexistência de chaves antigas e novas
sops --encrypt \
--age age1NEWKEY...,age1OLDKEY... \
--in-place secret.yaml
# Ou gerenciar grupos de chaves via .sops.yaml
# Após atualizar a chave, descriptografar com chave antiga e depois re-criptografar com chave nova
sops --decrypt --age age1OLDKEY... secret.yaml | \
sops --encrypt --age age1NEWKEY... \
--filename-override secret.yaml /dev/stdin > secret_new.yaml
Armadilha 4: Não é Possível Revogar Rapidamente Secrets Vazados
Sintoma: Uma API Key é vazada, mas SealedSecret requer re-criptografia e commit — a janela de revogação é muito longa.
Solução:
# Abordagem ESO: desabilitar diretamente a chave antiga no Vault
vault kv metadata put -delete-version-after=0s \
secret/data/production/api-key
# Abordagem Sealed Secrets: exclusão de emergência via Controller
kubectl delete secret api-key -n production
# Re-criptografar imediatamente e commitar novo SealedSecret
# Abordagem genérica: bloqueio a nível de rede
kubectl apply -f - <<EOF
apiVersion: networkpolicies.k8s.io/v1
kind: NetworkPolicy
metadata:
name: block-compromised
namespace: production
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- compromised-api.example.com/32
EOF
Armadilha 5: Secrets Compartilhados Entre Equipes Levam ao Caos de Controle de Acesso
Sintoma: Múltiplas equipes usam o mesmo Token do Vault para acessar secrets, tornando impossível distinguir quem fez o quê.
Solução:
# Criar políticas do Vault por equipe
path "secret/data/team-a/*" {
capabilities = ["read", "list"]
}
path "secret/data/team-b/*" {
capabilities = ["read", "list"]
}
# Negação explícita para acesso cruzado
path "secret/data/team-a/*" {
capabilities = ["deny"]
}
# Vincular ao role team-b
# Usar namespaces do Vault para isolamento
namespace "team-a" {
path "secret/*" {
capabilities = ["read", "list", "create", "update"]
}
}
10 Soluções para Erros Comuns
1. failed to unseal: no private key found
# Verificar se o Controller possui a chave mestra
kubectl get secrets -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key
# Restaurar chave mestra
kubectl apply -f sealed-secrets-key-backup.yaml -n kube-system
kubectl rollout restart deployment/sealed-secrets-controller -n kube-system
2. ExternalSecret "not ready": could not get secret data
# Verificar conexão SecretStore
kubectl describe secretstore vault-backend -n production
# Verificar autenticação Vault
kubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets
# Verificar permissões do ServiceAccount
kubectl auth can-i get secret -n production \
--as=system:serviceaccount:production:external-secrets-sa
3. sops error decrypting: could not find a matching key
# Verificar se a chave Age está correta
age-keygen -y age.key
# Verificar configuração de chaves no .sops.yaml
cat .sops.yaml
# Tentar especificar chave para descriptografia
sops --decrypt --age age1YOURKEY... secret.yaml
4. SealedSecret "sealed-secrets" is invalid: metadata.name
# Verificar namespace e nome especificados durante a criptografia
# O modo strict requer correspondência exata
kubeseal --scope strict --namespace production \
--name db-credentials < secret.yaml > sealed.yaml
5. Vault seal status: sealed
# Verificar status do Vault
vault status
# Deslacrar Vault (requer 2 de 3 chaves de unseal)
vault operator unseal <key1>
vault operator unseal <key2>
# Configuração de auto-unseal (recomendada para produção)
# Usar AWS KMS / GCP KMS / Azure Key Vault para auto-unseal
6. refreshInterval: cannot unmarshal
# Garantir que o formato de refreshInterval está correto
# Correto: "15m", "1h", "24h"
# Incorreto: 15, "15", "15minutes"
spec:
refreshInterval: 15m
7. kubeseal: error: failed to get certificate
# Verificar se o Controller está em execução
kubectl get deployment sealed-secrets-controller -n kube-system
# Verificar Secret do certificado
kubectl get secret -n kube-system sealed-secrets-key
# Obter chave pública do Controller
kubeseal --fetch-cert > sealed-secrets-cert.pem
kubeseal --cert sealed-secrets-cert.pem < secret.yaml
8. ExternalSecret status: SecretSyncedError
# Ver erro detalhado
kubectl describe externalsecret db-credentials -n production
# Causa comum: caminho do Vault não existe
# Verificar caminhos no Vault
vault kv list secret/production/
# Causa comum: permissões insuficientes
vault policy read eso-role
9. SOPS: mac mismatch: file has been modified
# O arquivo foi modificado manualmente após a criptografia, a verificação MAC falhou
# Abordagem segura: re-criptografar
sops --decrypt secret.yaml > secret_plain.yaml
# Modificar e depois re-criptografar
sops --encrypt --age age1YOURKEY... secret_plain.yaml > secret.yaml
10. Kubernetes: secret "xxx" not found
# ESO ainda não terminou de sincronizar
kubectl get externalsecret -n production
# Verificar logs do Controller ESO
kubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets \
--tail=100
# Acionar sincronização manual
kubectl annotate externalsecret db-credentials \
force-sync=$(date +%s) -n production \
--overwrite
Dicas Avançadas de Otimização
1. Hook Pre-commit do Git para Prevenir Commits de Secrets em Texto Plano
#!/bin/bash
# .git/hooks/pre-commit
# Escanear arquivos preparados em busca de secrets suspeitos
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.yaml$|\.yml$|\.env$')
for FILE in $STAGED_FILES; do
# Detectar Secret de K8s codificado em Base64
if grep -qE 'kind:\s*Secret' "$FILE" 2>/dev/null; then
if ! grep -qE 'kind:\s*SealedSecret|encryptedData|sops:' "$FILE" 2>/dev/null; then
echo "ERRO: Arquivo Secret não criptografado encontrado: $FILE"
echo "Por favor criptografar com kubeseal ou sops antes de commitar"
exit 1
fi
fi
# Detectar padrões de secrets comuns
if grep -qiE '(password|secret|api.key|token)\s*[:=]\s*["\x27]?[A-Za-z0-9+/=]{16,}' "$FILE" 2>/dev/null; then
if ! grep -qE 'ENC\[|sops:|encryptedData' "$FILE" 2>/dev/null; then
echo "AVISO: Secret em texto plano suspeito em: $FILE"
echo "Por favor confirmar criptografia ou usar gerenciamento de secrets externo"
fi
fi
done
exit 0
2. Integração de Gerenciamento de Secrets com ArgoCD
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: app-with-secrets
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/gitops-manifests.git
targetRevision: main
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
3. Motor de Templates de Secrets
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-config
namespace: production
spec:
refreshInterval: 15m
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: app-config
template:
type: Opaque
engineVersion: v2
data:
DATABASE_URL: "postgresql://{{ .db_user }}:{{ .db_pass }}@{{ .db_host }}:5432/{{ .db_name }}?sslmode=require"
REDIS_URL: "redis://:{{ .redis_pass }}@{{ .redis_host }}:6379/0"
JWT_SECRET: "{{ .jwt_secret }}"
CONFIG_JSON: |
{
"database": {
"host": "{{ .db_host }}",
"port": 5432,
"name": "{{ .db_name }}"
},
"redis": {
"host": "{{ .redis_host }}"
}
}
data:
- secretKey: db_user
remoteRef:
key: secret/data/production/database
property: username
- secretKey: db_pass
remoteRef:
key: secret/data/production/database
property: password
- secretKey: db_host
remoteRef:
key: secret/data/production/database
property: host
- secretKey: db_name
remoteRef:
key: secret/data/production/database
property: dbname
- secretKey: redis_pass
remoteRef:
key: secret/data/production/redis
property: password
- secretKey: redis_host
remoteRef:
key: secret/data/production/redis
property: host
- secretKey: jwt_secret
remoteRef:
key: secret/data/production/auth
property: jwt_secret
4. Verificação de Saúde de Secrets
apiVersion: batch/v1
kind: CronJob
metadata:
name: secret-health-check
namespace: security
spec:
schedule: "0 8 * * 1"
jobTemplate:
spec:
template:
spec:
serviceAccountName: secret-checker
containers:
- name: checker
image: bitnami/kubectl:1.30
command:
- /bin/bash
- -c
- |
echo "=== Verificação de Saúde de Secrets $(date) ==="
echo ""
echo "--- Verificando certificados TLS a vencer ---"
kubectl get secrets --all-namespaces \
-o json | jq -r '.items[] |
select(.type=="kubernetes.io/tls") |
"\(.metadata.namespace)/\(.metadata.name)"' | \
while read secret; do
ns=$(echo $secret | cut -d/ -f1)
name=$(echo $secret | cut -d/ -f2)
cert=$(kubectl get secret $name -n $ns \
-o jsonpath='{.data.tls\.crt}' | base64 -d)
expiry=$(echo "$cert" | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -n "$expiry" ]; then
days=$(( ($(date -d "$expiry" +%s) - $(date +%s)) / 86400 ))
if [ $days -lt 30 ]; then
echo "AVISO: certificado $secret vence em ${days} dias"
fi
fi
done
echo ""
echo "--- Verificando status de sincronização do ExternalSecret ---"
kubectl get externalsecrets --all-namespaces \
-o json | jq -r '.items[] |
select(.status.conditions[]?.type=="Ready" and
.status.conditions[]?.status!="True") |
"\(.metadata.namespace)/\(.metadata.name): NÃO PRONTO"'
restartPolicy: OnFailure
Comparação: Sealed Secrets vs External Secrets vs SOPS
| Dimensão | Sealed Secrets | External Secrets Operator | SOPS |
|---|---|---|---|
| Criptografia | Assimétrica (chave pública do cluster) | Sem criptografia de conteúdo no Git | Simétrica/Assimétrica |
| Conteúdo no Git | Texto cifrado (SealedSecret) | Referências (ExternalSecret) | Texto cifrado (valores criptografados) |
| Deps Externas | Nenhuma (autocontido) | Vault/AWS/GCP etc. | Nenhuma (autocontido) |
| Rotação de Secrets | Re-criptografia necessária | Suporte nativo | Re-criptografia necessária |
| Multi-Cluster | Sincronizar chave mestra | SecretStore por cluster | Compartilhar chave de criptografia |
| Compatível com Git Diff | Não (texto cifrado ilegível) | Sim (referências legíveis) | Sim (chaves legíveis, valores criptografados) |
| Descriptografia Offline | Não (precisa de chave privada do cluster) | Não (precisa de serviço externo) | Sim (chave local suficiente) |
| Curva de Aprendizado | Baixa | Média | Média |
| Complexidade Operacional | Baixa | Alta (precisa de Vault) | Baixa |
| Capacidade de Auditoria | Histórico do Git | Logs de auditoria do Vault | Histórico do Git |
| Secrets Dinâmicos | Não suportado | Suportado (credenciais dinâmicas do Vault) | Não suportado |
| Escala Adequada | Equipes pequenas-médias | Empresas médias-grandes | Qualquer escala |
| Melhor Para | GitOps simples | Gerenciamento de secrets empresarial | Criptografia multi-formato |
Árvore de Decisão de Seleção
Você já tem Vault/AWS Secrets Manager?
├── Sim → External Secrets Operator
│ └── Precisa de secrets dinâmicos? → ESO + credenciais dinâmicas do Vault
└── Não → Precisa armazenar texto cifrado no Git?
├── Sim → Precisa de compatibilidade com Git Diff?
│ ├── Sim → SOPS + Age
│ └── Não → Sealed Secrets
└── Não → Precisa de secrets dinâmicos?
├── Sim → Implantar Vault + ESO
└── Não → Sealed Secrets
Ferramentas Online Recomendadas
- Codificador/Decodificador Base64: /pt-BR/encode/base64 — Codificar/decodificar dados de K8s Secret e SealedSecret
- Gerador de Chaves RSA: /pt-BR/encode/rsa — Gerar pares de chaves RSA para criptografia GPG do SOPS
- Calculadora de Hash: /pt-BR/encode/hash — Calcular impressões digitais de secrets e somas de verificação
Resumo: Não há bala de prata para o gerenciamento de secrets DevOps GitOps, mas existe um caminho de melhores práticas. Equipes pequenas começam com Sealed Secrets — zero dependências externas para segurança de secrets GitOps. Empresas médias e grandes escolhem External Secrets Operator + Vault, com rotação nativa de secrets e credenciais dinâmicas. Quando você precisa de criptografia multi-formato e compatibilidade com Git Diff, SOPS + Age é a melhor escolha. Os princípios fundamentais de 2026: zero armazenamento em texto plano, rotação automática, auditável e rastreável. Lembre-se — vazamentos de secrets não são questão de "se" mas de "quando."
Posts Relacionados:
- GitOps com ArgoCD em Produção — Guia completo de automação de implantação com ArgoCD
- GitOps com Flux CD em Produção — Entrega contínua com Flux CD na prática
- Endurecimento de Segurança de Contêineres Docker — Sistema de defesa de contêineres de 8 camadas
Referências Externas:
Experimente estas ferramentas executadas localmente no navegador — nenhum cadastro necessário →