GitHub Deploy Key: how to choose the right SSH key

GitHub is a well known tool to share and manage repositories among the developers. There are various ways to clone a GitHub repository, but in this article we are gonna focus on how to clone a repository with a Deploy Key and how to use the right SSH key.

maestro di chiavi

GitHub is one of the leading host solutions for public Git repositories when it comes to open-source projects, and it offers, since mid 2020, unlimited private repositories and collaboration to the free accounts too.

While public repositories can be easily downloaded via web URL (HTTPS), private repositories can be cloned through user authentication or SSH URL.

Every developer that collaborates on a GitHub project has its own personal account, where he stores his public SSH keys. A GitHub project’s development is usually done locally, on the developer’s machine, but when the project arrives in the staging or production stage it is important to clone the repository on one or more servers. So, how is this operation usually done? Those are the most common solutions:

  • In order to clone a repository on many servers, or to use it like a “snippet” of code in an automated system (like Ansible), the user should choose the OAuth tokenauthentication;
  • When it is necessary to clone many repositories on one server, maybe it’s more suitable to create a GitHub account for the server (Machine user), with its SSH key.
  • A third solution is to use a Deploy Key.

What is a Deploy Key?

A Deploy keyit’s basically a ssh key  generated for a specific server user, which allows a server to make a direct connection with a GitHub repository.

However, compared to other git hosting providers you can't reuse a Deploy Keyfor multiple repositories,  and you have to create a different ssh key for every cloned one (see the commands below):

ssh-keygen -t rsa -b 4096 -f ~/.ssh/github_repo1
ssh-keygen -t rsa -b 4096 -f ~/.ssh/github_repo2

The action will generate the following files:


At this point, the user will have to paste the ssh public key (eg. in the Key field of the Deploy keys on the GitHub repository settings. As title of the Deploy key it is suggested to use the following format:

<user name>@<server id>

Now we need to edit the user’s ssh configuration file, usually located under the user’s home folder (/home/user_name/.ssh/config), to tell git which ssh key to use. Within the config file, we’ll use the keyword Match with the criteria Host and Exec, with the bash conditional expressions.

Match Host Exec "[[ = $(git config --get remote.origin.url) ]]"
    	IdentityFile ~/.ssh/github_repo1

Let’s explain the expression a little bit. First of all we check if the hostname we are connecting to is if this matches, we will execute the bash command, that has to return 0 in case of match or 1 if it doesn’t match. This is done by the comparison between the set repository ssh URL:

and the output of the command:

$(git config --get remote.origin.url)

This will return the same string if we are in the right local repository. The whole procedure sounds pretty easy when the repository has already been cloned, but this is just a particular case. What if the repository hasn’t been cloned yet? And if we clone a repository within another repository? (This latter case may sounds strange, but in a software environment can happen to have some additional modules linked to other repositories).

So, if we execute the command:

git config --get remote.origin.url

it could print the top repository’s remote origin url. In this case, we have to evaluate the ps command output. And here things become a little bit more complicated, since the output is not always equal: We could obtain different outputs if we make a simple clone, a pull or if we are working on a branch in debug mode.

In this case, the best thing to do is to search for the word ‘git-upload-pack’, which will give us a similar output:

1074313 pts/3	S+ 	0:00 /usr/bin/ssh git-upload-pack '/repo1.git'
1074333 pts/3	S+ 	0:00 /bin/bash -c ps ax | grep git-upload-pack
1074335 pts/3	S+ 	0:00 git-upload-pack
  • In the first line is displayed the effective git  command used by the ssh process;
  • in the second line we have the grep of the ps command from the SSH Match Exec;
  • and finally the grep process itself.

Therefore we will use a command like this:

ps ax | grep git-upload-pack | head -n 1

Finally, we have to evaluate this output in order to be able to compare it with the repository URL:

1074313 pts/3	S+ 	0:00 /usr/bin/ssh git-upload-pack '/repo1.git'

The output of this command could change, this is why, in order to check if one matches with the repository name, the best way is to save the output in an array and compare each of its elements. The builtin bash mapfile command comes in our help:

Match Host Exec "unset MAPFILE; mapfile < <(ps ax | grep git-upload-pack | head -n 1 ); [[ ${MAPFILE[@]} =~ '/repo1.git' ]] && exit 0 || exit 1"
    	IdentityFile ~/.ssh/github_repo1

Let’s explain the full command in detail: 

  • first of all we are gonna clear the MAPFILE array;
  • then we read the output of the ps/grep command in the default MAPFILE array;
  • then we compare every element of the MAPFILE array with the repository name and organization;
  • if match is found it exits with 0;
  • if no match is found, exit with 1 (error).

This procedure, however, has a flow, since it can’t manage checkouts executed in parallel.

Now, what remains to be done is to check if the match process is working properly, and to do so we have to debug the SSH process:

GIT_SSH_COMMAND="ssh -vvv" git clone
Cloning into 'repo1'...
OpenSSH_8.2p1 Ubuntu-4ubuntu0.2, OpenSSL 1.1.1f 31 Mar 2020 debug1: Reading configuration data ~/.ssh/config debug2: checking match for 'Host Exec "echo $(ps ax | grep git-upload-pack ) > /tmp/git_ssh; [[ ${MAPFILE[@]} =~ '/repo1.git' ]] && exit 0 || exit 1"' host originally
debug3: ~/.ssh/config line 5: matched 'Host ""'
debug1: Executing command: 'echo $(ps ax | grep git-upload-pack ) > /tmp/git_ssh; [[ ${MAPFILE[@]} =~ '/repo1.git' ]] && exit 0 || exit 1'
debug3: command returned status 0
debug3: ~/.ssh/config line 5: not matched 'Exec "echo $(ps ax | grep git-upload-pack ) > /tmp/git_ssh; [[ ${MAPFILE[@]} =~ '/repo1"'
debug2: match found
Potrebbero interessarti...