1password's ssh agent and nix
A while ago, 1password introduced an SSH agent, and I’ve been using it for a while now. The following describe how I’ve configured it with nix
. All my ssh keys are in 1password, and it’s the only ssh agent I’m using at this point.
Personal configuration
I have a personal 1password account, and I’ve created a new SSH key in it that I use for both authenticating to github and to sign commits. I use nix-darwin and home-manager to configure my personal machine.
This is how I configure ssh:
programs.ssh = {
enable = true;
forwardAgent = true;
serverAliveInterval = 60;
controlMaster = "auto";
controlPersist = "30m";
extraConfig = ''
IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
'';
matchBlocks = {
"github.com" = {
hostname = "github.com";
user = "git";
forwardAgent = false;
extraOptions = { preferredAuthentications = "publickey"; };
};
};
};
The configuration for git:
{ lib, pkgs, config, ... }:
let
sshPub = builtins.fromTOML (
builtins.readFile ../../configs/ssh-pubkeys.toml
);
in
{
home.file.".ssh/allowed_signers".text = lib.concatMapStrings (x: "franck@fcuny.net ${x}\n") (with sshPub; [ ykey-laptop ykey-backup op ]);
programs.git = {
enable = true;
userName = "Franck Cuny";
userEmail = "franck@fcuny.net";
signing = {
key = "key::${sshPub.op}";
signByDefault = true;
};
extraConfig = {
gpg.format = "ssh";
gpg.ssh.allowedSignersFile = "~/.ssh/allowed_signers";
gpg.ssh.program = "/Applications/1Password.app/Contents/MacOS/op-ssh-sign";
};
}
In the repository with my nix configuration, I’ve a file ssh-pubkeys.toml
that contains all the public ssh keys I keep track of (mine and a few other developers). Keys from that file are used to create the file ~/.ssh/allowed_signers
that is then used by git
(for example git log --show-signature
) when I want to ensure commits are signed with a valid key.
ssh-pubkeys.toml
looks like this:
# yubikey key connected to the laptop
ykey-laptop="ssh-ed25519 ..."
# backup yubikey key
ykey-backup="ssh-ed25519 ..."
# 1password key
op="ssh-ed25519 ..."
And the following is for zsh
so that I can use the agent for other commands that I run in the shell:
programs.zsh.envExtra = ''
# use 1password ssh agent
# see https://developer.1password.com/docs/ssh/get-started#step-4-configure-your-ssh-or-git-client
export SSH_AUTH_SOCK=~/Library/Group\ Containers/2BUA8C4S2C.com.1password/t/agent.sock
'';
And that’s it, this is enough to get use the agent for all my personal use cases.
Work configuration
The work configuration is slightly different. Here I want to use both my work and personal keys so that I can clone some of my personal repositories on the work machine (for example my emacs configuration). We also use both github.com and a github enterprise instance and I need to authenticate against both.
I’ve imported my existing keys into 1password, and I keep the public keys on the disk: $HOME/.ssh/work_gh.pub
and $HOME/.ssh/personal_gh.pub
. I’ve removed the private keys from the disk.
This is the configuration I use for work:
programs.ssh = {
enable = true;
forwardAgent = true;
serverAliveInterval = 60;
controlMaster = "auto";
controlPersist = "30m";
extraConfig = ''
IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
'';
matchBlocks = {
"personal" = {
hostname = "github.com";
user = "git";
forwardAgent = false;
identifyFile = "~/.ssh/personal_gh.pub";
identitiesOnly = true;
extraOptions = { preferredAuthentications = "publickey"; };
};
"work" = {
hostname = "github.com";
user = "git";
forwardAgent = false;
identifyFile = "~/.ssh/work_gh.pub";
identitiesOnly = true;
extraOptions = { preferredAuthentications = "publickey"; };
};
"github.enterprise" = {
hostname = "github.enterprise";
user = "git";
forwardAgent = false;
identifyFile = "~/.ssh/work_gh.pub";
identitiesOnly = true;
extraOptions = { preferredAuthentications = "publickey"; };
};
};
};
I also create a configuration file for the 1password agent, to make sure I can use the keys from all the accounts:
# Generate ssh agent config for 1Password - I want both my personal and work keys
home.file.".config/1Password/ssh/agent.toml".text = ''
[[ssh-keys]]
account = "my.1password.com"
[[ssh-keys]]
account = "$work.1password.com"
'';
Then the ssh configuration:
{ config, lib, pkgs, ... }:
let
sshPub = builtins.fromTOML (
builtins.readFile ../etc/ssh-pubkeys.toml
);
in
{
home.file.".ssh/allowed_signers".text = lib.concatMapStrings (x: "franck@fcuny.net ${x}\n") (with sshPub; [ work_laptop op ]);
programs.git = {
enable = true;
signing = {
key = "key::${sshPub.op}";
signByDefault = true;
};
extraConfig = {
gpg.format = "ssh";
gpg.ssh.allowedSignersFile = "~/.ssh/allowed_signers";
gpg.ssh.program = "/Applications/1Password.app/Contents/MacOS/op-ssh-sign";
url = {
"ssh://git@github.enterprise/" = {
insteadOf = "https://github.enterprise/";
};
};
};
};
}
Now, when I clone a repository, instead of doing git clone git@github.com/$WORK/repo
I do git clone work:/$WORK/repo
.
Conclusion
I’ve used yubikey to sign my commits for a while, but I find the 1password ssh agent a bit more convenient. The initial setup for yubikey was not as straightforward (granted, it’s a one time thing per key).
On my personal machine, my $HOME/.ssh
looks as follow:
➜ ~ ls -l ~/.ssh ~
total 16
lrwxr-xr-x@ 1 fcuny staff 83 Nov 6 17:03 allowed_signers -> /nix/store/v9qhbr2vb7w6bd24ypbjjz59xis3g8y2-home-manager-files/.ssh/allowed_signers
lrwxr-xr-x@ 1 fcuny staff 74 Nov 6 17:03 config -> /nix/store/v9qhbr2vb7w6bd24ypbjjz59xis3g8y2-home-manager-files/.ssh/config
-rw-------@ 1 fcuny staff 828 Nov 13 17:53 known_hosts
When I create a new commit, 1password ask me to authorize git to use the agent and sign the commit. Same when I want to ssh to a host.
When I’m working on the macbook, I use touch ID to confirm, and when the laptop is connected to a dock, I need to type my 1password’s password to unlock it and authorize the command.
There’s a cache in the agent so I’m not prompted too often. I find this convenient, I will never have to copy my ssh key when I get a new laptop, since it’s already in 1password.
The agent has worked flawlessly so far, and I’m happy with this setup.
December 02, 2023