!XLCFfvFhUkYwOMLbVx:nixos.org

agenix

330 Members
age-encrypted secrets for NixOS https://github.com/ryantm/agenix/95 Servers

Load older messages


SenderMessageTime
6 Nov 2024
@vengmark2:matrix.org@vengmark2:matrix.org joined the room.01:44:18
10 Nov 2024
@sbc64:matrix.orgsbc64 left the room.20:02:08
11 Nov 2024
@sbc64:matrix.orgsbc64 joined the room.08:46:46
12 Nov 2024
@azahi:azahi.ccazahi changed their profile picture.18:51:18
13 Nov 2024
@inayet:matrix.orginayet joined the room.22:15:17
15 Nov 2024
@rane:junkyard.systemsrane [they/them] left the room.06:22:21
@zoechi:matrix.orgzoechi joined the room.10:16:18
16 Nov 2024
@42398234iuodfhjkdsfjdsfsdffgs:matrix.org@42398234iuodfhjkdsfjdsfsdffgs:matrix.org left the room.15:37:38
@reddima100:matrix.org@reddima100:matrix.org left the room.22:06:20
18 Nov 2024
@pfhuh:matrix.orgpfhuh joined the room.15:08:46
19 Nov 2024
@martijn:plebian.nlmartijn ⚡️ changed their display name from martijn to martijn ⚡️.15:43:54
20 Nov 2024
@inayet:matrix.orginayet removed their profile picture.00:59:49
21 Nov 2024
@pie000:matrix.orgpie000 joined the room.18:08:28
@howlymowly:matrix.orgThomas m joined the room.22:19:51
22 Nov 2024
@zacharyweiss:matrix.orgZach joined the room.16:59:48
23 Nov 2024
@aidalgol:matrix.org@aidalgol:matrix.org joined the room.19:13:42
@lordkekz:matrix.orgLordKekz joined the room.19:32:59
25 Nov 2024
@nullcube:matrix.orgNullCube joined the room.09:59:40
26 Nov 2024
@ericschoville:matrix.orgEric Schoville joined the room.04:06:56
@xiaoxiangmoe:matrix.org🐰 xiaoxiangmoe joined the room.04:58:41
27 Nov 2024
@thedragon44:matrix.org@thedragon44:matrix.org left the room.23:23:40
28 Nov 2024
@denkn:denkn.at𝔇𝔢𝔫𝔎𝔫 changed their display name from DenKn to 𝔇𝔢𝔫𝔎𝔫.10:54:07
29 Nov 2024
@bl1nk:matrix.orgbl1nk changed their profile picture.01:11:33
@bl1nk:matrix.orgbl1nk changed their profile picture.01:11:53
30 Nov 2024
@ysomic:matrix.orgySomic joined the room.01:17:42
@ysomic:matrix.orgySomic

Reading this github issue, I imagine my reasoning is flawed about how home-manager integration works?

Is there some resources I could learn from, because this does not seem to work, the file is not present on my activation script nor does agenix ask for my passphrase before switching

{
  self,
  config,
  lib,
  pkgs,
  ...
}: let
  secretsDir = "${self}/data/secrets";
  commonPub = "${secretsDir}/common/pub.gpg";

  mkGpgImportsWithFunc = keys: let
    importStatements =
      lib.concatMapStrings (key: ''
        echo "Attempting to import key: ${key}"
        import_gpg_key "${config.age.secrets.${key}.path}"
      '')
      keys;
  in ''
    import_gpg_key() {
      local key_path="$1"
      if [ -f "$key_path" ]; then
        local gpg_output
        gpg_output=$(run ${pkgs.gnupg}/bin/gpg --import "$key_path" 2>&1)
        local gpg_exit_code=$?

        if echo "$gpg_output" | grep -q "not changed"; then
          echo "Warning: Key $key_name already exists and wasn't modified"
        elif echo "$gpg_output" | grep -q "secret key already exists"; then
          echo "Warning: Secret key $key_name already exists"
        fi

        if [ $gpg_exit_code -ne 0 ]; then
          echo "Error: Failed to import GPG key: $key_path"
          echo "GPG output: $gpg_output"
          failed=1
        else
          echo "GPG imported: $key_path"
        fi

        echo "Will shred GPG Key: $key_path"
        run ${pkgs.coreutils}/bin/shred -u "$key_path"
        [ "$failed" = "1" ] && return 1
      else
        echo "Path not found: $key_path"
      fi
    }

    ${importStatements}
  '';

  gpgKeys = [
    "common/gpg"
  ];

  assertions = [
    {
      assertion = lib.all (key: lib.hasAttr key config.age.secrets) gpgKeys;
      message = let
        # Find which keys are missing
        missingKeys = lib.filter (key: !(lib.hasAttr key config.age.secrets)) gpgKeys;
      in "The following GPG keys are in gpgKeys but not in age.secrets: ${toString missingKeys}";
    }
  ];
in {
  inherit assertions;

  age.secrets = {
    "common/gpg" = {
      file = "${secretsDir}/common/gpg.age";
      path = "${config.home.homeDirectory}/secrets/gpg/common.gpg.temp";
    };
  };

  programs.gpg = {
    enable = true;
    publicKeys = [
      {
        source = commonPub;
        trust = "ultimate";
      }
    ];
  };

  services.gpg-agent = {
    enable = true;
    enableBashIntegration = true;
    pinentryPackage = pkgs.pinentry-curses;
  };

  home.activation = {
    importPrivateGpgKeys =
      lib.hm.dag.entryAfter ["writeBoundary"]
      (mkGpgImportsWithFunc gpgKeys);
  };
}
01:43:57
@ysomic:matrix.orgySomic *

Reading this github issue, I imagine my reasoning is flawed about how home-manager integration works?

Is there some resources I could learn from, because this does not seem to work, the file is not present on my activation script nor does agenix ask for my passphrase before switching

{
  self,
  config,
  lib,
  pkgs,
  ...
}: let
  secretsDir = "${self}/data/secrets";
  commonPub = "${secretsDir}/common/pub.gpg";

  mkGpgImportsWithFunc = keys: let
    importStatements =
      lib.concatMapStrings (key: ''
        echo "Attempting to import key: ${key}"
        import_gpg_key "${config.age.secrets.${key}.path}"
      '')
      keys;
  in ''
    import_gpg_key() {
      local key_path="$1"
      if [ -f "$key_path" ]; then
        local gpg_output
        gpg_output=$(run ${pkgs.gnupg}/bin/gpg --import "$key_path" 2>&1)
        local gpg_exit_code=$?

        if echo "$gpg_output" | grep -q "not changed"; then
          echo "Warning: Key $key_name already exists and wasn't modified"
        elif echo "$gpg_output" | grep -q "secret key already exists"; then
          echo "Warning: Secret key $key_name already exists"
        fi

        if [ $gpg_exit_code -ne 0 ]; then
          echo "Error: Failed to import GPG key: $key_path"
          echo "GPG output: $gpg_output"
          failed=1
        else
          echo "GPG imported: $key_path"
        fi

        echo "Will shred GPG Key: $key_path"
        run ${pkgs.coreutils}/bin/shred -u "$key_path"
        [ "$failed" = "1" ] && return 1
      else
        echo "Path not found: $key_path"
      fi
    }

    ${importStatements}
  '';

  gpgKeys = [
    "common/gpg"
  ];

  assertions = [
    {
      assertion = lib.all (key: lib.hasAttr key config.age.secrets) gpgKeys;
      message = let
        # Find which keys are missing
        missingKeys = lib.filter (key: !(lib.hasAttr key config.age.secrets)) gpgKeys;
      in "The following GPG keys are in gpgKeys but not in age.secrets: ${toString missingKeys}";
    }
  ];
in {
  inherit assertions;

  age.secrets = {
    "common/gpg" = {
      file = "${secretsDir}/common/gpg.age";
      path = "${config.home.homeDirectory}/secrets/gpg/common.gpg.temp";
    };
  };

  programs.gpg = {
    enable = true;
    publicKeys = [
      {
        source = commonPub;
        trust = "ultimate";
      }
    ];
  };

  services.gpg-agent = {
    enable = true;
    enableBashIntegration = true;
    pinentryPackage = pkgs.pinentry-curses;
  };

  home.activation = {
    importPrivateGpgKeys =
      lib.hm.dag.entryAfter ["writeBoundary"]
      (mkGpgImportsWithFunc gpgKeys);
  };
}

Like to me, it looks like agenix doesn't even run at all

I do have these in my output though

  /nix/store/5qpmkj6682nzs2w4176c0fsvsfbqhddg-agenix-home-manager-mount-secrets.drv
  /nix/store/kpzmmrm5jibxc0rb8f7fg1acky8bf3b2-agenix.service.drv

The service is dead because it's asking for the passphrase. I thought it would've worked like nixos rebuild where it asks you during build time.

01:48:49
@soliprem:beeper.comSoliprem joined the room.08:08:30
@ysomic:matrix.orgySomic

I've changed it to this

{
  self,
  config,
  lib,
  pkgs,
  ...
}: let
  secretsDir = "${self}/data/secrets";
  commonPub = "${secretsDir}/common/pub.gpg";

  # We create a trigger file on activation to make sure we only run once per activation
  # triggerFile = "${config.home.homeDirectory}/.local/state/gpg-import-needed";
  versionFile = "${config.home.homeDirectory}/.local/state/gpg-import-version";

  # We create a version using a manual version and a hash
  # The manual version forces to reimport on key changes with the same name
  # Note, private keys will still not be imported, that's how GPG import works.
  manualVersion = "1";
  gpgKeys = ["common/gpg"];
  gpgKeysHash = builtins.hashString "sha256" (builtins.toString gpgKeys);
  combinedVersion = "${manualVersion}-${gpgKeysHash}";

  # Cleanup script, that will be ran in a separate systemd service
  # The reason it's in a separate one is, that we guarantee the run onSuccess or OnFailure.
  # If we don't do this, we need to jump around hoops and trap exits to make sure we shred the decrypted keys
  cleanupScriptFunc = keys:
    lib.concatMapStrings (key: ''
        if [ -f "${config.age.secrets."${key}".path}" ]; then
        echo "Will shred GPG Key: ${config.age.secrets."${key}".path}"
        ${pkgs.coreutils}/bin/shred -u "${config.age.secrets."${key}".path}"
      else
        echo "Key not found: ${config.age.secrets."${key}".path}"
      fi
    '')
    keys;

  cleanupScript = pkgs.writeShellScriptBin "cleanup-gpg-keys" ''
    ${cleanupScriptFunc gpgKeys}
  '';

  # Trigger keys, a list of strings.
  # Uses systemd condition path exists with an 'or' prefix.
  # More info: https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#Conditions%20and%20Asserts
  triggerPaths = keys:
    map (key: "|${config.age.secrets.${key}.path}") keys;

  # Main script + import script
  mkGpgImportsWithFunc = keys:
    lib.concatMapStrings (key: ''
      echo "Attempting to import key: ${key}"
      import_gpg_key "${config.age.secrets.${key}.path}"
    '')
    keys;

  importScript = pkgs.writeShellScriptBin "import-gpg-keys" ''
    # First check version
    if [ -f "${versionFile}" ]; then
      current_version=$(cat "${versionFile}")
      if [ "$current_version" = "${combinedVersion}" ]; then
        echo "GPG keys already imported at version ${manualVersion} with current keys"
        exit 0
      else
        echo "Version mismatch:"
        echo "Current: $current_version"
        echo "New: ${combinedVersion}"
      fi
    else
      echo "No version file found, will import keys"
    fi

    # Import function
    import_gpg_key() {
      local key_path="$1"
      if [ -f "$key_path" ]; then
        local gpg_output
        gpg_output=$(run ${pkgs.gnupg}/bin/gpg --import "$key_path" 2>&1)
        local gpg_exit_code=$?
        if echo "$gpg_output" | grep -q "not changed"; then
          echo "Warning: Key $key_name already exists and wasn't modified"
        elif echo "$gpg_output" | grep -q "secret key already exists"; then
          echo "Warning: Secret key $key_name already exists"
        fi
        if [ $gpg_exit_code -ne 0 ]; then
          echo "Error: Failed to import GPG key: $key_path"
          echo "GPG output: $gpg_output"
          return 1
        else
          echo "GPG imported: $key_path"
        fi
      else
        echo "Path not found: $key_path"
        return 1
      fi
    }

    # Import statements for each key
    ${mkGpgImportsWithFunc gpgKeys}

    # Create new version
    mkdir -p "$(dirname "${versionFile}")"
    echo "${combinedVersion}" > "${versionFile}"
    echo "Updated version to ${combinedVersion}"
  '';

  assertions = [
    {
      assertion = lib.all (key: lib.hasAttr key config.age.secrets) gpgKeys;
      message = let
        # Find which keys are missing
        missingKeys = lib.filter (key: !(lib.hasAttr key config.age.secrets)) gpgKeys;
      in "The following GPG keys are in gpgKeys but not in age.secrets: ${toString missingKeys}";
    }
  ];
in {
  inherit assertions;

  age.secrets = {
    "common/gpg" = {
      file = "${secretsDir}/common/gpg.age";
      path = "${config.home.homeDirectory}/secrets/gpg/common.gpg.temp";
    };
  };

  programs.gpg = {
    enable = true;
    publicKeys = [
      {
        source = commonPub;
        trust = "ultimate";
      }
    ];
  };

  services.gpg-agent = {
    enable = true;
    enableBashIntegration = true;
    pinentryPackage = pkgs.pinentry-curses;
  };

  # Systemd services
  systemd.user.services = {
    # Main import service
    import-gpg-keys = {
      Unit = {
        Description = "Import GPG keys (v${manualVersion})";
        After = ["agenix.service"];
        ConditionPathExists = triggerPaths gpgKeys;
      };

      Install = {
        WantedBy = ["default.target"];
      };

      Service = {
        Type = "oneshot";
        RemainAfterExit = true;
        ExecStart = "${importScript}/bin/import-gpg-keys";
        ExecStopPost = "${cleanupScript}/bin/cleanup-gpg-keys";

        # # Isolate the service's /tmp directory
        # PrivateTmp = true;
        # # Prevent privilege escalation
        # NoNewPrivileges = true;
        # # Make the root filesystem read-only except /var, /run, etc
        # ProtectSystem = "strict";
        # # Allow writing to home for the version file
        # # ProtectHome = "read-write";
        # # Restrict network access since we don't need it
        # RestrictAddressFamilies = "AF_UNIX";
        # # Prevent memory exploits
        # MemoryDenyWriteExecute = true;
      };
    };
  };
}

I'm just wondering, is there still a way to use agenix for home with an ssh key that has a passphrase?

15:05:30
1 Dec 2024
@vengmark2:matrix.org@vengmark2:matrix.org left the room.00:10:17

Show newer messages


Back to Room ListRoom Version: 6