Add SSH key to agent at login using KWallet

Hey everyone,

Goal
I’m a new NixOS user and my goal is to add my SSH key automatically to the SSH-agent using the KWallet at login time so that I just enter my passphrase at the first login after boot, and afterwards can just use it freely. I’m trying to use KWallet for this, as I think using just ssh-agent for this would mean one starts a new ssh-agent every time you login after a suspend session as well, if I understand correctly. See link and more specifically the last paragraph below the header ’ ssh-key with passphrase, with ssh-agent’ in the first answer.
I managed to get this working using this link on a different machine with debian, but now I’m trying to replicate the behaviour on this NixOS machine.

Problems
However, I’m running into 2 problems:

  1. I can’t seem to get the ssh-agent to be KWallet / can’t seem to set ssh.askPassword to ksshaskpass successfully in my configuration.nix → If I try to set it, it still will just ask me in the console for the passphrase.
  2. I can’t seem to get the script to run at login time, I think, because nothing happens.

What I’ve tried:
I’ve added ksshaskpass to systemPackages and set programs to the following:

programs = {
  ssh.startAgent = true;
  ssh.askPassword = pkgs.lib.mkForce "${pkgs.ksshaskpass.out}/bin/ksshaskpass";
};

and finally a systemd service to perform the automatic adding of the SSH keys at login (I have no reason to believe this last part works either, but the ksshaskpass also doesn’t seem to work when I use it in a regular terminal) :

systemd.user.services.add_ssh_keys = {
  script = ''
    ssh-add $HOME/.ssh/<key> # where <key> is of course replaced by the actual key.
  '';
  wantedBy = [ "multi-user.target" ];  #starts after login
};

Before, I tried a different way to run the script at startup, using programs.bash.loginShellInit, but this way every terminal that one would start also somehow runs the script.

Would you guys have an idea what could be wrong here? Cheers in advance!

More info
I’m running NixOS 23.05, with the unstable channel and home-manager installed (got git configured there) and with KDE Plasma installed with sddm and X11.

Other resources:
On ssh-agent
On ksshaskpass (see last messages)

6 Likes

Thanks @tobiasvandriessel for a very detailed write up, it matches my current situation as well :smiley:

I think I solved part of the puzzle, ksshaskpass seemingly non-working. It seems that the default behavior for ssh agent is to only use askPassword if no tty is present. That is, it prompts you in console because you launch it from console. This is controlled by SSH_ASKPASS_REQUIRE=never|force|prefer environment variable.

So, I added

  environment = {
    sessionVariables = {
      SSH_ASKPASS_REQUIRE="prefer";
    };
  };

to my configuration to solve this particular issue. Not sure if there’s some more specific nixos option here.

4 Likes

Big help from both of you, thanks!

While this does get me close, notably using ssh directly does not need a password. Ssh via git (ie git fetch, however, still asked for a password. Running ssh-add manually solved that across all terminals, but i struggled to automate that with the service, as provided at least.

To contribute, i did at least make some progress on a couple points. Keep in mind there’s probably many things wrong with this, as this was primarily trial and error.

  systemd.user.services.add_ssh_keys = {
    script = ''
      eval `${pkgs.openssh}/bin/ssh-agent -s`
      export SSH_ASKPASS="${pkgs.ksshaskpass}/bin/ksshaskpass"
      export SSH_ASKPASS_REQUIRE="prefer"
      ${pkgs.openssh}/bin/ssh-add $HOME/.ssh/key
    '';
    wantedBy = [ "default.target" ];
  };

Notably, i found that multi-user.target wasn’t actually running this at all for me. default.target seemed to be the only thing that actually ran it.

Also, when i did get it to run i noticed that ssh-add was failing. Adding ${pkgs.openssh}/bin/ssh-add solved that. Next i noticed that ssh-add was failing to connect to an agent, so i tried starting the agent, and then it started asking for a pass over the tty, odd since it doesn’t normally. So setting SSH_ASKPASS and SSH_ASKPASS_REQUIRE were test hacks to explore that idea.

All together this got me to have KWallet automatically open at login asking for a pass, which… kinda isn’t what i want. I imagine the context of running this as a service might be difficult? Or i’m just doing it very ignorantly (probably that).

Alas i give up for now. This post has got it mostly working so i can just run ssh-add manually once at login and i’m set. I bet i could also put this in my shell for ease.

Appreciate the post, and good luck to future googlers.

1 Like

Thanks @matklad @yak for your further investigations,

FYI: After posting this and not getting anywhere, I switched to Gnome and later to just using the 1Password SSH agent. So, I’ve not fixed the problem, but rather just switched to a different approach.

Cheers.

You want to do wantedBy = [ "graphical-session.target" ]; and probably after = [ "kwin_wayland.service" ]; (or something like that) in order for it to be pulled in late enough. multi-user.target is a system-level target, not user-level and default.target is too early for kwallet to be running.

I managed to make it work for me. It delays the KDE loading screen, so it’s not perfect, but I don’t have to type in the password. I used a user systemd service with home-manager:

  systemd.user.services.add_ssh_keys = {
    Unit.Description = "Add SSH keys";
    Unit.After = [ "plasma-kwallet-pam.service" "sops-nix.service" "plasma-kwin_x11.service" "plasma-kwin_wayland.service" "plasma-polkit-agent.service"];
    Install.WantedBy = [ "graphical-session.target" ];
    Service = {
      ExecStart = "${pkgs.writeShellScript "add_ssh_keys" ''
      export SSH_ASKPASS="${pkgs.ksshaskpass}/bin/ksshaskpass"
      export SSH_ASKPASS_REQUIRE="prefer"
      ${pkgs.openssh}/bin/ssh-add ${secretsUserPath}/github.key
    ''}";
    };
  };

None of these seem to work for me on unstable:

  • Replacing services.xserver.desktopManager.gnome.enable = true; with services.xserver.desktopManager.plasma6.enable = true; seems to have caused SSH_ASKPASS to be set to […]/ksshaskpass.
  • Setting environment.sessionVariables.SSH_ASKPASS_REQUIRE = "prefer"; applies that globally.
  • Running ssh-add after a restart reports that it successfully added the key.

Even so, git pull still pops up a window every time asking for the same SSH key password.

Update: Looks like programs.gnupg.agent.enableSSHSupport might be interfering.

Update 2: Swapping to the “standard” SSH agent fixed one issue and caused another. Now Git no longer asks for the password every time I pull, but instead every time I commit with SSH signing enabled.

Update 3: Hah, nope, I just have to ssh-add every time I boot. D’oh. Now to figure out how to do that.

Based on mostly this thread and the SSH module I’ve ended up with this:

systemd.user.services.add-ssh-key = {
  inherit (config.systemd.user.services.ssh-agent) unitConfig wantedBy;
  bindsTo = [
    "ssh-agent.service"
  ];
  environment.SSH_AUTH_SOCK = "/run/user/%U/ssh-agent";
  path = [
    options.programs.ssh.package.value
  ];
  script = "${options.programs.ssh.package.value}/bin/ssh-add";
  serviceConfig = {
    CapabilityBoundingSet = "";
    LockPersonality = true;
    NoNewPrivileges = true;
    ProtectClock = true;
    ProtectHostname = true;
    PrivateNetwork = true;
    ProtectKernelLogs = true;
    ProtectKernelModules = true;
    ProtectKernelTunables = true;
    RestrictAddressFamilies = "AF_UNIX";
    RestrictNamespaces = true;
    SystemCallArchitectures = "native";
    SystemCallFilter = "~@clock @cpu-emulation @debug @module @mount @obsolete @privileged @raw-io @reboot @resources @swap";
    UMask = "0777";
  };
};

Needs more testing, but IIUC:

  • Inherits ssh-agent’s wantedBy, so it should start during the same phase.
  • unitConfig specifies that this user service applies to non-system users, so it should work for any regular users.
  • Unfortunately has to guess at the SSH agent socket file location. I’d be interested if you know a less magic way to get this value.
  • Uses the SSH package configured by the system.
  • Calls ssh-add without any parameters, which does the right thing on my system. YMMV :person_shrugging:, but you’ll need to use %h to refer to the home directory of the service user if you want to load a specific key.
  • Specifies a bunch of limitations, so the service can’t do much fishy. This still needs a more thorough inspection of systemd-analyze security --user add-ssh-key.service.