NixOS and the SSH-caching nightmare

Hello, all!

I am having a massive headache in automatically adding/unlocking my SSH keys. Currently, only one key gets added automatically, and I have to manually add the others every session. Here’s some context:

Upon first login and opening a terminal, I am greeted with:

Starting ssh-agent ...
Identity added: /home/user/.ssh/id_ed25519 (<ssh-key-comment-of-primary-key)

Checking ssh-add and manually importing other keys:

~$ ssh-add -L
ssh-ed25519 <primary-key>

# Adding another key from my .ssh/ folder
~$ ssh-add .ssh/gitlabkey
ssh-ed25519 <gitlabkey>

# Listing the keys
~$ ssh-add -L
ssh-ed25519 <primary-key>
ssh-ed25519 <gitlabkey>

Checking keychain

~$ keychain

 * keychain 2.8.5 ~ http://www.funtoo.org
 * Found existing ssh-agent: 3130

# Adding keys using keychain
~$ keychain .ssh/gitlabkey

 * keychain 2.8.5 ~ http://www.funtoo.org
 * Found existing ssh-agent: 3130
 * Adding 1 ssh key(s): .ssh/gitlabkey
 * ssh-add: Identities added: .ssh/gitlabkey

Adding the keys manually using either method works just fine; no password is asked, but logging out or rebooting requires me to manually redo the process.


Relevant configurations

home-manager configurations

Services

# services.nix (hm)
{ config, lib, pkgs, myVars, ... }:

{
  services = {
    gpg-agent = {
      enable = true;

      enableSshSupport = false;
      enableExtraSocket = true;
      enableBashIntegration = true;
      enableZshIntegration = true;
      enableScDaemon = false; # Smartcard

      defaultCacheTtl = 34560000;
      defaultCacheTtlSsh = 34560000;
      maxCacheTtl = 34560000;
      maxCacheTtlSsh = 34560000;

      sshKeys = [ "<gpg-ssh-key" ];

      extraConfig = ''
        allow-preset-passphrase
      '';
    };

    ssh-agent = { enable = true; };
  };
}

Programs

# programs.nix (hm)
...
{
  programs = {
    ...
    gpg = {
      enable = true;

      homedir = "${config.home.homeDirectory}/.gnupg";

      mutableTrust = false; # Allow trustdb modifications
      mutableKeys = false; # Allow key modifications

      settings = {
        # General settings
        no-greeting = true; # Disable the GnuPG greeting message
        no-emit-version = true; # Do not emit the version of GnuPG
        no-comments = false; # Do not write comments in clear text signatures

        # Export options
        export-options = "export-minimal"; # Export minimal information
        keyid-format = "0xlong"; # Use long key IDs
        with-fingerprint = true; # Include key fingerprints in key listings
        with-keygrip = true; # Include key grip in key listings

        # List and verify options
        list-options = "show-uid-validity"; # Show the validity of user IDs
        verify-options = "show-uid-validity show-keyserver-urls"; # Show the validity of user IDs and keyserver URLs

        # Cipher and digest preferences
        personal-cipher-preferences = "AES256"; # Set the personal cipher preferences
        personal-digest-preferences = "SHA512"; # Set the personal digest preferences
        default-preference-list = "SHA512 SHA384 SHA256 RIPEMD160 AES256 TWOFISH BLOWFISH ZLIB BZIP2 ZIP Uncompressed"; # Set the default preference list
        cipher-algo = "AES256"; # Set the cipher algorithm
        digest-algo = "SHA512"; # Set the digest algorithm
        cert-digest-algo = "SHA512"; # Set the certificate digest algorithm
        compress-algo = "ZLIB"; # Set the compression algorithm

        # Disable weak algorithms
        disable-cipher-algo = "3DES"; # Disable 3DES
        weak-digest = "SHA1"; # Disable SHA1

        # String-to-key (S2K) settings
        s2k-cipher-algo = "AES256"; # Set the S2K cipher algorithm
        s2k-digest-algo = "SHA512"; # Set the S2K digest algorithm
        s2k-mode = "3"; # Set the S2K mode
        s2k-count = "65011712"; # Set the S2K count
      };
    };

    keychain = {
      enable = true;

      enableBashIntegration = true;
      enableZshIntegration = true;

      # agents = [ "ssh" ];

      inheritType = "any";

      keys = [
        "id_ed25519"
        "gitlabkey"
      ];

      extraFlags = [
        # "--eval"
        "--noask"
        "--quiet"
      ];
    };
    ...
    zsh = {
      enable = true;

      enableCompletion = true;
      autosuggestion = { enable = true; };
      syntaxHighlighting = { enable = true; };
      autocd = true;
      enableVteIntegration = true;

      history = {
        expireDuplicatesFirst = true;
        extended = true;
        ignoreAllDups = true;
      };

      initExtraBeforeCompInit = ''
        source ${pkgs.zsh-powerlevel10k}/share/zsh-powerlevel10k/powerlevel10k.zsh-theme
      '';

      initExtra = ''
        [[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
      '';

      oh-my-zsh = {
        enable = true;

        plugins = [
          ...
          "gpg-agent"
          "ssh-agent"
          ...
        ];
      };
    };
    ...
}

Admin user

# main-user.nix (hm)
...
{
  users.users."${myVars.users.admin.user}" = {
    description = myVars.users.admin.description;
    isNormalUser = myVars.users.admin.isNormalUser;
    extraGroups = myVars.users.admin.extraGroups ++ lib.optionals (config.networking.hostName == myVars.systems.desktop.hostname) [ "openrazer" ];
    shell = pkgs.zsh;

    packages = with pkgs; [
      # Your packages here
    ];

    openssh.authorizedKeys.keys = [
      myVars.users.admin.sshPubKey
      "<gitlabkey>"
      myVars.users.admin.gpgSshPubKey
    ];
  };
}

System-wide configurations

GPG/SSH setup

# common/networking/defaults.nix
...
{
  services.openssh = {
    enable = true;

    settings = {
      X11Forwarding = false;
      PermitRootLogin = "no";
      PasswordAuthentication = false;
      UsePAM = true;
    };

    openFirewall = true;
  };

  programs = {
    ssh = {
      startAgent = true;
      enableAskPassword = true;
      forwardX11 = false;
      setXAuthLocation = false;
    };

    gnupg = {
      agent = {
        enable = true;

        enableSSHSupport = false;

        settings = {
          default-cache-ttl = 34560000; # 400 days
          max-cache-ttl = 34560000; # 400 days
        };
      };
    };
  };

  environment.systemPackages = with pkgs; [ gnupg openssh ];
}

Environment variables

# common/environment/defaults.nix
...
{
  environment = {
    variables = {
      ...
      # Set the SSH_ASKPASS_REQUIRE
      SSH_ASKPASS_REQUIRE = "prefer";
      ...
    };
  };
}

Is there a way to automatically unlock all of my SSH keys, not just the primary one? I’ve tried a lot of suggestions that I’ve found across GitHub, Reddit, and this forum, but I haven’t found a solution that works. I’m currently using KDE Plasma 6.

I’d also like to replicate this functionality on a headless setup (if possible). On that system, I tried enabling KDE, adding the SSH keys, and selecting the ‘Remember password’ option, but the keys still aren’t added automatically.

1 Like

Shameless self-bump.

Do you know how to do it outside of NixOS?

On my old setup (Fedora KDE), IIRC, I had to fix it using a startup script and adding some lines to my .bashrc. Before that I used to use Fedora Workstation (GNOME), which took care of it all automatically, without any scripts. I thought that there was a way to do this declaratively, maybe.

I’d really like to not switch back to GNOME, tbh. But, if that’s the only way, so be it. What I don’t understand is why one key gets automatically imported while the rest don’t.

If you still have the script it should be straightforward to port the script and stuff it into your shell initialization. It’s probably less elegant than figuring out whatever is going on with keychain but it has advantage of being likely to succeed.

1 Like

Yeah, I think I do have it laying around somewhere. You’re absolutely right about the elegance, which is what I was aiming for :laughing: I guess I’ll just have to bite the bullet and go for the hacky way.

Thank you for the answer; I’ll keep this open in case anyone finds out how to do it the “proper” way in the future and decides to chime in.

2 Likes