agenix icon indicating copy to clipboard operation
agenix copied to clipboard

Usage with age plugins

Open dghubble opened this issue 1 year ago • 4 comments

I'm able to use agenix (the CLI) with age or rage using the age-plugin-yubikey, but when using agenix as NixOS module and referencing secrets, agenix is not able to invoke the age or rage commands and have them detect the installed plugin.

When running sudo nixos-rebuild, age doesn't want to invoke the age-plugin-yubikey. Or using rage, it can't find the plugin at all.

  # Configure agenix
  age = {
    ageBin = "${pkgs.age}/bin/age";
    #ageBin = "${pkgs.rage}/bin/rage";
    identityPaths = [ ../identity.txt ];

    # Reference age secret files
    secrets = {
      bar = {
        file = ../secrets/bar.age;
      };
    };
  };
# age 1.1.1
$ sudo nixos-rebuild switch
...
[agenix] decrypting secrets...
decrypting '/nix/store/7daqifqz4avszwsm5r2kmf2lvqmw00zx-bar.age' to '/run/agenix.d/30/bar'...
age: error: yubikey plugin: couldn't start plugin: age-plugin-yubikey resolves to executable in current directory (./age-plugin-yubikey)
# rage 0.9.2
$ sudo nixos-rebuild switch
...
[agenix] decrypting secrets...
decrypting '/nix/store/7daqifqz4avszwsm5r2kmf2lvqmw00zx-bar.age' to '/run/agenix.d/31/bar'...
Error: Could not find '⁨age-plugin-yubikey⁩' on the PATH.
Have you installed the plugin?

Both age and rage are able to detect and use the plugin when I use them directly or when I use the agenix CLI. I've tried installing them with both environment.systemPackage and home-manage home-packages. I think this is something to do with the nixos-rebuild environment's view of the PATH, like it just can't see plugins in general. Is there a way to tell this module about these plugins that I'm missing?

Related: #115

dghubble avatar Jan 14 '24 06:01 dghubble

I’m pretty sure you should be able to wrap age and overwrite ageBin so that it always has the plugin in PATH. That could potentially work, at least as a workaround until there’s first party support for that in agenix

nrabulinski avatar Apr 10 '24 19:04 nrabulinski

I tried wrapping age to add plugins to PATH, but it does not work. However, I found a workaround;

age.ageBin = "PATH=${pkgs.age-plugin-tpm}/bin:$PATH ${pkgs.age}/bin/age"

It works because ageBin happens to be a string, not a derivation (https://github.com/ryantm/agenix/blob/e600439ec4c273cf11e06fe4d9d906fb98fa097c/modules/age.nix#L192 and https://github.com/ryantm/agenix/blob/e600439ec4c273cf11e06fe4d9d906fb98fa097c/modules/age.nix#L85).

This works for me as a workaround to use TPM.

dbeecham avatar Apr 17 '25 17:04 dbeecham

I tried wrapping age to add plugins to PATH, but it does not work. However, I found a workaround;

age.ageBin = "PATH=${pkgs.age-plugin-tpm}/bin:$PATH ${pkgs.age}/bin/age"

There is a special case where this does not work: home-manager on boot. It does work when you rebuild switch, but on boot home-manager will fail saying that plugin binary is not in path.

okt 30 08:53:50 personal-laptop systemd[1000]: Failed to start agenix activation.
okt 30 08:53:50 personal-laptop systemd[1000]: agenix.service: Failed with result 'exit-code'.
okt 30 08:53:50 personal-laptop systemd[1000]: agenix.service: Main process exited, code=exited, status=1/FAILURE
okt 30 08:53:50 personal-laptop agenix-home-manager-mount-secrets[1750]: age: report unexpected or unhelpful errors at https://filippo.io/age/report
okt 30 08:53:50 personal-laptop agenix-home-manager-mount-secrets[1750]: age: error: tpm plugin: couldn't start plugin: exec: "age-plugin-tpm": executable file not found in $PATH
okt 30 08:53:50 personal-laptop agenix-home-manager-mount-secrets[1735]: decrypting '/nix/store/f76x37p8sa392961k79i1yjgsjz9q6xh-rclone.conf.age' to '/run/user/1000/agenix.d/1/rclone_config'...
okt 30 08:53:50 personal-laptop agenix-home-manager-mount-secrets[1735]: [agenix] decrypting secrets...
okt 30 08:53:50 personal-laptop agenix-home-manager-mount-secrets[1735]: [agenix] creating new generation in /run/user/1000/agenix.d/1
okt 30 08:53:50 personal-laptop systemd[1000]: Starting agenix activation...

age.ageBin option is not available in home-manager module, but there must come to some discrepancy about what ageBin is in home-manager at boot.

Looking at the generated /nix/store/iqlxjcf7x1kj514ga6g5xwhgn5zmshzj-agenix-home-manager-mount-secrets/bin/agenix-home-manager-mount-secrets it is clear that home-manager module does not respect ageBin set in nixos module:

  age = {
    identityPaths = [age_host_identity];
    ageBin = "PATH=$PATH:${lib.makeBinPath [pkgs.age-plugin-tpm]} ${pkgs.rage}/bin/rage";
  };
#!/nix/store/cl2gkgnh26mmpka81pc2g5bzjfrili92-bash-5.3p3/bin/bash
set -o errexit
set -o nounset
set -o pipefail

export PATH="/nix/store/00bc157nm93q5fjz551fwk60ihlbilvj-coreutils-9.7/bin:$PATH"

_agenix_generation="$(basename "$(readlink "${XDG_RUNTIME_DIR}/agenix")" || echo 0)"
(( ++_agenix_generation ))
echo "[agenix] creating new generation in ${XDG_RUNTIME_DIR}/agenix.d/$_agenix_generation"
mkdir -p "${XDG_RUNTIME_DIR}/agenix.d"
chmod 0751 "${XDG_RUNTIME_DIR}/agenix.d"
mkdir -p "${XDG_RUNTIME_DIR}/agenix.d/$_agenix_generation"
chmod 0751 "${XDG_RUNTIME_DIR}/agenix.d/$_agenix_generation"

echo '[agenix] decrypting secrets...'
test -f /nix/store/9di07c07030b5flr001x5jsszaxqijp9-age_identitiy || echo '[agenix] WARNING: config.age.identityPaths entry /nix/store/9di07c07030b5flr001x5jsszaxqijp9-age_identitiy not present!'

_truePath="${XDG_RUNTIME_DIR}/agenix.d/$_agenix_generation/rclone_config"


echo "decrypting '/nix/store/f76x37p8sa392961k79i1yjgsjz9q6xh-rclone.conf.age' to '$_truePath'..."
TMP_FILE="$_truePath.tmp"

IDENTITIES=()
# shellcheck disable=2043
for identity in /nix/store/9di07c07030b5flr001x5jsszaxqijp9-age_identitiy; do
  test -r "$identity" || continue
  IDENTITIES+=(-i)
  IDENTITIES+=("$identity")
done

test "${#IDENTITIES[@]}" -eq 0 && echo "[agenix] WARNING: no readable identities found!"

mkdir -p "$(dirname "$_truePath")"
# shellcheck disable=SC2193,SC2050
[ "${XDG_RUNTIME_DIR}/agenix/rclone_config" != "${XDG_RUNTIME_DIR}/agenix/rclone_config" ] && mkdir -p "$(dirname "${XDG_RUNTIME_DIR}/agenix/rclone_config")"
(
  umask u=r,g=,o=
  test -f "/nix/store/f76x37p8sa392961k79i1yjgsjz9q6xh-rclone.conf.age" || echo '[agenix] WARNING: encrypted file /nix/store/f76x37p8sa392961k79i1yjgsjz9q6xh-rclone.conf.age does not exist!'
  test -d "$(dirname "$TMP_FILE")" || echo "[agenix] WARNING: $(dirname "$TMP_FILE") does not exist!"
  LANG=C /nix/store/gr5mgkw3bh4br8m8nz5x370ms26hdg0n-age-1.2.1/bin/age --decrypt "${IDENTITIES[@]}" -o "$TMP_FILE" "/nix/store/f76x37p8sa392961k79i1yjgsjz9q6xh-rclone.conf.age"
)
chmod 0400 "$TMP_FILE"
mv -f "$TMP_FILE" "$_truePath"

# shellcheck disable=SC2193,SC2050
[ "${XDG_RUNTIME_DIR}/agenix/rclone_config" != "${XDG_RUNTIME_DIR}/agenix/rclone_config" ] && ln -sfT "${XDG_RUNTIME_DIR}/agenix/rclone_config" "${XDG_RUNTIME_DIR}/agenix/rclone_config"


_agenix_generation="$(basename "$(readlink "${XDG_RUNTIME_DIR}/agenix")" || echo 0)"
(( ++_agenix_generation ))
echo "[agenix] symlinking new secrets to ${XDG_RUNTIME_DIR}/agenix (generation $_agenix_generation)..."
ln -sfT "${XDG_RUNTIME_DIR}/agenix.d/$_agenix_generation" "${XDG_RUNTIME_DIR}/agenix"

(( _agenix_generation > 1 )) && {
echo "[agenix] removing old secrets (generation $(( _agenix_generation - 1 )))..."
rm -rf "${XDG_RUNTIME_DIR}/agenix.d/$(( _agenix_generation - 1 ))"
}

exit 0

mihakrumpestar avatar Oct 30 '25 08:10 mihakrumpestar

After digging in the code I found that home-manager has package instead of ageBin and it expects an actual package, not path like ageBin.

For my use case (you can replace rage with age):

 let
  rage-with-tpm =
    pkgs.runCommand "rage-with-tpm"
    {
      nativeBuildInputs = [pkgs.makeWrapper];
      propagatedBuildInputs = [pkgs.rage];
    }
    ''
      makeWrapper ${pkgs.rage}/bin/rage $out/bin/rage \
        --prefix PATH : "${pkgs.lib.makeBinPath [pkgs.age-plugin-tpm]}"
    ''
    // {meta.mainProgram = "rage";};
in {
  age = {
    identityPaths = [age_host_identity];
    ageBin = lib.getExe rage-with-tpm;
  };

  home-manager.sharedModules = [
    {
      # systemctl status --user agenix.service
      age = {
        inherit (config.age) identityPaths;
        package = rage-with-tpm;
      };
    }
  ];

  environment.systemPackages = with pkgs; [
    rage-with-tpm
    age-plugin-tpm
  ];

Or you can use the age.withPlugins:

  age-with-tpm = let
    wrapped = pkgs.age.withPlugins (ps: [ps.age-plugin-tpm]);
  in
    wrapped.overrideAttrs (old: {
      meta =
        (old.meta or {})
        // {
          mainProgram = "age";
        };
    });

mihakrumpestar avatar Oct 30 '25 14:10 mihakrumpestar