Armazenar secrets do Kubernetes em repositórios Git sem criptografia é um dos erros de segurança mais comuns em implantações cloud-native. Embora os Secrets do Kubernetes usem codificação base64, isso não fornece nenhuma proteção criptográfica — qualquer pessoa com acesso ao repositório pode decodificá-los instantaneamente. SOPS (Secrets OPerationS) combinado com criptografia Age resolve este problema de forma elegante, permitindo armazenar secrets criptografados diretamente no Git mantendo compatibilidade total com fluxos de trabalho GitOps como ArgoCD e FluxCD.
Pré-requisitos
- Um cluster Kubernetes em funcionamento (v1.24+) com
kubectlconfigurado sopsv3.8+ instaladoagev1.1+ instalado- Familiaridade básica com Kubernetes Secrets e manifestos YAML
- Repositório Git para seus manifestos Kubernetes
- Opcional: ArgoCD ou FluxCD para integração GitOps
Entendendo os Riscos dos Secrets do Kubernetes
Os Secrets do Kubernetes são frequentemente mal interpretados como um mecanismo de armazenamento seguro. Na realidade, eles têm limitações significativas que você deve entender antes de projetar sua estratégia de secrets.
Base64 Não É Criptografia
Um Secret padrão do Kubernetes armazena valores como strings codificadas em base64:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: YWRtaW4=
password: cDRzc3cwcmQxMjM=
Decodificar esses valores é trivial:
echo "cDRzc3cwcmQxMjM=" | base64 -d
# Saída: p4ssw0rd123
Qualquer pessoa que clone seu repositório ou obtenha acesso de leitura ao etcd pode extrair cada secret do seu cluster.
Preocupações com Armazenamento no etcd
Por padrão, o Kubernetes armazena Secrets sem criptografia no etcd. Embora você possa habilitar criptografia em repouso com EncryptionConfiguration, isso protege apenas os arquivos de dados do etcd — não as respostas do servidor API nem os manifestos armazenados no Git. Você precisa de uma solução que criptografe os secrets antes de entrarem no seu sistema de controle de versão.
O Problema do Git
GitOps requer que o estado desejado do seu cluster viva no Git. Mas fazer commit de secrets em texto plano no Git significa:
- Cada desenvolvedor com acesso ao repositório vê credenciais de produção
- O histórico de secrets persiste no Git para sempre, mesmo após exclusão
- Repositórios vazados expõem cada secret já commitado
- Frameworks de conformidade (SOC 2, PCI-DSS) proíbem credenciais em texto plano no controle de código fonte
Instalação do SOPS e Age
Linux
# Instalar Age
sudo apt-get install age
# Ou das releases do GitHub
curl -LO 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 age/age-keygen /usr/local/bin/
# Instalar SOPS
curl -LO https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.linux.amd64
sudo mv sops-v3.9.4.linux.amd64 /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops
macOS
brew install sops age
Verifique ambas as instalações:
sops --version
# sops 3.9.4
age --version
# v1.2.0
Criptografando Secrets com SOPS e Age
Gerar um Par de Chaves Age
age-keygen -o age-key.txt
# Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
Armazene a chave privada com segurança. A chave pública é segura para compartilhar e commitar no repositório:
# Armazenar a chave onde o SOPS possa encontrá-la
mkdir -p ~/.config/sops/age
mv age-key.txt ~/.config/sops/age/keys.txt
# Ou definir a variável de ambiente
export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt
Criar Configuração .sops.yaml
Crie um arquivo .sops.yaml na raiz do seu repositório para definir regras de criptografia:
creation_rules:
# Criptografar todos os arquivos no diretório secrets/
- path_regex: secrets/.*\.yaml$
age: >-
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
# Criptografar secrets de staging com uma chave diferente
- path_regex: envs/staging/secrets/.*\.yaml$
age: >-
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p,
age1lzd99uca0lqtmgahfmxj4gvr2fcswcaxmxnz30fwcmm22hjvrzrqsnqxsl
# Chave diferente para produção
- path_regex: envs/production/secrets/.*\.yaml$
age: >-
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p,
age1an5quvgyl8uny5mkmgkzpnpe9wuufrl2vvmqsht4xp3k96s608q0eamcl
Múltiplos destinatários (separados por vírgula) habilitam acesso de equipe — qualquer chave listada pode descriptografar o arquivo.
Criptografar um Secret do Kubernetes
Comece com seu manifesto de secret em texto plano:
# secrets/db-credentials.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
stringData:
username: admin
password: "s3cur3-pr0d-p@ssw0rd"
connection-string: "postgresql://admin:s3cur3-pr0d-p@ssw0rd@db.internal:5432/myapp"
Criptografe com SOPS:
sops --encrypt --in-place secrets/db-credentials.yaml
O arquivo criptografado preserva a estrutura YAML mas criptografa os valores:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
stringData:
username: ENC[AES256_GCM,data:k8mN3w==,iv:abc...,tag:def...,type:str]
password: ENC[AES256_GCM,data:dGhpcyBpcyBl...,iv:ghi...,tag:jkl...,type:str]
connection-string: ENC[AES256_GCM,data:bG9uZ2VyIHN0cmluZw==...,iv:mno...,tag:pqr...,type:str]
sops:
age:
- recipient: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
...
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-02-17T10:00:00Z"
version: 3.9.4
As chaves (apiVersion, kind, metadata) permanecem legíveis enquanto os valores são criptografados. Isso significa que git diff mostra quais secrets mudaram sem revelar os valores reais.
Descriptografar e Aplicar
# Descriptografar e aplicar em um único comando
sops --decrypt secrets/db-credentials.yaml | kubectl apply -f -
# Ou descriptografar para um arquivo temporário
sops --decrypt secrets/db-credentials.yaml > /tmp/secret.yaml
kubectl apply -f /tmp/secret.yaml
rm -f /tmp/secret.yaml
Integração com Fluxos de Trabalho GitOps
ArgoCD com KSOPS
KSOPS é um plugin do Kustomize que descriptografa arquivos criptografados com SOPS durante as operações de sincronização do ArgoCD.
Instale o KSOPS no servidor de repositório do ArgoCD:
# Patch do argocd-repo-server
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-repo-server
namespace: argocd
spec:
template:
spec:
containers:
- name: argocd-repo-server
env:
- name: SOPS_AGE_KEY
valueFrom:
secretKeyRef:
name: sops-age-key
key: age-key.txt
- name: XDG_CONFIG_HOME
value: /.config
volumeMounts:
- mountPath: /.config/kustomize/plugin/viaduct.ai/v1/ksops
name: custom-tools
initContainers:
- name: install-ksops
image: viaductoss/ksops:v4.3.2
command: ["/bin/sh", "-c"]
args:
- cp /usr/local/bin/ksops /.config/kustomize/plugin/viaduct.ai/v1/ksops/ksops
volumeMounts:
- mountPath: /.config/kustomize/plugin/viaduct.ai/v1/ksops
name: custom-tools
volumes:
- name: custom-tools
emptyDir: {}
Crie um gerador KSOPS no seu overlay do Kustomize:
# secret-generator.yaml
apiVersion: viaduct.ai/v1
kind: ksops
metadata:
name: secret-generator
files:
- secrets/db-credentials.yaml
- secrets/api-keys.yaml
Integração Nativa do FluxCD
O FluxCD tem suporte nativo ao SOPS. Crie um secret de descriptografia e configure sua Kustomization:
# Criar o secret de chave Age no namespace flux-system
kubectl create secret generic sops-age \
--namespace=flux-system \
--from-file=age.agekey=~/.config/sops/age/keys.txt
# flux-kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
namespace: flux-system
spec:
interval: 10m
path: ./envs/production
prune: true
sourceRef:
kind: GitRepository
name: my-app
decryption:
provider: sops
secretRef:
name: sops-age
O FluxCD descriptografa automaticamente qualquer arquivo criptografado com SOPS no caminho especificado durante a reconciliação.
SOPS+Age vs Sealed Secrets vs Vault vs External Secrets
| Recurso | SOPS + Age | Sealed Secrets | HashiCorp Vault | External Secrets |
|---|---|---|---|---|
| Local da criptografia | Lado cliente | Lado controlador | Lado servidor | Lado servidor |
| Compatível com Git | Sim (YAML criptografado) | Sim (recurso customizado) | Não (apenas referências) | Não (apenas referências) |
| Infraestrutura necessária | Nenhuma | Controlador no cluster | Servidor Vault | Operador + backend |
| Gerenciamento de chaves | Arquivos de chave Age | Certificado do cluster | Políticas do Vault | Varia por backend |
| Multi-ambiente | Regras .sops.yaml | Certificados por cluster | Namespaces/políticas | Múltiplos stores |
| Integração GitOps | Plugins ArgoCD/Flux | K8s nativo | Driver CSI/injector | Sincronização operador |
| Rotação | Re-criptografar com SOPS | Re-selar necessário | Secrets dinâmicos | Depende do backend |
| Complexidade | Baixa | Baixa | Alta | Média |
| Funciona offline | Sim | Não | Não | Não |
| Ideal para | Equipes pequenas-médias | Clusters individuais | Empresa/conformidade | Multi-cloud |
SOPS+Age se destaca quando você quer simplicidade, capacidade offline e GitOps verdadeiro sem infraestrutura adicional.
Cenário do Mundo Real
Você gerencia uma implantação multi-ambiente do Kubernetes em clusters de desenvolvimento, staging e produção. Sua equipe de oito engenheiros usa ArgoCD para GitOps, e você precisa de uma solução de secrets que:
- Permita desenvolvedores criar e atualizar secrets sem acesso ao cluster
- Mantenha secrets criptografados no Git para auditabilidade
- Use diferentes chaves de criptografia por ambiente
- Permita rotação de chaves sem re-implantar cada secret
Veja como estruturar o repositório:
k8s-manifests/
├── .sops.yaml
├── base/
│ ├── deployment.yaml
│ └── service.yaml
├── envs/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ ├── secret-generator.yaml
│ │ └── secrets/
│ │ └── app-secrets.yaml # criptografado com chave dev
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ ├── secret-generator.yaml
│ │ └── secrets/
│ │ └── app-secrets.yaml # criptografado com chave staging
│ └── production/
│ ├── kustomization.yaml
│ ├── secret-generator.yaml
│ └── secrets/
│ └── app-secrets.yaml # criptografado com chave prod
Cada ambiente tem seu próprio par de chaves Age. Desenvolvedores têm a chave dev, líderes de equipe têm dev+staging, e apenas o pipeline CI/CD tem a chave de produção. O arquivo .sops.yaml direciona a criptografia para a chave correta baseado no caminho do arquivo.
Armadilhas e Casos Extremos
A rotação de chaves requer re-criptografia. Quando você rotaciona uma chave Age, você deve descriptografar cada arquivo com a chave antiga e re-criptografar com a nova. O SOPS fornece sops updatekeys para isso, mas teste primeiro:
# Atualizar chaves para um único arquivo (usa regras do .sops.yaml)
sops updatekeys secrets/db-credentials.yaml
# Re-criptografar todos os secrets em lote
find . -name "*.yaml" -path "*/secrets/*" -exec sops updatekeys {} \;
Criptografia parcial com encrypted_regex. Por padrão, o SOPS criptografa todos os valores. Use encrypted_regex no .sops.yaml para criptografar apenas chaves específicas:
creation_rules:
- path_regex: secrets/.*\.yaml$
encrypted_regex: "^(data|stringData)$"
age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
Secrets binários precisam de codificação base64 primeiro. O SOPS opera em arquivos de texto. Para secrets binários (certificados TLS, keystores), codifique-os em base64 antes de criptografar:
# Codificar o certificado, depois deixar o SOPS criptografar a string base64
cat tls.crt | base64 -w0 > tls.crt.b64
Nunca faça commit da chave privada Age. Adicione ao .gitignore imediatamente:
echo "age-key.txt" >> .gitignore
echo "*.agekey" >> .gitignore
A seção de metadados do SOPS cresce com os destinatários. Cada destinatário Age adicional adiciona ~300 bytes de metadados. Com muitos destinatários, considere usar uma chave de equipe compartilhada distribuída através de um canal seguro em vez de chaves individuais.
Solução de Problemas
Erro: could not find common decryption key
A chave privada Age não está disponível. Verifique a localização do arquivo de chave:
# Verificar se o arquivo de chave existe
ls -la ~/.config/sops/age/keys.txt
# Ou verificar a variável de ambiente
echo $SOPS_AGE_KEY_FILE
# Verificar se a chave corresponde ao destinatário
grep "public key" ~/.config/sops/age/keys.txt
Erro: failed to decrypt
O arquivo foi criptografado com uma chave Age diferente. Verifique qual chave foi usada:
sops --decrypt --verbose secrets/db-credentials.yaml 2>&1 | grep "recipient"
A sincronização do ArgoCD falha com erros KSOPS Certifique-se de que o container init do KSOPS foi concluído com sucesso e que o secret da chave Age existe:
kubectl logs deployment/argocd-repo-server -n argocd -c install-ksops
kubectl get secret sops-age-key -n argocd
FluxCD Kustomization preso em Not Ready
Verifique os logs do controlador Kustomize:
kubectl logs deployment/kustomize-controller -n flux-system | grep -i sops
Causa comum: o secret sops-age está no namespace errado ou tem o nome de chave errado (deve ser age.agekey).
Arquivo criptografado mostra erro mac mismatch
O arquivo foi modificado após a criptografia sem usar sops --set ou sops edit. Re-criptografe a partir da fonte em texto plano:
sops --decrypt secrets/db-credentials.yaml > /tmp/plain.yaml
sops --encrypt /tmp/plain.yaml > secrets/db-credentials.yaml
rm -f /tmp/plain.yaml
Resumo
- Secrets do Kubernetes usam codificação base64, não criptografia — não são seguros por padrão
- SOPS criptografa valores YAML mantendo chaves legíveis, habilitando diffs significativos no Git
- Age fornece gerenciamento de chaves mais simples que PGP sem sobrecarga de configuração
- O arquivo
.sops.yamldefine regras de criptografia por caminho para configurações multi-ambiente - ArgoCD se integra via plugin KSOPS; FluxCD tem suporte nativo de descriptografia SOPS
- A rotação de chaves requer re-criptografar todos os arquivos afetados com
sops updatekeys - Sempre armazene chaves privadas Age fora do repositório e no
.gitignore - Para ambientes empresariais que requerem secrets dinâmicos, considere HashiCorp Vault