All the information up to now (2020-01) are useful, but IMHO are a little bit messy, and no post sums them up to a clean solution.
So here are my solutions. If you are lucky with your current solution, then you are free to ignore this post.
Overall Premise:
Solution for *nix-based OS.
Tested on Debian-based OS, but should work on other *nix-based OS.
Overall Prerequisites:
- Separate identity files (keys) for different hosts and/or users available, e.g. generated via
ssh-keygen -f <filename> ...
Goals:
- Use a specific identity file (key) per repository (optional: per host)
- Works with any host: GitHub, GitLab, BitBucket, etc
- Simple to use and to adopt
- Prefer not messing around with URLs and use them as-is
- Prefer to use normal configuration of git and ssh
- (nice-to-have) Working in shell and gui tools
- (nice-to-have) Works with any tool: git, ssh, svn, rsync, etc.
Solutions:
- Convenient tool ssh-ident
- Just normal git and ssh, with a minor portition of shell magic
- Host renaming (also works with Putty/Pageant/Plink)
Note: A Match Path
feature for OpenSSH would not help here, see https://marc.info/?l=openssh-unix-dev&m=141833748901966&w=2
Solution #1 - Tool ssh-ident
Notes:
- Currently (as of 2020-01) I link my own fork of the original ssh-ident, as it gives more flexibility to argument and key filename matching.
- See docstring inside ssh-ident for how to use
Prerequisites:
Download and prepare
Configure ssh-ident and the identities
- Prepare files and directories
[ -f ~/.ssh-ident ] || printf -- '\n' >~/.ssh-ident
MYEXTRAIDENTITIES='1 gh2 dummy'
for MYIDENTITY in ${MYEXTRAIDENTITIES}; do
echo "${MYIDENTITY}"
[ -d ~/.ssh/identities/"${MYIDENTITY}" ] || mkdir -vp ~/.ssh/identities/"${MYIDENTITY}"
done
chmod -v -R u=rwX,go= ~/.ssh/identities
unset -v MYEXTRAIDENTITIES MYIDENTITY
- Maintain configuration of ssh-ident to detect GitHub repositories
Example: ~/.ssh-ident
...
MATCH_ARGV = [
(r"\s(git@)?github\.com\s.*'my-git-user-1\/dummy\.git'", "dummy"),
(r"\s(git@)?github\.com\s.*'my-git-user-1\/.+\.git'", "1"),
(r"\s(git@)?github\.com\s.*'my-git-user-2\/.+\.git'", "gh2"),
...
#
(r"\s(git@)?gist\.github\.com\s.*'abcdef01234567890fedcba912345678\.git'", "1"),
...
]
...
- Copy private keys plus their public keys(!) to the corresponding identity directory
- Optionally create a per-identity ssh config file
Example: ~/.ssh/identities/1/config
Host github.com,gist.github.com
IdentitiesOnly yes
IdentityFile ~/.ssh/identities/1/github-user1.key
User git
Enable ssh-ident
Solution #2 - Just normal git and ssh
Prerequisites:
Concept:
Use OpenSSH client config's Match Exec
functionality to check for an environment variable (e.g. SSHGITUSER
) to select a specific identity file (key).
Fail when environment variable is not set or contains an unknown user id.
Allows for the following options:
- One account on host: use
Host
block in OpenSSH client config to directly define the IdentityFile
= pure vanilla ssh standard, no environment variable needed
- Multiple accounts on a host: pass the environment variable to the OpenSSH client, so it can react on it within
Match
blocks
- Either set the environment variable directly in front of the git call (e.g.
SSHGITUSER='<id>' git ...
)
- Has to be used when cloning a git repository (e.g.
SSHGITUSER='<id>' git clone ...
)
- Works also for remotes that need different identity files (e.g.
SSHGITUSER='<id>' git fetch <remote>
)
- Works also when using environment variables
GIT_SSH_COMMAND
or GIT_SSH
(see docs)
- or use git config
core.sshCommand
to hard-code it for a repository (e.g. git config --local core.sshCommand "SSHGITUSER='<id>' ssh"
)
- Does not work for remotes that need different identity files
- Does not work when using environment variables
GIT_SSH_COMMAND
or GIT_SSH
(see docs) as these override git config core.sshCommand
of the repository
Setting up OpenSSH client config ssh_config:
Whenever we connect via ssh to a host, then we only want to connect with the identity files we explicitly specify. This can be achieved by setting IdentitiesOnly
to yes
and IdentityFile
to none
in a matching Host
block, then no other identity files are offered to the target server, even when cached inside ssh-agent, nor the default key definitions will be tried.
If a default Host
block exists for all servers, then extend its pattern to exclude the special host there.
Select the wanted identity file by checking an environment variable (e.g. SSHGITUSER
). Define an id (number, letter or name) for each user and create a Match
block for each id. The Match
block should check the Host
first to avoid using the identity file for other hosts and to avoid useless calls to the shell for testing the environment variable.
Specifing the user git
for github.com
and gist.github.com
allows to remove git@
from the URL. This setting is overriden when a user is defined in the URL.
Example ~/.ssh/config for GitHub:
...
### >>> GitHub
## use: SSHGITUSER='<id>' git ...
## optional: git config --local core.sshCommand "SSHGITUSER='<id>' ssh"
Host github.com,gist.github.com
IdentitiesOnly yes
IdentityFile none
User git
Match Host github.com,gist.github.com Exec "test ${SSHGITUSER:-_} = '1'"
IdentityFile ~/.ssh/github-user1.key
Match Host github.com,gist.github.com Exec "test ${SSHGITUSER:-_} = 'gh2'"
IdentityFile ~/.ssh/github-user2.key
Match Host github.com,gist.github.com Exec "test ${SSHGITUSER:-_} = 'dummy'"
IdentityFile ~/.ssh/github-dummy-fake.key
### <<< GitHub
...
Host !github.com,!gist.github.com,*
IdentityFile ~/.ssh/personal.key
...
Notes:
- If only a single identity file is used for a host, then in the related
Host
block change IdentityFile
from none
directly to the identity file and no Match
blocks are needed.
- Remote
User
can also be set in a Match
block. Remember a user in the URL overrides this setting.
Testing ssh config:
Pass the environment variable for the SSH call by defining it directly in front of the command.
Use a wrong user in the URL to avoid actual logins as we just want to see the identity files offered to the server.
SSHGITUSER='1' ssh -vT [email protected]
Setting up git config of a repository for a hard-coded user id:
To hard-code the user id to a repository use git config core.sshCommand
on the repository itself (via --local
) to pass the environment variable to the ssh command.
The ssh command for git is either defined by the environment variable GIT_SSH_COMMAND
(since git 2.3) or git config core.sshCommand
(since git 2.10). If both are empty, then it normally defaults to just ssh
(see Code funtions get_ssh_command() and fill_ssh_args() in connect.c).
Special case is when cloning a repository, then there is no config for the not yet cloned repository, and the environment variable has to be set directly in front of the git call (e.g. SSHGITUSER='<id>' git clone ...
).
Example:
SSHGITUSER='1' git clone [email protected]:my-git-user-1/test-repo.git ~/work/test-repo
cd ~/work/test-repo
git config --local core.sshCommand "SSHGITUSER='1' ssh"
# Test: git fetch
Notes:
When using environment variables GIT_SSH_COMMAND
or GIT_SSH
(see docs) these will override git config core.sshCommand
of the repository.
Check first via set | grep -e '^GIT_SSH'
and if any is set, then adapt from using environment variables to git config core.sshCommand
of the user (via --global
) and remove the GIT_SSH[_COMMAND] variable.
(e.g. git config --global core.sshCommand "<content of used GIT_SSH[_COMMAND] variable>"
, plus git config --local core.sshCommand "SSHGITUSER='<id>' <content of used GIT_SSH[_COMMAND] variable>"
)
Other ideas for #2:
- Use environment variable for
IdentityFile ~/.ssh/github-${SSHGITUSER}.key
or User ${SSHGITUSER}
. Has the tendency to longer user ids.
Setting up other tools for #2:
On the command line just pass the environment variable by defining it directly in front of the command.
For hard-coding the user id the task is to find out how to pass the environment variable to the tool, e.g. configuration, wrapper script, alias, etc.
Solution #99 - Host renaming
Prerequisites:
Concept:
As also mentioned in this thread you can rename the host as described in the git FAQ for the ssh config to recognize it, choose the identity depending on it and set the correct host name.
I normally do not prefer to mess up URLs instead of using configurations/variables.
Still I would then add the user id as a subdomain, e.g. <user id>.github.com
.
After some talk adding the user id behind the host has the advantage that normally DNS resolution will fail as the TLD got messed up and therefore the connection will immediately fail also.
Extended the example for this, e.g. github.com-<user id>
.
This also works for Plink and Pageant from Putty.
Putty checks for a saved session named like the "host", e.g. session github.com-<user id>
. This can be used for this solution.
Then Putty checks for a session which references the host, e.g. session testing
with host github.com
. Just to let you know.
Example ~/.ssh/config for GitHub:
...
### >>> GitHub
Host github.com,gist.github.com,*.github.com,*.gist.github.com,github.com-*,gist.github.com-*
IdentitiesOnly yes
IdentityFile none
User git
Host 1.github.com,github.com-1
IdentityFile ~/.ssh/github-user1.key
Hostname github.com
Host 1.gist.github.com,gist.github.com-1
IdentityFile ~/.ssh/github-user1.key
Hostname gist.github.com
Host gh2.github.com,github.com-gh2
IdentityFile ~/.ssh/github-user2.key
Hostname github.com
Host gh2.gist.github.com,gist.github.com-gh2
IdentityFile ~/.ssh/github-user2.key
Hostname gist.github.com
### <<< GitHub
Host !github.com,!gist.github.com,*
IdentityFile ~/.ssh/personal.key
...