Using git with multiple profiles and GPG+SSH keys
I work across 2 computers and 3 OS, all with nearly same development setup. For work I need some special care for my git config, since I want/need access to personal repositories.
Recently I helped a coworker during onboarding with his machine setup and he asked me about this very same topic. I provided him with what I have and use. The following blog post is a refined version of it.
The slightly more complex version control system configuration is due to the following requirements and expectations:
- I want to use my personal (and main) email address for my personal GitHub projects.
- I want to use my work email address for the workplace stuff.
- I want separate GPG keys for each email address.
- I want to sign my commits with the individual GPG keys for each of the addresses.
- I want separate SSH keys for each context (work, personal; but also for each computer).
- I want to use the individual SSH keys based on the project I work on.
- I want all of it happening as automatically as possible.
- I want to forget that it is there.
I know, I know, this looks like a huge list of requirements. But not only is this my personal preference, partially also my workplace has some strictler rules, especially when it comes to security.
If you do work in an early stage startup you might think that it would be excessive, but I promise you: if that company becomes successfull and grows and turns into a bigger deal like an enterprise, things will change. And part of it will be around security and compliance. You better improve your setup now when it's still easy to do so.
On Latacora you can find a nice compilation of things you need to consider and implement if you want to go big and sell your startup, but a lot of it could and should be done without that goal in mind.
So, if you cannot get rid of the first point and do need also access to your personal persona on GitHub (or any other git based hosting platform for that matter), this article might help you to achieve it.
»Why would I continue using my personal GitHub account on my work machine anyway?« — You may or may not be an open source contributor, and you may or may not use such open source software at your current employer's projects for example. The reasons are plenty and some will justify the need for what is described here.
By the way, this approach should work for both single and multiple GitHub accounts. Since GitHub makes it easy to keep using a single account while also being a member of an Enterprise organization, I haven't bothered testing it with a true multi-account configuration, but since you will use individual SSH and GPG keys for either way your computer won't really know the difference.
SSH configuration
While git
does support the HTTPS transport, in most cases you will use the more preferred way of talking git+ssh instead. So let's tackle this lower level first:
SSH Keys
If you working with GitHub you want to generate keys with the latest and greatest recommended algorithms:
ssh-keygen -t ed25519 -C "your_email@example.com"
If you work on other platforms, please check first, which algorithms are supported there.
In decreasing order of preference and security:
# prefer this if possible:
ssh-keygen -t ed25519
# this is still quite okay:
ssh-keygen -t ecdsa -b 521
# this only as a last resort option, should work everywhere:
ssh-keygen -t rsa -b 4096
# avoid this, please;
# also GitHub does not allow new keys with DSA anymore!
ssh-keygen -t dsa
More details about key generations at: https://www.ssh.com/ssh/keygen/
~/.ssh/config
### github
### -- PERSONAL/MAIN ACCOUNT --
Host github.com
Hostname github.com
User <YOUR GITHUB USERNAME>
IdentityFile ~/.ssh/<YOUR PERSONAL SSH KEY>
### -- WORK PERSONA/ACCOUNT --
Host github.com-work
Hostname github.com
User <YOUR GITHUB USERNAME>
IdentityFile ~/.ssh/<YOUR WORK SSH KEY>
### general
Host *
AddKeysToAgent yes
IdentitiesOnly yes
PreferredAuthentications publickey
Compression yes
Important note: Do not change the order of the configuration.
For each parameter, the first obtained value will be used. The configuration files contain sections separated by ''Host'' specifications, and that section is only applied for hosts that match one of the patterns given in the specification. The matched host name is the one given on the command line.
Since the first obtained value for each parameter is used, more host-specific declarations should be given near the beginning of the file, and general defaults at the end.
Since we will use two different hosts, we must repeat the Hostname
line, but also want to specify User
and IdentityFile
specifically. If you use your ssh config only for git and also have only a single user name, you could move that config line into the generic section in the bottom. Though I still prefer the explicit repetition for each specific Host block.
At this point you might wonder »How is github.com-work supposed to function?« Hold that thought, we will come back to it later.
GPG key management
You want to use GPG for signing commits. Your workplace might not require it (yet), but if you have any level of trust issues, or just want to be sure that a commit was made by you and be able to prove it, use commit signing.
Interestingly GitHub does not recommend you to use a more modern algorithm and requires you to pick RSA. A bit sad, but RSA with 4096 bits seems to be still fine for this purpose.
After you have created your keys, you should grab the IDs for them:
gpg --list-secret-keys --keyid-format LONG
# I use shorter IDs and git doesn't seem to struggle
gpg --list-secret-keys --keyid-format SHORT
Example output:
/home/asaaki/.gnupg/pubring.kbx
-------------------------------
sec rsa4096/D73D7242 2021-02-14 [SC]
AE93078BDC15BF6A84767DBBA3CBBB61D73D7242
uid [ultimate] TEST KEY <test-key@example.com>
ssb rsa4096/57047776 2021-02-14 [E]
The ID is the part after the rsa4096/
from the sec
line: D73D7242
In the LONG variant it just used more characters from the whole fingerprint (which you can completely see in the line between sec and uid).
git shenanigans
The git
command line interface (CLI) improved a lot over the years. I remember that I used a custom GI_SSH=…
wrapper in my shell environments to make this whole host/key mapping possible, but thanks to the power of git configs this is a thing of the past.
~/.gitconfig
[init]
defaultBranch = main
[commit]
gpgsign = true
# other sections cut for brevity; add the following to the bottom:
[user]
name = Your Name
useConfigOnly = true
# optional (if you sometimes work outside of your regular directories)
[include]
path = ~/.gitconfig.personal
[includeIf "gitdir:~/Development/Personal/"]
path = ~/.gitconfig.personal
[includeIf "gitdir:~/Development/Work/"]
path = ~/.gitconfig.contentful
~/.gitconfig.personal
[user]
email = your.personal@email.here
signingKey = <PERSONAL GPG SIGNING KEY ID>
~/.gitconfig.work
[user]
email = your.work@email.here
signingKey = <WORK GPG SIGNING KEY ID>
[url "git@github.com-work"]
insteadOf = git@github.com
Explanation
include
and includeIf
are the key components to separate out specific configurations based on where you are on your system.
include
is always pulled in, this is useful if you really want to separate out parts of your main .gitconfig
.
If you only work in specific directories like ~/Development/Personal/
and ~/Development/Work/
— and this means where you also need to commit and push — then you could remove the general [include]
section entirely. I keep it around, because I might have checked out a repository somewhere else and don't want to move it for a commit.
There's probably a better way to organize this, but the above has served me well for quite some time now.
I recently added the global [user]
section for setting useConfigOnly = true
and the name, since I only have one. useConfigOnly
prevents git from guessing name and email and forces it to read it from the configuration files. If the email would be missing, git will complain the next time you try to commit or push. And you will know that something is broken in the configuration.
Important note: The trailing slashes (/
) on the [includeIf …]
lines are very important. If you forget them, then git would try to match only this very specific folder and ignore it for any folder within it. More details about conditional includes in the git documentation. (I totally missed that you can use them even for branches, too.)
The signingKey
values should be set appropriately based on the IDs you have noted from the previous section of this article. Now when you commit anything git will use the correct key based on where the repository directory lives.
To automatically enforce the commit signing, use commit.gpgsign
set to true
.
Remember the question you had earlier about the weird looking Host configuration in SSH? The [url "git@github.com-work"]
section is the counterpart making it work, because git will do the translation when you are in your company's repos.
What will happen is the following:
- You are in a work related repository.
- You want to fetch/pull the latest changes or push your local state to the origin.
- Since git will load the work config, it replaces the regular URLs having
git@github.com
in it with the value of the[url …]
, heregit@github.com-work
. - git reaches one level down and uses ssh for the communication.
- SSH sees a
github.com-work
host and tries to find all matching configurations for that, including the exact one we have defined above. - SSH will ignore our personal configuration, because the regular
github.com
does not match anymore. - SSH picks up your work identity file for authentication with GitHub's server.
- SSH will also use the actual
Hostname
instead ofHost
(translating it back again). - Everyone is happy. \o/
What's with this [init]
block you haven't mentioned before?
Since Git 2.28 you can set a name for the default branch when creating a new repository. Be a good citizen and avoid offensive and negatively connotated terms. The wider developer community seems to like "main" as a good replacement for the previous default name. You can read more about renaming existing default brances on GitHub.
Security considerations
You might want to use passwords for both the GPG and SSH keys, your employer might even have a rule for this. To avoid annoyances in your workflows make sure your system has some sort of keychain manager and can keep the passwords for a period of time.
Since the setups vary for each operating system and this part is also out of scope for this post, I leave it to you to figure it out.
Who's pushing?
In total 2 files to change and 2 files to add (excluding all the keys), and you have a nice setup for using multiple accounts, emails, GPG keys, and/or SSH keys on your computer.
And from here you can expand it even further if you like or need.
Last but not least: this now works for existing repositories, locally created, or freshly cloned ones. As long as the repos are somewhere nested in the right parent directory, you're set and done.
Bonus
To quickly check which config is used execute the following command in a repository:
git remote -v
The URL should provide you a quick hint if the correct profile is used.
Alternatively you can also run:
git config --show-origin --get user.email
This will also print where the final value was retrieved from.
Git came a long way and I'm very glad and happy that we can have such setup without a lot of manual tinkering and workarounds. Gone are all my custom scripts, snippets, and .envrc
's for that purpose, which were also not completely platform agnostic.
Have you tried it yourself? Is something not working as expected? Let me know and send me a message on Twitter.