Skip to content

tedious ramblings

The blog of Robert Hafner

Menu
  • Projects
  • Resume
  • Sponsor
  • Archives
  • About
Menu

Telling OpenSSH to Pull Keys from Github with AuthorizedKeysCommand

Posted on November 19, 2021December 29, 2023 by Robert Hafner

Last week Corey Quinn asked an awesome question on twitter- paraphrasing, what's the best way to use your Github SSH Keys to log into your personal services?

What’s the modern state of managing ssh keys on multiple servers? My GitHub key list is the canonical source; do people just cron a curl that replaces authorized_keys on a schedule or what?

— Corey Quinn (@QuinnyPig) November 12, 2021

This reminded me of one of my favorite little tricks- OpenSSH's AuthorizedKeysCommand option. This OpenSSH configuration option allows administrators to specify a command that dynamically generates an authorized_keys list. This command takes the username as its only argument, gets called before any on system key files (your standard ~/.ssh/authorized_keys file for instance) are checked, and outputs keys to stdout in the same format. If the script doesn't return a matching key then OpenSSH falls back to its normal lookup.

This trick can be used to pull keys from a variety of places, and Github is no exception. The nice thing about Github is that it publishes the public SSH key of every user in an easy to access place, https://github.com/<username>.keys (for example, my keys are published at https://github.com/tedivm.keys). We can create a simple bash script to act as our AuthorizedKeysCommand to pull down those keys and give them go OpenSSH.

#!/usr/bin/env bash

# Explicit "allow" list in case Github users overlap with system users
ALLOWED_USERS="tedivm"
KEY_URL="https://github.com/${1}.keys"
KEY_FILE=$(eval echo ~$1/.ssh/authorized_keys2)

if [[ -z $1 ]]; then
  >&2 echo "Username required."
  exit 1
fi

if [[ ! " $ALLOWED_USERS " =~ .*\ $1\ .* ]]; then
  >&2 echo "User not in allowed list."
  exit 1
fi

TMP_AUTHORIZED_KEYS=$(mktemp)
HTTP_STATUS=$(curl -m 5 -s -o $TMP_AUTHORIZED_KEYS -w "%{http_code}" $KEY_URL)
PUBLIC_KEYS=$(cat $TMP_AUTHORIZED_KEYS)
rm $TMP_AUTHORIZED_KEYS

if [ $HTTP_STATUS != "200" ]; then
  >&2 echo "Pulling keys from Github failed with status code ${HTTP_STATUS}"
  exit 1
else
  mkdir -p $(eval echo ~$1/.ssh)
  chown $1:$1 $(eval echo ~$1/.ssh)
  echo "$PUBLIC_KEYS" > $KEY_FILE
  cat $KEY_FILE
fi

There's a couple of important things to note about this script-

  • There's a hardcoded set of Github users that are allowed. This way users on Github with overlapping usernames in your system won't be able to log in.
  • It assumes your Github username is the same as your system username. You can add some username mapping if that isn't the case.
  • We copy the keys onto the system for the user, so that if Github is down the systems are still accessible. The script can be called directly in a cronjob to automate that copy and keep keys fresh.
  • The command to get the key returns the HTTP status directly and stores the response in a file, which is the easiest way to get both with one curl command.
  • There's a five second timeout on the curl command. OpenSSH will try the system keys if this script fails, but only if the script fails before OpenSSH times out. So curl has to time out long before that would happen.

We'll place that script at /opt/github_authorized_keys.sh and set it to execute (chmod /opt/github_authorized_keys.sh). Then update OpenSSH's config file, typically at /etc/ssh/sshd_config, to set the command and the user to run as. Since we want to push files to different users the command needs to run as root- if that makes you uncomfortable you can create a dedicated user and remove the file creation parts of the script.

AuthorizedKeysCommand /opt/github_authorized_keys.sh
AuthorizedKeysCommandUser root

Now restart OpenSSH and log into your server- your keys will be pulled directly from Github.

Share this:

  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on X (Opens in new window) X
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to print (Opens in new window) Print
  • More
  • Click to share on Pinterest (Opens in new window) Pinterest
  • Click to share on Tumblr (Opens in new window) Tumblr
  • Click to email a link to a friend (Opens in new window) Email

Leave a ReplyCancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

About

Robert Hafner is a Principal Engineer based in Chicago focusing on distributed applications, infrastructure, and security. This blog is a running journal of projects, tutorials, and random ideas that pop into his head.

  • GitHub
  • Mastodon
  • LinkedIn

Popular Posts

Subscribe to Blog via Email

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

©2025 tedious ramblings | Built using WordPress and Responsive Blogily theme by Superb