Agenix y pass — dos formas de guardar secretos, y cuándo usar cada una


11 de mayo de 2026

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:

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.

Cuando necesito un secreto, abro una terminal, pass show foo/bar, me lo copia al clipboard durante 45 segundos. Listo.

Lo bueno

Lo malo (o lo no-encaja)

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:

  1. Lee el .age cifrado en build time.
  2. En boot, lo descifra usando la SSH host key (/etc/ssh/ssh_host_ed25519_key).
  3. Lo deposita en /run/agenix/ con los permisos declarados.
  4. 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.

Lo bueno

Lo malo (o lo no-encaja)

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 humanopass. Cuando el secreto lo lee máquinaagenix.

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:

  1. Editar el archivo manualmente con pass edit cohete/garage/env.
  2. Confiar en que el servicio sabe re-leer el secret cuando alguien se lo cambia (spoiler: normalmente no).
  3. Restart manual del servicio.
  4. Acordarme de propagar el cambio al resto de máquinas si las hay.

Con agenix, el flujo fue:

  1. echo "S3_ENDPOINT=http://100.64.0.4:3900..." | agenix -e cohete-garage-credentials.age
  2. git commit, git push (los .age cifrados van a git).
  3. nixos-rebuild en 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

pass es para mí. agenix es para mis máquinas.

Cuando un humano teclea pass show, eso es pass. Cuando un servicio systemd se lee un EnvironmentFile, eso es agenix. 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.

Comparte este post:

Es tu post

Estas seguro? Esto no se puede deshacer.

Comentarios (0)

Sin comentarios todavia. Se el primero!

Deja un comentario