Rotating secrets¶
Workflow estándar para rotar tokens, API keys y passwords usando SOPS + age. Asume que la setup base de SOPS key rotation está hecha (al menos un keypair por nodo y por usuario humano).
Cuándo rotar¶
- Programado: cada 6 meses para tokens long-lived (CF API, Telegram, HA).
- Reactivo: filtración sospechada, ex-colaborador, dispositivo perdido.
- Tras la migración inicial (F3): los secretos pre-existentes en plaintext se consideran posiblemente expuestos en logs/history y deben rotarse.
Secretos en alcance¶
Inventario actual (post-migración F3 inicial):
| Secreto | Archivo SOPS | Notas |
|---|---|---|
Healthchecks UUIDs, HA token, Telegram (veraclawd_bot), Grafana, Loki bearer token |
secrets/observability.sops.yaml |
✅ |
HA long-lived, PBS API, Telegram (verahermes_bot), PocketID OIDC (grafana+karakeep), Proxmox API |
secrets/agents.sops.yaml |
✅ |
| Cloudflare API + tunnel | secrets/cloudflare.sops.yaml |
✅ |
| Proxmox cluster secrets | secrets/proxmox.sops.yaml |
✅ |
| Media stack (sonarr/radarr/…) | secrets/media.sops.yaml |
✅ |
| UniFi (CloudKey Gen2+) | secrets/unifi.sops.yaml |
✅ |
| Remote-Pulse (LXC 280) | secrets/remote-pulse.sops.yaml |
✅ (TODO en .sops.yaml: aún falta añadir pubkey age de LXC 280 como recipient) |
| WP-Pulse (LXC 281) | secrets/wp-pulse.sops.yaml |
✅ (TODO en .sops.yaml: aún falta añadir pubkey age de LXC 281 como recipient) |
Los secretos se consolidan en esos 8 ficheros (verificado contra
secrets/*.sops.yamlen el repo, 2026-08-01); no hay uno por servicio.
Plano aparte: secretos de Vera / OpenClaw (CT100, FUERA de SOPS)¶
No pasan por SOPS+age
Vera / OpenClaw mantiene su propio plano de secretos, separado del
workflow SOPS+age de homelab-infra. Viven en texto plano en un
fichero .env dentro de CT100 (clawdbot):
/root/.openclaw/secrets.env. No están cifrados, no están en el
repo homelab-infra, y no se rotan con sops.
Claves en /root/.openclaw/secrets.env (CT100):
| Clave | Qué es |
|---|---|
TG_BOT |
Token del bot de Telegram de Vera |
TG_CHAT |
Chat ID de Telegram destino |
OPENCLAW_GATEWAY_PASSWORD |
Password del gateway OpenClaw (≈clawdbot2026) |
OPENAI_API_KEY |
API key de OpenAI |
GOG_KEYRING_PASSWORD |
Password del keyring (GOG) |
JOBHUNTER_DB_PASSWORD |
Password de la DB de JobHunter (≈jobhunter2025) |
Cómo rotar (plano OpenClaw)¶
Al ser un .env plano, la rotación es manual — no hay sops, git push
ni ansible-pull que lo propague:
# 1) Entrar en CT100 (desde un nodo Proxmox que hospede el CT)
pct exec 100 -- bash
# 2) Backup antes de tocar
cp /root/.openclaw/secrets.env /root/.openclaw/secrets.env.bak.$(date +%Y%m%d)
# 3) Editar el valor a rotar (fichero KEY=VALUE plano)
vi /root/.openclaw/secrets.env
# 4) Rotar el secreto en su provider (Telegram BotFather, OpenAI dashboard,
# DB de JobHunter, etc.) y pegar el nuevo valor en el .env
# 5) Reiniciar el servicio OpenClaw para recargar el .env
systemctl restart openclaw-gateway # (o el unit que consuma el secrets.env)
Deuda técnica conocida
Este plano en plano-texto está fuera del modelo SOPS+age del resto del
homelab. Objetivo futuro: migrar estas claves a secrets/*.sops.yaml y
renderizarlas con Ansible como el resto (ver más abajo). Mientras tanto,
trátalo como posiblemente expuesto y rota tras cualquier incidente.
Workflow base¶
ℹ️ Requiere
sops+age(ambos instalados en.208desde 2026-07-12: sops v3.9.1).
1. Editar el archivo SOPS¶
Se abre el editor ($EDITOR) con el contenido descifrado. Edita el valor:
Guarda y sal. SOPS re-cifra automáticamente.
2. Verificar el diff¶
Debería mostrar solo cambios en los campos cifrados (data:, mac:, lastmodified:).
No debería aparecer texto plano.
3. Commit + push¶
4. Esperar Ansible-pull (15min) o forzar¶
ssh pmx-50 'ansible-pull -U https://github.com/monxas/homelab-infra.git ansible/playbook.yml --limit pmx-50'
(Igual para pmx-51 y nodos afectados.)
5. Reiniciar el servicio que usa el secreto¶
6. Verificar¶
# Trigger un alert de prueba
ssh pmx-51 'pct exec 200 -- curl -X POST http://localhost:5678/webhook/test-alert'
# Comprobar que llega Telegram
Generar un secret nuevo¶
Token aleatorio (32 chars hex)¶
Token aleatorio (URL-safe base64)¶
Password humano-recordable (passphrase 5 palabras)¶
Bcrypt hash (para basic auth Caddy o htpasswd)¶
Genera en la máquina destino (no via SSH desde la Mac — el shell remoto
expande $ y rompe el hash). Ver Creating OIDC client
para más detalle.
Crear un archivo SOPS nuevo¶
cd ~/docs-repo
# Edita partiendo de plantilla mínima
cat > secrets/newservice.sops.yaml.plain <<'EOF'
api_key: <REPLACE>
EOF
# ⚠️ PRIMERO añade una regla per-file en `.sops.yaml` — NO hay catch-all `secrets/.*`:
# - path_regex: secrets/newservice\.sops\.yaml$
# key_groups: [ { age: [ *ramon, ... ] } ]
sops -e secrets/newservice.sops.yaml.plain > secrets/newservice.sops.yaml
rm secrets/newservice.sops.yaml.plain
# Edita el cifrado para meter el valor real
sops secrets/newservice.sops.yaml
Cada fichero de secrets/ tiene su propia path_regex en .sops.yaml
(no hay regla global secrets/.*); sin añadirla antes, sops -e falla con
no matching creation rules.
Después de rotar¶
- Borrar el token viejo en el provider (CF, Telegram, etc.) — si no, sigue válido.
- Buscar referencias al token viejo en logs (
grep -r "<prefix-token>" /var/log) por si quedó en algún sitio. - Documentar la rotación en el commit message + Hermes log si aplica.
Lista de tokens a rotar inmediatamente post-F3¶
Tras la migración a SOPS, considerar los siguientes como posiblemente expuestos
(estuvieron en plaintext en .env/history/backups):
- CF API token con permisos DNS — alta prioridad.
- CF Tunnel credentials (
<uuid>.json) — regenerar tunnel si dudas. - Telegram bot tokens (n8n alert forwarder + morning-report).
- HA long-lived tokens (Hermes, ha-ml, scripts externos).
- PBS API token.
- NAS SMB/NFS share passwords.
- PocketID admin password.
- Vaultwarden admin token.
Plan: rotar 1-3 por semana para evitar romper algo y poder bisectar.