Agenix y pass — dos formas de guardar secretos, y cuándo usar cada una
La pregunta natural
En el enjambre tengo dos gestores de secretos coexistiendo:
pass y agenix. La primera reacción de cualquiera que
llega es: "¿por qué dos? ¿no es redundante? ¿no podríamos usar solo
uno?".
La respuesta corta es: hacen cosas distintas. La respuesta larga es este post.
Lo que tienen en común
Los dos son gestores de secretos para humanos paranoicos:
- Cero servicios cloud. Ni HashiCorp Vault, ni AWS Secrets Manager, ni 1Password. Los secretos viven en tus máquinas.
- Cifrado simétrico fuerte.
passusa GPG,agenixusaage(un protocolo más moderno, también seguro). - Texto plano en disco después del descifrado, durante el tiempo mínimo necesario. No quedan rastros en historiales de shell ni en variables permanentes.
- Versionables en git: los archivos cifrados pueden subirse a repositorios públicos sin riesgo. Los descifradores son los que importan.
Hasta aquí parecen primos. La diferencia profunda es qué momento tiene que existir el secreto descifrado y quién lo descifra.
pass — la navaja suiza del humano
Qué es
pass es un wrapper de bash de unas 800
líneas alrededor de GPG. Lo escribió Jason A. Donenfeld (el mismo de
WireGuard) en 2012.
$ pass insert correo/gmail/pascual
Enter password: ********
$ pass show correo/gmail/pascual
********Bajo el capó, los secretos viven en ~/.password-store/ como ficheros .gpg organizados en carpetas. La organización en
carpetas es la organización en el filesystem. Lo que ves es lo que
hay.
~/.password-store/
├── correo/
│ └── gmail/
│ └── pascual.gpg
├── github.gpg
├── telegram/
│ ├── bot-token.gpg
│ └── chat-id-pascual.gpg
└── work/
└── vocento/
├── jira.gpg
└── ldap.gpg
Para qué lo uso yo
Secretos manuales que consume YO o un script ad-hoc.
- Contraseñas de las cuentas que uso día a día (GMail, GitHub, Vocento Workday, banca, etc).
- Cookies de servicios web cuando hago scraping ocasional.
- Tokens de APIs para experimentos puntuales.
Cuando necesito un secreto, abro una terminal, pass show foo/bar, me lo copia al clipboard
durante 45 segundos. Listo.
Lo bueno
- Filosofía Unix pura: filesystem + GPG + bash. No hay base de datos opaca, no hay daemon, no hay JSON Schemas.
- Funciona en cualquier Linux con GPG instalado. Sin Nix, sin systemd, sin NixOS. Solo bash.
- Sincronización con git:
pass git pushy ya está. (Yo uso Syncthing además, pero git también funciona.) - Múltiples destinatarios via GPG multi-key: el mismo
.gpgse puede descifrar con distintas llaves privadas (yo mismo en aurin, macbook, etc). - Hay clientes móviles decentes (Password Store para Android).
- Browser integration: tengo
browserpassconfigurado y Firefox rellena formularios desde mi store. Sin LastPass, sin riesgo de brecha.
Lo malo (o lo no-encaja)
- Es interactivo por diseño. Para usar
passen un servicio systemd que arranca sin yo presente, hay que hacer juegos malabares congpg-agentypinentry. Funciona, pero es feo. - No es declarativo. Si configuro un host nuevo y quiero que tenga acceso a tres secretos, tengo que acordarme de añadir su key GPG y re-cifrar manualmente. Si me equivoco, no me entero hasta que alguien intenta descifrar y falla.
- Los secretos vivos en filesystem en claro durante la
ejecución del comando. Esto es lo mismo que agenix en runtime,
pero el modelo mental es distinto:
passes "abre, copia, cierra";agenixes "ya está montado, úsalo".
agenix — secretos declarativos para NixOS
Qué es
agenix es un módulo NixOS + una pequeña CLI.
Cifra ficheros con el protocolo age y los
expone descifrados en /run/agenix/<nombre> durante el arranque
del sistema, antes de que arranquen los servicios que los
necesitan.
# En tu config NixOS
age.secrets.telegram-bot-token = {
file = ./secrets/telegram-bot-token.age;
owner = "passh";
mode = "400";
};Después, en un servicio systemd:
systemd.services.mi-bot = {
serviceConfig.EnvironmentFile = [
config.age.secrets.telegram-bot-token.path
# → /run/agenix/telegram-bot-token
];
};Y eso es todo. NixOS:
- Lee el
.agecifrado en build time. - En boot, lo descifra usando la SSH host key (
/etc/ssh/ssh_host_ed25519_key). - Lo deposita en
/run/agenix/con los permisos declarados. - El servicio arranca con el secreto listo.
Quién descifra: las SSH host keys
Aquí está la magia más bonita de agenix:
El identity GPG/age personal no aparece en producción. Quien descifra es la propia máquina, usando su SSH host key — la misma clave que ya usa para anunciar su identidad en SSH.
Eso significa: no hay clave de descifrado personal instalada en el servidor. Si el servidor cae comprometido, el atacante no se lleva "mi" clave — se lleva la del servidor, que ya estaba ahí.
En secrets/secrets.nix declaro quién
puede descifrar qué:
let
aurin = "ssh-ed25519 AAAAC3Nz... root@aurin";
cohete = "ssh-ed25519 AAAAC3Nz... root@cohete";
macbook = "ssh-ed25519 AAAAC3Nz... root@macbook";
retropix = "ssh-ed25519 AAAAC3Nz... root@retropix";
pascual = "ssh-ed25519 AAAAC3Nz... passh@aurin";
todos = [ aurin cohete macbook retropix pascual ];
in {
"telegram-bot-token.age".publicKeys = todos;
"cohete-garage-credentials.age".publicKeys = todos;
"fichaje-ust-credentials.age".publicKeys = [ cohete pascual ];
}Cuando ejecuto agenix -r,
todos los .age se re-cifran con
esa lista. Mañana cuando enchufe vespino y añada su host key a todos, agenix -r
le da acceso instantáneo a todos los secretos del enjambre.
Para qué lo uso yo
Secretos que un servicio del sistema consume sin mi intervención.
- Bot tokens de Telegram que mis servicios leen al arrancar.
- Credenciales S3 (Garage) que cohete-blog necesita siempre.
- Auth keys de Headscale.
- Tokens de fichaje UST para que el cron de la mañana ejecute.
- Cualquier cosa que un systemd unit lea de un
EnvironmentFile.
Lo bueno
- Declarativo de pe a pa. Quién accede a qué está en
secrets.nix. Si añado un clon o lo quito, una sola edición. - Cero secretos personales en hosts headless. Cohete no tiene mi GPG. Tiene su propia host key. Mi laptop tampoco hace falta que esté on para que cohete arranque sus servicios.
- Boot-time integration. Los secretos están listos antes del arranque del servicio que los pide. Cero condiciones de carrera.
- Auditabilidad por diseño:
git log secrets.nixte dice quién tuvo acceso a qué y cuándo. - Re-key automático. Cambio una key, una sola orden
agenix -ry todo el enjambre queda alineado.
Lo malo (o lo no-encaja)
- Requiere NixOS. Si tengo un Ubuntu o un OpenWRT,
agenixno es opción. - Edición interactiva un poco torpe:
agenix -e foo.ageabre $EDITOR con el descifrado. No es feo, pero no es la experienciapass insert correo/gmailtampoco. - No hay clientes móviles. Si quieres usar el secreto
desde tu teléfono, no es
agenix, espass. - Los secretos vivos en
/run/agenix/todo el tiempo que el sistema corre. Permisos estrictos, sí, pero ahí están.passsolo vive en claro durante la duración delpass show.
La regla de oro: ¿quién consume el secreto?
Para decidir uso esta heurística sencilla:
| ¿Quién consume el secreto? | Herramienta |
|---|---|
| Yo (interactivo, una vez) | pass |
| Un servicio systemd en NixOS | agenix |
| Mi navegador (browserpass) | pass |
| Cron / timer en NixOS | agenix |
| Mi móvil | pass |
| Script puntual desde mi terminal | pass |
| Wrapper que invoca otro servicio | agenix |
Cuando el secreto lo lee humano → pass. Cuando el secreto lo lee máquina
→ agenix.
Hay casos frontera: tengo el bot token de Telegram en los
dos. En pass porque a veces lo uso
desde un script terminal con pass show telegram/bot-token. En agenix porque mi servicio fichaje-ust.service en cohete necesita leerlo
automáticamente al arrancar sin que yo esté delante.
¿Duplicación? Sí, pero deliberada. Los dos copies son la misma verdad, cifrada de dos formas distintas para dos consumidores distintos. El día que rote el token, lo cambio en los dos. Trade-off asumido.
Un ejemplo concreto del enjambre
Esta mañana descubrí que en cohete el secret cohete-garage-credentials tenía S3_ENDPOINT=http://aurin:3900, y que ReactPHP no
resolvía el hostname aurin por la mesh. La
solución fue cambiar el endpoint a la IP mesh 100.64.0.4:3900.
Si esto fuera pass, el flujo sería:
- Editar el archivo manualmente con
pass edit cohete/garage/env. - Confiar en que el servicio sabe re-leer el secret cuando alguien se lo cambia (spoiler: normalmente no).
- Restart manual del servicio.
- Acordarme de propagar el cambio al resto de máquinas si las hay.
Con agenix, el flujo fue:
echo "S3_ENDPOINT=http://100.64.0.4:3900..." | agenix -e cohete-garage-credentials.agegit commit,git push(los.agecifrados van a git).nixos-rebuilden cohete: en boot/switch,/run/agenix/se actualiza, el servicio se reinicia automáticamente porque su unit declara dependencia del secret.
Tres pasos versus cuatro. Más importante: el segundo flujo es
auditable. git log secrets/cohete-garage-credentials.age me
dice cuándo cambió, qué commit lo cambió, y el contenido vive en el repo
listo para que cualquier clon descifre con su host key.
El detalle
defensivo: ¿qué pasa si agenix cae?
Las SSH host keys viven en /etc/ssh/.
Esas no están cifradas. Si alguien tiene root en una máquina
del enjambre, tiene acceso a su host key, y por tanto a todos los
secretos cifrados para esa máquina.
¿Es eso un problema?
Es el mismo problema que tendrías con pass: si alguien tiene root en tu laptop, tiene
tu GPG private key (siempre que la hayas desbloqueado en algún momento
de la sesión, que es lo habitual).
La diferencia es que con agenix,
cada nodo solo desbloquea sus secretos. Si me comprometen
retropix, no se llevan el bot token de mi Telegram personal — se llevan
los secretos de retropix (WiFi-config, su WoL config). Si me comprometen
aurin sí se llevan todo porque aurin está en la lista todos. Trade-off conocido y asumido: clone-first implica simetría total.
Para un secreto verdaderamente sensible (la clave de mi cuenta
bancaria), no uso ni uno ni otro. Uso pass
con una passphrase distinta a la del login, no la introduzco en hosts de
larga vida, y acepto la fricción.
Resumen práctico
passes para mí.agenixes para mis máquinas.Cuando un humano teclea
pass show, eso espass. Cuando un servicio systemd se lee unEnvironmentFile, eso esagenix. Los dos pueden cohabitar en el mismo enjambre, los dos pueden tener el mismo secreto cifrado de dos formas distintas, y la duplicación deliberada es buena ingeniería, no mal diseño.
Si tu sistema es solo NixOS y solo servicios: agenix y olvida pass.
Si tu sistema es mixto (laptops, móviles, hostings ajenos): los dos.
Si tu sistema es Ubuntu/Mac sin Nix: pass y olvida agenix, no es para ti todavía. (Existe sops-nix como puente, pero esa es otra
historia.)
— Ambrosio Aurin, noche del 11 de mayo de 2026, después de arreglar el endpoint de Garage en cohete y antes de ponerme con Hydra-del-pobre.
Comentarios (0)
Sin comentarios todavia. Se el primero!
Deja un comentario