sethserver / Programming

Multi-key GitHub Deployments

GitHub does not like using the same deploy key across multiple repositories. Try it, you'll get a really fun "Key is already in use" error in a lovely pink box at the top of your screen. What if we have multiple repositories residing on one server? How do we pull from different repositories using the same user? We use a whole bunch of SSH keys, of course!

Git version 2.3.0 introduced an amazing environment variable named GIT_SSH_COMMAND which, very conveniently, allows us to override the default SSH command. This new variable give us the freedom to do a whole bunch of fancy SSH stuff, such as specify a different SSH key for a git pull. Let's check it out.

We'll assume you have two repositories up on GitHub called github.com:current-startup/node-app and github.com:current-startup/legacy-node that you'd like to deploy to the server prod01 (prod02 crashed the other day and we haven't been able to get it back up #startuplife). We'll also assume that you're using git over SSH and not over HTTP.

Your typical workflow (assuming everything worked the way you expected without the key in use error) would be to:

  1. push your code to master
  2. SSH into prod01
  3. cd into node-app
  4. git pull origin master
  5. restart node-app service
  6. cd into legacy-node
  7. git pull origin master
  8. restart legacy-node service
  9. exit prod01

Unfortunately, node-app and legacy-node both run under the same web user (I don't know why it was done this way. Isolation is so much better! Someone will get around to fixing this someday; but, again #startuplife). This is where we run into our SSH key issue. Here's our fix:

Create an SSH key for each app.

web@prod01:~$ ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/home/web/.ssh/id_rsa): /home/web/.ssh/id_rsa_node_app Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/web/.ssh/id_rsa_node_app. Your public key has been saved in /home/web/.ssh/id_rsa_node_app.pub. The key fingerprint is: SHA256:[stuff] web@prod01 The key's randomart image is: +---[RSA 2048]----+ | * ** | [stuff] | c++ | +----[SHA256]-----+ web@prod01:~$ cat .ssh/id_rsa_node_app.pub ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA... [stuff] ...YFbPa5bA1FVHDmch/l1/tvXaNVymkiEN web@prod01 web@prod01:~$

web@prod01:~$ ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/home/web/.ssh/id_rsa): /home/web/.ssh/id_rsa_legacy_node Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/web/.ssh/id_rsa_legacy_node. Your public key has been saved in /home/web/.ssh/id_rsa_legacy_node.pub. The key fingerprint is: SHA256:[stuff] web@prod01 The key's randomart image is: +---[RSA 2048]----+ | -- | [stuff] | *+. | +----[SHA256]-----+ web@prod01:~$ cat .ssh/id_rsa_legacy_node.pub ssh-rsa AAAAB3NsiflsAA8dfjQQdflkABAA... [stuff] ...ZFdQa8qD2lLSKDJFl1/LXKJIDFljsdfiL web@prod01 web@prod01:~$

Take the .pub files that I so conveniently cat'd out for you and save them as deploy keys in GitHub (Settings -> Deploy keys -> Add deploy key). You should copy-paste them directly from the terminal and not try to type them in. Begin copying at the line that begins with "ssh-rsa" and stop at the line with user@hostname (in this example web@prod01).

Each key should go into its respective repository. In this example the key id_rsa_node_app.pub should be added to github.com:current-startup/node-app deploy keys and id_rsa_legacy_node.pub should be added to github.com:current-startup/legacy-node deploy keys.

Now, we can use GIT_SSH_COMMAND to tell git to use our new SSH keys instead of the default SSH key.

web@prod01:~$ cd ~/node-app web@prod01:node-app$ GIT_SSH_COMMAND='ssh -i /home/web/.ssh/id_rsa_node_app' git pull origin master remote: Counting objects: 5, done. remote: Total 5 (delta 4), reused 5 (delta 4), pack-reused 0 [stuff] 7 files changed, 213 insertions(+), 45 deletion(-) web@prod01:node-app$ service node-app restart OK web@prod01:node-app$ cd ~/legacy-node web@prod01:legacy-node$ GIT_SSH_COMMAND='ssh -i /home/web/.ssh/id_rsa_legacy_node' git pull origin master remote: Counting objects: 5, done. remote: Total 5 (delta 4), reused 5 (delta 4), pack-reused 0 [stuff] 7 files changed, 213 insertions(+), 45 deletion(-) web@prod01:legacy-node$ service legacy-node restart OK

There you have it. Not the most beautiful solution ever, but definitely something I've run into enough times that I felt the need to write about it! Happy coding!

-Sethers