SOPS + age key rotation¶
Estado actual (verificado contra .sops.yaml 2026-08-01): 4 keypairs configurados
como recipients — ramon (Mac), pmx-50, pmx-51, media-208 (VM 208). TODO en .sops.yaml:
añadir pubkeys de LXC 280 (remote-pulse) y LXC 281 (wp-pulse) — sigue pendiente. Cada
fichero de secrets/ se cifra a un conjunto de recipients distinto (key_groups + anclas
YAML: p.ej. cloudflare = ramon+media-208; agents = solo ramon), no una lista global. No hay
backup offline de los age keys — riesgo: si se pierde la key de Ramón, los secretos quedan
inrecuperables. Mitigación pendiente: copia cifrada de las keys en KeePassXC personal.
hermesbot resuelto (2026-08-01)
LXC 101 (hermesbot) fue destruido el 2026-07-05 (ver memoria hermes_setup.md) y su age
pubkey había quedado como recipient huérfano de secrets/agents.sops.yaml. Se quitó de
.sops.yaml y se corrió sops updatekeys sobre el fichero — verificado que sigue
descifrando bien con la key de ramon.
Custodia
Los archivos ~/.config/sops/age/keys.txt son el secreto raíz.
Nunca commit. Backup manual offline (USB/papel) recomendado.
Generar un nuevo keypair age¶
# En la máquina destino (e.g. pmx-50 o un LXC)
age-keygen -o /root/.config/sops/age/keys.txt
chmod 600 /root/.config/sops/age/keys.txt
cat /root/.config/sops/age/keys.txt
# Apunta la línea "public key: age1xxxxxxxxxxxxxxxx"
Para la Mac de Ramón:
mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
chmod 600 ~/.config/sops/age/keys.txt
Añadir a ~/.zshrc:
Añadir un nuevo recipient a archivos existentes¶
1. Actualizar .sops.yaml¶
En la raíz del repo, edita .sops.yaml para añadir la nueva public key:
creation_rules:
- path_regex: secrets/.*\.sops\.yaml$
age: >-
age1mac....,
age1pmx50....,
age1pmx51....,
age1NEWKEY.... # <- añadir aquí
2. Re-encriptar archivos afectados¶
sops updatekeys re-aplica las reglas del .sops.yaml actual a cada archivo:
cd ~/docs-repo
sops updatekeys secrets/observability.sops.yaml
# repetir para cada secreto:
for f in secrets/*.sops.yaml; do sops updatekeys "$f"; done
3. Commit¶
Tras 15min (Ansible-pull) los nodos verán los nuevos archivos. El nuevo recipient podrá descifrar.
Rotación de un keypair comprometido¶
Si una clave privada se filtra (e.g. SSH leak, disco perdido sin LUKS):
1. Generar nueva keypair en el host afectado¶
ssh pmx-50 'mv /root/.config/sops/age/keys.txt /root/.config/sops/age/keys.txt.old'
ssh pmx-50 'age-keygen -o /root/.config/sops/age/keys.txt'
ssh pmx-50 'cat /root/.config/sops/age/keys.txt | grep "public key"'
2. Sustituir la public key en .sops.yaml¶
Reemplaza la línea vieja por la nueva. Commit + sops updatekeys igual que antes.
3. Re-cifrar archivos (quitando la vieja key)¶
updatekeys solo añade. Para quitar la vieja:
# Edición manual del header `sops.age` de cada archivo, o reencrypt entero:
for f in secrets/*.sops.yaml; do
sops -d "$f" > /tmp/plain.yaml
sops -e /tmp/plain.yaml > "$f"
rm /tmp/plain.yaml
done
4. Rotar además los secretos que la key vieja podía leer¶
Una vez comprometida, asumir que todo lo descifrable por esa key está comprometido. Generar tokens nuevos (CF API, Telegram bot, HA long-lived, n8n webhook secret, etc.). Ver Rotating secrets.
Verificación¶
# Desde la Mac (con tu key)
sops -d secrets/observability.sops.yaml | head
# Desde un nodo (con su key)
ssh pmx-50 'cd /var/lib/ansible/homelab-infra && sops -d secrets/observability.sops.yaml | head'
Si falla con Failed to get the data key: 0 successful groups required, 0 succeeded,
el host no tiene una key que sea recipient del archivo.
Backup offline¶
Recomendado (pendiente implementar):
- Imprimir
keys.txtde la Mac en papel + meter en caja fuerte. - Copia cifrada con passphrase fuerte (
age -p) en un USB + cloud personal. - Documentar el procedimiento de restauración (orden de re-generar nodos, etc.) en una nota offline.