Ken Muse

Automatic SSH Commit Signing With 1Password


In a previous post, I discussed how to automate SSH commit signing. I try to keep things simple whenever I can. It makes everything less challenging to maintain. I mentioned that it’s common to have an SSH key per system, but that’s not always what’s needed. For example, if you utilize tools to maintain your identity details and passwords, then it may make sense to centralize your keys. In my case, I utilize 1Password to manage my SSH signing keys. As a result, I can choose to carry the same identity (and key) across multiple systems.

Allowing this identity to pass through to my dev containers requires a some minor scripting considerations. The general approach is the same as my previous post, but takes advantage of the fact that the key will always be present if 1Password is running.

The static .gitconfig

First, you to make sure that my .gitconfig is using the correct signing program. 1Password uses op-ssh-sign for this purpose. This saves you from needing to configure SSH_AUTH_SOCK in the environment. I can either use the command line (git config set gpg.ssh.program <app>) or directly edit .gitconfig:

macOS

1[gpg "ssh"]
2  program = /Applications/1Password.app/Contents/MacOS/op-ssh-sign

Linux

1[gpg "ssh"]
2  program = /opt/1Password/op-ssh-sign

Windows

1[gpg "ssh"]
2  program = C:\\Users\\<username>\\AppData\Local\\1Password\\app\\8\\op-ssh-sign.exe

If you know that 1Password is always installed, you can hard-code the path and the public key. Just put the platform-specific details in a separate file. Then use includeIf to load the configuration based on the existence of specific paths. For example:

 1[includeIf "gitdir/i:C:/"]
 2  path = ~/.gitconfig-windows
 3  
 4[includeIf "gitdir/i:/Users/"]
 5  path = ~/.gitconfig-macos
 6
 7[includeIf "gitdir/i:/home/"]
 8  path = ~/.gitconfig-linux
 9
10[includeIf "gitdir/i:/workspaces/"]
11  path = ~/.gitconfig-devcontainer

Dynamic detection

But what if you only want to use 1Password in certain environments? At the moment, I prefer to handle this configuration dynamically using the CLI. That way, I only add support for 1Password if it’s installed and configured. In a desktop environment, I can use the path to the op-ssh-sign application. When running in a dev container, I take a different approach. In that case, 1Password is not installed but the SSH agent from the host environment is available within the container. Since I have access to the agent, I don’t need to configure op-ssh-sign.

You can dynamically detect the environment configuration a few different ways. For example, you can test for the presence of the op-ssh-sign binary. For example:

macOS

1if [ -f "/Applications/1Password.app/Contents/MacOS/op-ssh-sign" ]; then
2  git config --global gpg.ssh.program "/Applications/1Password.app/Contents/MacOS/op-ssh-sign"
3if

Linux

1if [ -f "/op1/1Password/op-ssh-sign" ]; then
2  git config --global gpg.ssh.program "/op1/1Password/op-ssh-sign"
3fi

Windows

1$signApp = "${env:LOCALAPPDATA}\1Password\app\8\op-ssh-sign.exe"
2if (Test-Path -Path $signApp) {
3  git config --global gpg.ssh.program $signApp
4}

Signing

The next steps is to get my signing key. There are two general approaches. The first options is to hard-code the public key reference into .gitconfig. This is the simplest approach if you know that you’ll always be using 1Password or have a fixed key. The second option is to dynamically detect the key.

The approach I’ll use lists all of the current SSH keys. It assumes that I’ve named the key in 1Password using the email address associated with my Git commits. The code looks for a key that contains the email address being used for Git commits from the current (dotfiles) repo. In case multiple keys are found, it returns just the first result.

macOS/Linux

1find_signing_key() { 
2  echo "$(ssh-add -L | grep "$(git config --get user.email)" | head -n 1 )"
3}

Windows

1$email=(git config --get user.email)
2$key=(ssh-add -L | Select-String -Pattern $email -SimpleMatch | Select-Object -ExpandProperty Line -First 1)

If no key is retrieved, the user.signingkey is not updated. If a key is retrieved, it is added to the Git configuration.

macOS/Linux

1local key=$(find_signing_key)
2if [ -n $key ]; then
3  git config --global user.signingkey "key::$key"
4fi

Windows

1if (-Not [string]::IsNullOrEmpty($key)){
2    git config --global user.signingkey "key::$key"
3}

This approach works well for situations where you want to use the 1Password key if it’s available, but fall back to a local key that follows the same naming convention when 1Password is not installed. If you want to handle the case where no key is available, then you coud default commit.gpgsign to false and update it to true if a key is found.

The final steps

You still need to ensure that you’ve configured the other settings required for signing commits. If you’re not familiar with those, you can refer to my previous post.

1git config --global gpg.format ssh
2git config --global commit.gpgsign true
3git config --global gpg.ssh.allowedSignersFile "$signers"

With all of this configured, your commits should now be automatically signed.