SOPS + Age: Gestão de Segredos para IaC na PME

Gerir segredos — palavras-passe, chaves de API, tokens, certificados — em repositórios Git é um dos maiores desafios de segurança em infraestrutura como código (IaC). A maioria das pequenas e médias empresas (PME) recorre a soluções inadequadas: ficheiros .env em .gitignore, variáveis de ambiente partilhadas por mensagens, ou até segredos em texto simples commitados por engano. O SOPS (Secrets OPerationS), originalmente da Mozilla e agora mantido pela comunidade, em conjunto com o Age como backend de encriptação, oferece uma alternativa leve e auditável ao HashiCorp Vault, perfeitamente adequada a equipas pequenas que precisam de protecção real sem infraestrutura adicional.

ℹ Nota: O SOPS foi originalmente desenvolvido pela Mozilla e transferido para a comunidade em 2023. O repositório oficial encontra-se em github.com/getsops/sops. O Age é desenvolvido por Filippo Valsorda em github.com/FiloSottile/age.

Neste artigo

SOPS vs Vault vs git-crypt

Existem várias abordagens para gerir segredos em IaC. O quadro seguinte compara as três soluções mais comuns para PME:

Critério SOPS + Age HashiCorp Vault git-crypt
Complexidade Baixa — binário único Alta — servidor dedicado Mínima
Encriptação granular Sim — por chave/valor Sim — via policies Não — ficheiro completo
Formatos suportados YAML, JSON, ENV, INI KV store dinâmico Qualquer ficheiro binário
Rotação de chaves Manual, via re-encrypt Automática Difícil
Deps de infraestrutura Nenhuma Servidor + storage backend Nenhuma
Auditoria Git history + diff visível Audit log dedicado Git history (difícil de ler)

O git-crypt encripta ficheiros inteiros, o que significa que um git diff mostra apenas “ficheiro encriptado” — impossível auditar o que mudou. O Vault é poderoso mas exige um servidor sempre disponível, TLS, policies e armazenamento persistente. Para uma PME com 5 a 50 servidores, o Vault é frequentemente desproporcional.

O SOPS resolve o problema de forma elegante: encripta apenas os valores em ficheiros YAML/JSON, mantendo as chaves legíveis. Isto permite ver a estrutura do ficheiro no git diff e perceber imediatamente qual segredo foi alterado, sem nunca o revelar.

⚠ Atenção: Nunca armazene segredos em texto simples no Git, mesmo em repositórios privados. Um repositório privado comprometido, um fork acidental ou um log de CI exposto podem revelar todos os segredos. O SOPS + Age garante que mesmo que o repositório seja acedido, os segredos permanecem encriptados.

Instalar SOPS e Age

Ambas as ferramentas são binários simples sem dependências. A instalação é rápida em qualquer distribuição Linux.

Instalar o Age

O Age é o motor de encriptação. Transfere-se o binário pré-compilado do GitHub:

# Descarregar a versão mais recente do Age
AGE_VERSION="1.2.1"
wget "https://github.com/FiloSottile/age/releases/download/v${AGE_VERSION}/age-v${AGE_VERSION}-linux-amd64.tar.gz"
tar xzf "age-v${AGE_VERSION}-linux-amd64.tar.gz"
sudo mv age/age /usr/local/bin/
sudo mv age/age-keygen /usr/local/bin/

# Verificar a instalação
age --version
age-keygen --version

Instalar o SOPS

O SOPS pode ser instalado a partir do repositório oficial ou via gestor de pacotes:

# Opção 1: Descarregar binário do GitHub
SOPS_VERSION="3.9.2"
wget "https://github.com/getsops/sops/releases/download/v${SOPS_VERSION}/sops-v${SOPS_VERSION}.linux.amd64"
sudo mv "sops-v${SOPS_VERSION}.linux.amd64" /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops

# Opção 2: Via Homebrew (macOS ou Linuxbrew)
brew install sops age

# Verificar a instalação
sops --version

Em Debian/Ubuntu, o SOPS também está disponível via repositório oficial com .deb:

# Ubuntu/Debian via repositório oficial
curl -fsSL https://packages.getsops.io/github.key | sudo gpg --dearmor -o /usr/share/keyrings/sops-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/sops-archive-keyring.gpg] https://packages.getsops.io/apt stable main" | sudo tee /etc/apt/sources.list.d/sops.list
sudo apt update && sudo apt install sops

ℹ Dica: O SOPS suporta vários backends de encriptação: GPG, AWS KMS, Google Cloud KMS, Azure Key Vault e Age. Para PME sem infraestrutura cloud, o Age é a escolha ideal por ser simples, rápido e não requerer serviços externos.

Gerar chaves Age

O Age usa criptografia assimétrica: uma chave pública para encriptar e uma chave privada para desencriptar. A chave privada deve ser guardada em segredo; a chave pública pode ser partilhada (por exemplo, em CI/CD).

# Gerar um par de chaves Age
age-keygen -o age-key.txt

# Output:
# # created: 2026-08-03T12:00:00Z
# # public key: age1ql3z7hjy54pw3hyzm5xc... 
# AGE-SECRET-KEY-1QZP...

# A chave pública pode ser mostrada novamente
age-keygen -y age-key.txt

A chave privada (o ficheiro age-key.txt) deve ser guardada num local seguro e nunca commitada no Git. Uma boa prática é armazená-la num gestor de palavras-passe (ex: KeePassXC, Bitwarden) ou numa pen USB encriptada.

O SOPS precisa de saber onde está a chave privada. Define-se via variável de ambiente:

# Indicar ao SOPS onde está a chave privada do Age
export SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/keys.txt"

# Em CI/CD, usar uma secret com o conteúdo da chave
# export SOPS_AGE_KEY_FILE="/tmp/age-key.txt"
# echo "$AGE_SECRET_KEY" > "$SOPS_AGE_KEY_FILE"
# chmod 600 "$SOPS_AGE_KEY_FILE"

⚠ Crítico: Se a chave privada for perdida, todos os segredos encriptados com ela tornam-se irrecuperáveis. Gere sempre uma cópia de segurança offline da chave e considere usar múltiplas chaves (uma por ambiente) para limitar o impacto de uma chave comprometida.

Encriptar ficheiros com SOPS

O fluxo típico é: cria-se o ficheiro em texto simples, encripta-se com SOPS e commita-se a versão encriptada. Vamos ver um exemplo com YAML.

Exemplo: ficheiro de segredos YAML

Cria-se um ficheiro secrets.yaml em texto simples:

# secrets.yaml — texto simples (NÃO commitar)
database:
  host: db.exemplo.pt
  port: 5432
  user: admin
  password: SuperSecreta123!
api:
  token: ghp_AbcDefGhiJklMnoPqr
  endpoint: https://api.exemplo.pt/v2

Encripta-se com SOPS usando a chave pública do Age:

# Encriptar o ficheiro com SOPS + Age
sops --encrypt --age age1ql3z7hjy54pw3hyzm5xc... \
  --in-place secrets.yaml

# O ficheiro fica assim (valores encriptados, chaves legíveis):
# database:
#   host: db.exemplo.pt
#   port: 5432
#   user: ENC[AES256_GCM,data:...,type:str]
#   password: ENC[AES256_GCM,data:...,type:str]
# api:
#   token: ENC[AES256_GCM,data:...,type=str]
#   endpoint: https://api.exemplo.pt/v2
# sops:
#   age:
#     - recipient: age1ql3z7hjy54pw3hyzm5xc...
# ...

Repara: apenas os valores sensíveis são encriptados (user, password, token). As chaves e valores não sensíveis (host, port, endpoint) permanecem legíveis. O SOPS adiciona um bloco sops: no fim com os metadados de encriptação.

Para desencriptar e visualizar:

# Desencriptar e mostrar no terminal
sops --decrypt secrets.yaml

# Desencriptar para um novo ficheiro
sops --decrypt secrets.yaml > secrets-decrypt.txt

# Editar o ficheiro encriptado (desencripta, abre editor, re-encripta)
sops secrets.yaml

Ficheiro .sops.yaml

O SOPS suporta um ficheiro de configuração .sops.yaml na raiz do repositório para definir regras de encriptação automáticas:

# .sops.yaml — configuração de encriptação
creation_rules:
  # Ficheiros de produção: chave Age específica
  - path_regex: secrets/prod/.*\.yaml$
    age: "age1prodxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  # Ficheiros de staging: outra chave
  - path_regex: secrets/staging/.*\.yaml$
    age: "age1stagingxxxxxxxxxxxxxxxxxxxxxxxx"
  # Restantes: chave por defeito
  - path_regex: .*\.yaml$
    age: "age1defaultxxxxxxxxxxxxxxxxxxxxxxx"

Com este ficheiro, basta executar sops secrets/prod/db.yaml e o SOPS selecciona automaticamente a chave correcta.

Integração com Ansible

O Ansible é uma das ferramentas IaC mais usadas em PME. A integração com SOPS é directa através de um script de lookup que desencripta os ficheiros automaticamente durante a execução dos playbooks.

Instalar o lookup plugin

# Instalar via pip o módulo sops para Ansible
pip install sops

# OU colocar o script de lookup no directório do projecto
mkdir -p lookup_plugins
wget -O lookup_plugins/sops.py \
  https://raw.githubusercontent.com/getsops/sops/master/usage/ansible/sops.py

No ansible.cfg, activar o directório de plugins:

# ansible.cfg
[defaults]
lookup_plugins = ./lookup_plugins

Usar SOPS num playbook

# playbook.yml
- hosts: servidores
  vars_files:
    # O SOPS desencripta automaticamente
    - secrets/prod/secrets.yaml
  tasks:
    - name: Criar ficheiro de configuração com segredos
      ansible.builtin.template:
        src: app.conf.j2
        dest: /etc/app/app.conf
        mode: "0600"
      no_log: true  # Não mostrar segredos no output

    - name: Usar um valor individual via lookup
      ansible.builtin.debug:
        msg: "Token da API: {{ lookup('sops', 'secrets/prod/secrets.yaml item=api.token') }}"
      no_log: true

ℹ Dica: Usa sempre no_log: true em tarefas que manipulam segredos. Sem esta directiva, o Ansible mostra os valores desencriptados no output da execução, o que pode ficar em registos de CI/CD acessíveis a pessoas não autorizadas.

Integração com OpenTofu

O OpenTofu (fork comunitário do Terraform) e o próprio Terraform podem consumir segredos SOPS através de um provider dedicado. O fluxo é simples: os segredos são encriptados no repositório e desencriptados em tempo de execução.

Provider SOPS

# main.tf — declaração do provider
terraform {
  required_providers {
    sops = {
      source  = "carlpett/sops"
      version = "~> 1.0"
    }
  }
}

provider "sops" {}

# Ler o ficheiro de segredos encriptado
data "sops_file" "secrets" {
  source_file = "secrets/prod/secrets.yaml"
}

# Usar os valores nos recursos
resource "aws_db_instance" "principal" {
  username = data.sops_file.secrets.data["database.user"]
  password = data.sops_file.secrets.data["database.password"]
  # ...
}

Durante o tofu plan ou tofu apply, o provider lê o ficheiro SOPS, desencripta-o com a chave Age (que deve estar disponível via SOPS_AGE_KEY_FILE) e injecta os valores nos recursos.

Variáveis sensíveis em OpenTofu

# variables.tf
variable "db_password" {
  type      = string
  sensitive = true
  description = "Palavra-passe da base de dados"
}

# tfvars encriptado com SOPS
# secrets.auto.tfvars.enc → desencriptar antes do apply:
sops --decrypt secrets.auto.tfvars.enc > secrets.auto.tfvars
tofu apply
rm secrets.auto.tfvars  # Apagar imediatamente

⚠ Atenção: O estado do OpenTofu/Terraform (terraform.tfstate) pode conter segredos em texto simples. Armazene o ficheiro de estado num backend remoto encriptado (S3 com SSE, Azure Blob, GCS) e nunca o commit no Git. O estado em texto simples é uma vulnerabilidade frequentemente ignorada.

Boas Práticas

Para tirar o máximo proveito do SOPS + Age em ambiente de PME, segue-se um conjunto de práticas recomendadas:

Prática Descrição
Chaves por ambiente Usar chaves Age diferentes para produção, staging e desenvolvimento. Limita o impacto de uma chave comprometida.
Rotação periódica Gerar novas chaves e re-encriptar os segredos a cada 6-12 meses. Manter as chaves antigas até todos os segredos estarem migrados.
Múltiplos destinatários Encriptar com mais do que uma chave pública (ex: equipa de operações + chave de recuperação). Se uma chave for perdida, a outra permite desencriptar.
Pre-commit hook Instalar um hook que bloqueia commits de ficheiros não encriptados em directórios de segredos. Evita exposição acidental.
Backup das chaves Guardar as chaves privadas em pelo menos dois locais seguros (ex: gestor de palavras-passe + pen USB encriptada). Sem a chave, os segredos são irrecuperáveis.
CI/CD com secrets Em pipelines (GitHub Actions, GitLab CI), injectar a chave Age via secret do CI. O SOPS desencripta em runtime sem persistir a chave.
Auditoria via Git Como o SOPS mantém as chaves YAML legíveis, o git log -p mostra que segredo foi alterado e quando, sem revelar o valor.

Pre-commit hook de protecção

Um hook simples que impede o commit de ficheiros não encriptados:

#!/bin/bash
# .git/hooks/pre-commit — bloquear segredos em texto simples
for f in $(git diff --cached --name-only -- 'secrets/**'); do
  if ! grep -q "^sops:" "$f" 2>/dev/null; then
    echo "ERRO: $f não está encriptado com SOPS. Execute:"
    echo "  sops --encrypt --age  --in-place $f"
    exit 1
  fi
done

Múltiplos destinatários

Para garantir resiliência, encripta-se com várias chaves públicas:

# .sops.yaml com múltiplas chaves (destinatários)
creation_rules:
  - path_regex: secrets/prod/.*\.yaml$
    age:
      - "age1equipaopsxxxxxxxxxxxxxxxxxxxx"   # Equipa de operações
      - "age1recuperacaoxxxxxxxxxxxxxxxxxxxx" # Chave de recuperação
      - "age1cixxxxxxxxxxxxxxxxxxxxxxxxx"     # CI/CD

Qualquer uma das chaves privadas correspondentes consegue desencriptar o ficheiro. Isto garante que mesmo que a chave da equipa de operações seja perdida, a chave de recuperação ou a do CI ainda permitem acesso aos segredos.

ℹ Resumo: O SOPS + Age oferece gestão de segredos encriptados em Git sem infraestrutura adicional, com auditoria via git diff legível, encriptação granular por valor, e integração nativa com Ansible e OpenTofu/Terraform. Para uma PME, é uma alternativa séria ao HashiCorp Vault — sem servidor dedicado, sem licenças, e sem dependências cloud.

Artigos Relacionados

Fontes Oficiais