How to configure individual Git repositories to use specific SSH keys. This is useful if you have more than one account on a forge, for example a personal and work account.
Background
Typically when pushing and pulling changes to a forge such as GitHub, GitLab or Codeberg you use an SSH (Secure SHell) key to authenticate that you have permission to access the repository.
SSH Keys
Concept
SSH keys are, in conjunction with “keychains”, used to save you having to enter a password each time you connect from one computer to another. They are generated on your computer and consist of two parts, a private key which remains on your computer and a public key which you place on remote computers you wish to connect to. There is a password associated with your key which is required to “unlock” your private key on your computer. Only an unlocked private key will match with a public key. Think of the public key as the lock on your front door, and the private key the key you carry on your traditional, physical, keychain/keyring. Only when the two match will things be unlocked, although you have to unlock your private key when you want to use it just as you have to get your keys out of your pocket (although “keychains” help with this).
Generation
There are different algorithms for generating SSH key pairs. DSA is no longer considered secure and RSA keys should have at least 2048-bits if not 4096-bits. A good choice these days is to use an elliptic curve based key such as ed25519 as they are shorter and faster. For more on why you should use this key see the article Upgrade your SSH keys!.
To generate a key use the following command entering a secure (i.e. long) password.
ssh-keygen -o -a 100 -t ed25519
You will be prompted for a filename to save your keys to, so you should know where to find them (the default is ~/.ssh/id_ed25519[.pub]
). You have a private key ~/.ssh/id_ed25519
and a public ~/.ssh/id_ed25519.pub
and we will use this to set up authentication on your Git Forge.
Forge Configuration
Under your account settings on your chosen Git Forge navigate to Settings > SSH and GPG Keys and select Add New Key on (GitHub). On GitLab navigate to Preferences > SSH Keys GitLab), this page allows you to add a new key.
You need to copy and paste your public key into the Key
box on these pages and give it a name (typically the hostname of your computer is a good choice). To view your public key simply use cat
and copy and paste it. You can optionally choose to set an expiration date for your key which is good practice but means you have to generate new keys in the future.
cat ~/.ssh/id_ed25519.pub
Git Global SSH Configuration
Typically your global configuration for which key to use is set in ~/.ssh/config
with an entry similar to the below.
Host github.com
User git
Port 22
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_ed25519
Here it uses the User name git
on port 22
. The preferred authentication method is using a publickey
and the private key used is stored locally at ~/.ssh/id_ed25519
.
When asked to connect to a forge using SSH (e.g. git pull
or git push
) will look through the ~/.ssh/config
file to see if there is a configuration section that matches the target and if so use the configuration defined there-in. You will then be prompted for your SSH private key password.
What are Keychains?
You may be wondering how an SSH key makes your life easier, you are still prompted to enter a password when trying to interact with a Git Forge, or use it in a more traditional manner to connect over SSH to another server. This is where the magic of a “keychain” steps in to make your life easier, you still have to enter a password but only once to add your SSH key to the “keychain”. Typically keychains are front-ends for interacting with and managing SSH agent. The name is apt since you add your SSH key to the keychain once, typically on log-in, and are asked for your password to unlock it and then stores it in the SSH agent. Then each time SSH requires an SSH key it retrieves it from the keychain rather than prompting you for a password.
There are many different implementations of keychain such as the Funtoo Keychain Project, Seahorse the GNOME GUI management tool,
Git Per Repository Configuration
We now get to the meat of this post, how to configure individual repositories to use specific SSH keys. This may be desirable if you have two accounts on the same forge e.g. both on GitHub.com or both on GitLab.com? As of Git 2.10.0 you can configure each repository to use a specific key (source). At the command line…
cd a/git/repository
git config core.sshCommand "ssh -i ~/.ssh/work_ed25519 -F /dev/null"
git config --local user.name "Username"
git config --local user.email "repos@username.com"
This adds the following to the repositories configuration which is stored under .git/config
and you can of course enter this directly to the configuration file yourself.
[core]
sshCommand = ssh -i ~/.ssh/work_ed25519 -F /dev/null
[user]
name = Username
email = repos@username.com
What is this doing? Well it’s instructing Git to run ssh using the private key file (with the -i
flag to specify the identity_file
) that is located at ~/.ssh/work_ed25519
. Providing you have…
- Already uploaded the public key (
work_ed25519.pub
) to your GitHub account. - Stored this key in a Keychain as described above.
…you shouldn’t be prompted for a password.
You can now configure, on a repository basis, which SSH key is used by Git when pushing/pulling changes from the remote origin
(typically a forge such as GitHub, GitLab, Codeberg or so forth). If however you have multiple projects you wish to setup with an alternative SSH key configuration it can be tedious to configure each repository. Thankfully Git >= 2.13 introduced Conditional includes to the configuration.
Conditional Includes
You global configuration is stored in ~/.gitconfig
and defines key variables such user
and name
, the default editor
and many other options, including a customised sshCommand
as was added above to a local .git/config
file.
Git 2.13 introduced the aforementioned Conditional includes which works “_by setting a includeIf.<condition>.path
variable to the name of the file to be included.”. For our current case-use the <condition>
we are interested in is whether the path
, which is interpreted as a pattern, is a gitdir
then we include what follows.
For example, we place all of our work related Git repositories under the ~/work/
directory and wish to use ~/.ssh/work_ed25519
for these and keep all of our personal repositories elsewhere and wish to use our main ~/.ssh/id_ed25519
key for those.
Out ~/.gitconfig
should look like
[user]
name = Your Name
email = your.personal@email.com
[includeIf "gitdir:~/work/"] # Directory paths ending in '/** has the globbing wildcard '**' added by default.
path = ~/work/.gitconfig_work
[core]
sshCommand = ssh -i ~/.ssh/id_ed25519 -F /dev/null
Then our ~/work/.gitconfig_work
can contain the alternative values we wish to use for all repositories under the ~/work/
directory.
[user]
name = Your Name
email = your.work@email.com
[core]
sshCommand = ssh -i ~/.ssh/work_ed25519 -F /dev/null
Commit verification with SSH
Verification of commits is a useful security feature, but beyond the scope of this article but as doing so with SSH keys is a recently supported feature on GitHub (see blog SSH commit verification now supported) I felt it worth mentioning.
Links
SSH
Forges
Reuse
Citation
@online{shephard2022,
author = {Shephard, Neil},
title = {Git : {Custom} {SSH} Credentials for Git Repositories},
date = {2022-08-28},
url = {https://blog.nshephard.dev/posts/git-ssh/},
langid = {en}
}