SSH Config
SSH Config
I am not exactly sure where to put this post, as I am using this more for git, but this is its own topic, that is SSH. The issue that I am running across is having multiple SSH keys for different purposes, and how to organize them and use them properly in the SSH config.
This will be more advanced and not how to use or setup the SSH Config file ( typically located at ~/.ssh/config ). There is many applications that use this config file, and this will not be about all of them. This will not go over those applications or how they are used. This will focus on the git application using this.
Objective
My situation is not uncommon, but fairly unique. I have a personal github that I use, as well as a personal gitlab and personal bitbucket. At work we have a private or enterprise or on-premise instance of each of those so instead of “bitbucket.com” the path is “bitbucket.mywork.com”. Fairly straightforward so far, obnoxious that they have all 3, but some of that is being transitioned, which is also not uncommon (at least not long term). However, the next caveat is that we did create some opensource software as well, that is listed on the public sites. Typically, for something like that, it would not be a big deal to just also use my personal account and call it a day. The snag is that the repositories on the public sites I need to use my work email.
NOTE: I recognize that “on-premise”, “enterprise”, and “private” are 3 different things for each of those organizations and could be setup different in each scenario or instance. I am just trying to convey that there is a separate server with a separate host/url that connects to that organizations instance of that git server.
My objective is that I want to use a different SSH key for each of these 3 different areas. First being “personal”, that is on the public sites. Second is the “work private” repos. Third is the “work public” repos.
Regarding having “personal” repos on my “work” computer, there are various reasons anyone would have this. It is correct that removing all of this would mitigate most of my problems, so for those that are having a similar issue and this is acceptable, just do that. My “personal” repos on my work computer are mostly notes that I want to take with me (they are non-proprietary notes) and repos testing how code works. I don’t have side-projects and personal projects and personal information there. But its mostly information that can be used at any company without being specific about anything.
Scenario - Work Private and Public Personal
This would be if you have some personal notes and such that you want on your work computer, while having the companies private repos as well. This would be if the organization only had private / on-premise type repos.
Host gitlab.com
HostName gitlab.com
IdentityFile ~/.ssh/gitlab_personal
Host gitlab.mywork.com
HostName gitlab.mywork.com
IdentityFile ~/.ssh/gitlab_work
This is fairly straight forward. One ssh key for personal stuff, one for work.
Scenario - Work Public and Private
Similar setup as the above situation. If on my work computer I just had the “public” and “private” repos for work, I would do something like below.
Host gitlab.com
HostName gitlab.com
IdentityFile ~/.ssh/gitlab_public_work
Host gitlab.mywork.com
HostName gitlab.mywork.com
IdentityFile ~/.ssh/gitlab_private_work
The Host begins the stanza and defines what location to look for, which if matched the information in the rest of the stanza will be used. Given this, we have two different files, generically named for the sake of this example.
What we see happening is that the all the “public” connections should just be to the public server, in this case “gitlab.com”, and then use the ‘public repo’ ssh key that is defined as “gitlab_public_work”. Alternatively, the private repos should be on its own server with a different host or url, often with “gitlab” or “github” or “bitbucket” or “git” prepended to the beginning of your companies domain. This will then use the organizations ‘private repo’ ssh key.
NOTE: I want to point out that I am using the term ‘private’ and ‘public’ SSH key to describe the ssh key that connects to the type of repo. When creating an SSH key, there is a public and private key pairing. THIS IS NOT THAT. Please, don’t confuse these, and I will try my best to not make it more confusing.
This works, and is easy to read and understand in the SSH config file.
Something similar, if you only want to use 1 SSH key for both the ‘private repos’ and the ‘public repos’. I personally like to have multiple SSH keys for security purposes, but you can use the same SSH key for multiple connections, and not just with git. If you really wanted, you could use the same SSH key for Git, SFTP, SCP, and SSH remote connection. The ssh config file would look something like this.
Host gitlab.com
HostName gitlab.com
IdentityFile ~/.ssh/gitlab_work
Host gitlab.mywork.com
HostName gitlab.mywork.com
IdentityFile ~/.ssh/gitlab_work
But this can be condensed down to something like below…
Host gitlab.com gitlab.mywork.com
IdentityFile ~/.ssh/gitlab_work
For the “Host” declaration, you can have multiple hosts defined they just need to be separated by a space. This works as an “OR” statement. With the above example this would use that gitlab_work ssh key for “gitlab.com” OR “gitlab.mywork.com”. There can be as many hosts define here as you want, though it may become unreadable at some point.
Similarly, if you wanted all the “public repos” to use a certain key, and then the private ones to use the same, you can do that as well.
Host gitlab.com github.com bitbucket.com
IdentityFile ~/.ssh/gitlab_public_work
Host gitlab.mywork.com github.mywork.com bitbucket.mywork.com
IdentityFile ~/.ssh/gitlab_private_work
Alternatively, the above could be shown for personal repos and work repos instead of public and private repos, or whatever you have.
Patterns and Wildcards
As a quick side, this could also be simplified to have a wildcard character.
Host git* bit*
IdentityFile ~/.ssh/git_key
The * is a wildcard character to match 0 (zero) or more of any character. The above example would then use the same key file for ‘gitlab.com’, ‘github.com’, ‘gitlab.mywork.com’, ‘github.mywork.com’, and the ‘bitbucket’ domains as well. Awesome, right.
If only 1 of any character is needed, then you could use ? instead.
If I had a range of devices on my local network that all used the same ssh key, I could possibly do something like below.
Host 192.168.0.?
IdentityFile ~/.ssh/ssh_key
This would use the same ssh key for ‘192.168.0.0’ to ‘192.168.0.9’, but not apply for ‘192.168.0.10’ and beyond.
We can also exclude certain things using ! as part of the declaration.
Host "!.mywork.com,git*"
IdentityFile ~/.ssh/git_key
The above would use the ssh key file git_key for all hosts that contain ‘git’ at the beginning, except when its part of the ‘mywork.com’ domain.
There a few things to go over here. This is an awesome feature, and the man page on patters does explain some of this.
First, when you want to test against multiple conditions, separate them by a comma for an “and” statement. Previously, I mentioned using a space for an “or” to test against multiple domains. If some of the logic gets complicated and you don’t want to list everything, you can use the wildcards with a comma separated list. Maybe your work has a “.com” and a “.net” and a “.org” TLD, but they are all different, so you don’t want to use the same ssh key for each. This can be separated out.
Second, is with the use of a list, I have found that putting it in quotes works best, and no spaces, only commas. My tendency is to use a space after the comma to make it more readable. This will not work. There is also nothing in the documentation that I saw about NEEDING quotes, but its part of the examples. I have found that it can work without quotes, but its always consistent with quotes.
Third, the wildcard character location defines where there may be additional characters. As in the examples above, this would work for “gitlab.com” and “github.mywork.com”, but it would not work for “mywork.github.com”.
Last, this can be combined with a space for more complex situation. Using the last example, if you did Host "!.mywork.com,git*" bit*, this would include ALL of the bitbucket domains, regardless if its bitbucket.mywork.com or just bitbucket.com.
Warning with any wildcard, you can get unexpected results. You may find that when you add a wildcard to the end the wrong directive or stanza is being used. You may also find that suddenly some servers are being included that you did not want. All things to keep in mind.
Scenario - Personal, Public Work, and Private Work
This is where things get complicated and my objective.
Utilizing everything I just went over, how do I utilize the SSH key for github.com public work repos and not the SSH key for github.com personal repos?
Just to go over again, 3 groups of repos.
First - personal public repos at github.com and such.
Second - private work repos at private server such as github.mywork.com.
Third - public work repos at a public server like github.com.
Because the first and third are different “groups” for my sake and security, I want two different SSH keys. I don’t want my personal repos to have the same SSH key as my work. If I am stupid about something and a hacker gets my “personal” SSH key, they will only have access to my grocery list on github.com, and not have access to my employers public repos.
Host "github.com,!*mywork*"
IdentityFile ~/.ssh/ssh_key_1
Host "github.com,!*personal*"
IdentityFile ~/.ssh/ssh_key_2
Host github.mywork.com
IdentityFile ~/.ssh/ssh_key_3
Something to note is that the “exceptions” come first in the list for the pattern of the Host. Having the !*mywork* at the end will not exclude those domains from this.
We see here the three groups. Easiest one is the third one we see, showing that all SSH connections going to host “github.mywork.com” will use the “ssh_key_3” file. The first two are opposites of each other, but may be changed around depending on your setup. Lets go more in-depth about these.
The first using !*mywork*,github.com will use the “ssh_key_1” for anything that uses the host “github.com” AND does not have “mywork” in the connection. Meaning this is all of the personal repos. The “mywork” can be changed to whatever you need, but this assumes that “mywork” is in the host string somewhere. Something similar to git@github.com:mywork/company_website.git. I was in a situation once, where the company had bought 4 other companies over time and we were maintaining all their repos. However, those public repos were still all under the old company name. This statement for me was something similar to this below…
Host "!*company_1*,github.com" "!*company_2*,github.com" "!*company_3*,github.com" "!*company_4*,github.com"
Host "github.com,!*company_1*" "github.com,!*company_2*" "github.com,!*company_3*" "github.com,!*company_4*"
This may not be that readable or may be that logical to some. This might also catch some false positives as its only excluding these companies specifically, and there could be something else that you want to exclude (maybe another side-project company).
Instead of the above we could have something like below.
Host "myUserName*,github.com"
Host "github.com,myUserName*"
However, this might catch some false positives as well. You might have some repos under your name for the company for some reason.
Also, the wildcards might not be needed. This might help limit the false positives that occur. For myself, at one organization I needed the wildcard at the end, because of some inconsistent naming, at another organization I needed the wildcard at the beginning and end because of even less consistent naming, and at another place I didn’t need it at all because of good consistent naming conventions.
Something to note after writing this. I did have this working for a while and just within the past few weeks, early of 2023, this stopped working for me as expected. There was a new release of git within the past few weeks as well. I am not certain if what I had was wrong, or accidentally worked properly, or something else. However, what I noticed is that by switching the “github.com” (or whatever server) and the inclusion/exclusion part everything seemed to work for me again. Meaning, changing Host "myUserName*,github.com" to Host "github.com,myUserName*". What I suspect is happening is that the lookup for that is now order dependent. So now it much be “github.com” followed by “myUserName”, else it won’t work.
I have temporarily included both versions in this article.
I want to do more research on this, but not at the moment, and I will come back to this. If anyone is actually reading this and knows, then please feel free to leave a comment. Thank you.
Hopefully this helps someone out there.