|
1 | 1 | +++ |
2 | 2 | title = 'Proton Pass CLI: switching to the Secret Service keyring' |
3 | 3 | date = 2026-03-24T09:00:00Z |
4 | | -draft = true |
| 4 | +draft = false |
5 | 5 | tags = ['nixos', 'linux', 'security', 'ssh'] |
6 | 6 | +++ |
7 | 7 |
|
8 | 8 | In my [previous post](/posts/2026-03-07-proton-pass-migration/) I documented the migration from 1Password to Proton Pass and landed on a workaround for the main annoyance: `pass-cli` stores its local encryption key in the Linux kernel keyring, which is wiped on every reboot, forcing a manual `pass-cli login` before SSH keys could be loaded each morning. |
9 | 9 |
|
10 | | -Proton has now merged a change that makes this unnecessary. |
| 10 | +Fortunately, Proton has now merged a change that makes this unnecessary and allows for a setup much closer to that of 1Password. |
11 | 11 |
|
12 | 12 | ## The change |
13 | 13 |
|
14 | 14 | `pass-cli` now supports a `PROTON_PASS_LINUX_KEYRING` environment variable that selects which keyring backend it uses to store the encryption key ([docs](https://protonpass.github.io/pass-cli/get-started/configuration/#1-keyring-storage-default)): |
15 | 15 |
|
16 | | -| Value | Backend | Persists across reboots | |
17 | | -|---|---|---| |
18 | | -| `kernel` (default) | Linux kernel user keyring | No | |
19 | | -| `dbus` | D-Bus Secret Service | Yes | |
| 16 | +| Value | Backend | Persists across reboots | |
| 17 | +|--------------------|---------------------------|-------------------------| |
| 18 | +| `kernel` (default) | Linux kernel user keyring | No | |
| 19 | +| `dbus` | D-Bus Secret Service | Yes | |
20 | 20 |
|
21 | 21 | Setting it to `dbus` makes `pass-cli` use the D-Bus Secret Service instead of the kernel keyring. On a desktop machine with GNOME Keyring running and unlocked at login, the encryption key now survives reboots. |
22 | 22 |
|
23 | | -## The NixOS config change |
24 | | - |
25 | | -My setup already had GNOME Keyring running and unlocked at login via PAM, so no additional plumbing was needed. The full change in my NixOS config was: |
26 | | - |
27 | | -```nix |
28 | | -# profiles/base/proton-pass/home.nix |
29 | | -
|
30 | | -home.sessionVariables = { |
31 | | - PROTON_PASS_LINUX_KEYRING = "dbus"; |
32 | | -}; |
33 | | -
|
34 | | -programs.zsh.shellAliases = { |
35 | | - # login step is no longer needed after reboot |
36 | | - pass-ssh-load = "pass-cli ssh-agent load"; |
37 | | -}; |
38 | | -``` |
| 23 | +This opens up the option for automatically loading our SSH keys on startup once the keyring is unlocked. |
39 | 24 |
|
40 | | -`home.sessionVariables` is picked up by login shells and PAM sessions, so the variable is available to `pass-cli` wherever it runs. |
41 | | - |
42 | | -The `pass-ssh-load` alias still exists, as loading SSH keys into the agent is still a manual step since `pass-cli ssh-agent load` is a one-shot command rather than a persistent process, but the `pass-cli login` prefix is gone. |
| 25 | +## The NixOS config change |
43 | 26 |
|
44 | | -## Auto-loading SSH keys on login |
| 27 | +My setup already had GNOME Keyring running and unlocked at login via PAM, so no additional plumbing was needed. If you're setting up from scratch check the [NixOS wiki](https://wiki.nixos.org/wiki/Secret_Service) for more information on setting this up. |
45 | 28 |
|
46 | | -With the authentication problem solved, I can now auto-load SSH keys into the agent at session start. `pass-cli ssh-agent load` is a one-shot command, so a systemd user service is the right fit. It runs after login, has access to the D-Bus session bus, and can be ordered after both the keyring and the SSH agent are ready. |
| 29 | +Here is the full `profiles/base/proton-pass/home.nix` after all the changes: |
47 | 30 |
|
48 | 31 | ```nix |
49 | 32 | # profiles/base/proton-pass/home.nix |
50 | | -
|
51 | | -systemd.user.services.proton-pass-ssh-load = { |
52 | | - Unit = { |
53 | | - Description = "Load Proton Pass SSH keys into agent"; |
54 | | - After = [ |
55 | | - "graphical-session.target" |
56 | | - "gnome-keyring-daemon.service" |
57 | | - "ssh-agent.service" |
58 | | - ]; |
59 | | - }; |
60 | | - Service = { |
61 | | - Type = "oneshot"; |
62 | | - ExecStart = "${pkgs-unstable.proton-pass-cli}/bin/pass-cli ssh-agent load"; |
| 33 | +{ |
| 34 | + config, |
| 35 | + pkgs, |
| 36 | + pkgs-unstable, |
| 37 | + ... |
| 38 | +}: |
| 39 | +
|
| 40 | +let |
| 41 | + # Version 1.8.0 introduces PROTON_PASS_LINUX_KEYRING support. |
| 42 | + # Remove this override once 1.8.0 reaches nixpkgs-unstable. |
| 43 | + proton-pass-cli = pkgs-unstable.proton-pass-cli.overrideAttrs (old: { |
| 44 | + version = "1.8.0"; |
| 45 | + src = pkgs-unstable.fetchurl { |
| 46 | + url = "https://proton.me/download/pass-cli/1.8.0/pass-cli-linux-x86_64"; |
| 47 | + hash = "sha256-M7zWxVYHHjM86/l3K+0AR8QceiydP0n0sXj9rSctaeI="; |
| 48 | + }; |
| 49 | + }); |
| 50 | +in |
| 51 | +
|
| 52 | +{ |
| 53 | + home.packages = [ proton-pass-cli ]; |
| 54 | +
|
| 55 | + # Use the D-Bus Secret Service (GNOME Keyring) as the keyring backend so |
| 56 | + # that the pass-cli encryption key persists across reboots. |
| 57 | + home.sessionVariables = { |
| 58 | + PROTON_PASS_LINUX_KEYRING = "dbus"; |
63 | 59 | }; |
64 | | - Install = { |
65 | | - WantedBy = [ "graphical-session.target" ]; |
| 60 | +
|
| 61 | + # Auto-load SSH keys into the agent at login. |
| 62 | + systemd.user.services.proton-pass-ssh-load = { |
| 63 | + Unit = { |
| 64 | + Description = "Load Proton Pass SSH keys into agent"; |
| 65 | + After = [ |
| 66 | + "graphical-session.target" |
| 67 | + "gnome-keyring-daemon.service" |
| 68 | + "ssh-agent.service" |
| 69 | + ]; |
| 70 | + }; |
| 71 | + Service = { |
| 72 | + Type = "oneshot"; |
| 73 | + Environment = [ |
| 74 | + "PROTON_PASS_LINUX_KEYRING=dbus" |
| 75 | + "SSH_AUTH_SOCK=%t/ssh-agent" |
| 76 | + ]; |
| 77 | + ExecStart = "${proton-pass-cli}/bin/pass-cli ssh-agent load"; |
| 78 | + }; |
| 79 | + Install = { |
| 80 | + WantedBy = [ "graphical-session.target" ]; |
| 81 | + }; |
66 | 82 | }; |
67 | | -}; |
| 83 | +} |
68 | 84 | ``` |
69 | 85 |
|
| 86 | +The `overrideAttrs` block pins `pass-cli` to 1.8.0, the first release with `PROTON_PASS_LINUX_KEYRING` support. It can be removed once 1.8.0 reaches `nixpkgs-unstable` and `pkgs-unstable.proton-pass-cli` can be used directly. |
| 87 | + |
| 88 | +The systemd service sets `PROTON_PASS_LINUX_KEYRING` and `SSH_AUTH_SOCK` directly in the `Service` block because user units don't inherit the session environment. `%t` is the systemd specifier for the user runtime directory (`/run/user/<uid>`), so `%t/ssh-agent` resolves to the same socket that `programs.ssh.startAgent` creates without hardcoding a UID. |
| 89 | + |
70 | 90 | The `After` ordering ensures the service waits for GNOME Keyring to be unlocked (so `pass-cli` can retrieve its encryption key via D-Bus) and for the SSH agent to be running (so there is a socket to load keys into). The store path is used directly in `ExecStart` rather than relying on `$PATH`, since systemd services don't inherit the user's shell environment. |
71 | 91 |
|
72 | | -Keys injected by `pass-cli ssh-agent load` live only in the agent's memory for the session. The keyring has no record of them, so the service needs to run on every login, which is exactly what `WantedBy = graphical-session.target` gives you. |
| 92 | +Keys injected by `pass-cli ssh-agent load` live only in the agent's memory for the session. The keyring has no record of them, so the service needs to run on every login, which is exactly what `WantedBy = graphical-session.target` provides. |
73 | 93 |
|
74 | 94 | ## One caveat |
75 | 95 |
|
|
0 commit comments