Sunday March 24 2019

Automatically populate SSH keys from Github or Gogs API

There’s quite often a need for your servers to have up to date SSH keys for their users. Traditional methods of editing ~username/.ssh/authorized_keys is not only tedius, but error prone and does not update automatically.

Configuration Management

Some solve this problem by pulling in configuration management frameworks such as Puppet, Ansible, Chef, Salt, etc. While those work they have the problem of still allowing the user to edit the key that they have on disk ( even if it is being overwritten at some intervals ) and it requires a change to the configuration management solution to update the keys. Unless they’re a system’s engineer it’s not likely that they’re going to be touching the configuration management software all that often.

User Directories

Another solution is to use something like FreeIPA, or LDAP with SSH keys, while this works, it’s a complete pain to configure and if you’re not already using some sort of centralized directory getting people to switch is going to be a complete pain.

Using Existing Infrastructure

For my purposes engineers who need access to servers are going to have either a public Github account, or something internal to the company, since those services are often heavily used by engineers and will usually have up to date SSH keys we can have a small program pull them out of there.

For instance with Github we can see the key for Linus Torvalds with a simple curl command:

$ curl -s -X GET https://api.github.com/users/torvalds/keys | jq '.[].key'
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCoQ9S7V+CufAgwoehnf2TqsJ9LTsu8pUA3FgpS2mdVwcMcTs++8P5sQcXHLtDmNLpWN4k7NQgxaY1oXy5e25x/4VhXaJXWEt3luSw+Phv/PB2+aGLvqCUirsLTAD2r7ieMhd/pcVf/HlhNUQgnO1mupdbDyqZoGD/uCcJiYav8i/V7nJWJouHA8yq31XS2yqXp9m3VC7UZZHzUsVJA9Us5YqF0hKYeaGruIHR2bwoDF9ZFMss5t6/pzxMljU/ccYwvvRDdI7WX4o4+zLuZ6RWvsU6LGbbb0pQdB72tlV41fSefwFsk4JRdKbyV3Xjf25pV4IXOTcqhy+4JTB/jXxrF"

Well that’s great, but how are we going to make use of this? It’s pretty simple The OpenSSH server offers us an AuthorizedKeysCommand Which will allow us to write a small program to take advantage of the API and supply the key to the server.

From here you could probably bash together a short shell script to complete this task. Although I often use shell scripts, something like this that requires using curl and something along the lines of jq or manually trying to parse the JSON I’m going to reach for something a little bit more cohesive.

To accomplish this I’ve written a simple Go program that takes a few arguments for the API endpoint as well as the username to search for:


package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

var (
	endpoint = flag.String("e", "https://gogs.example.com/api/v1",
		"Gogs server endpoint, possibly github e.g. https://api.github.com")
	user = flag.String("u", "", "Username to look for")
)

type Key struct {
	Key string `json: key`
}

func (k Key) String() string {
	return k.Key
}

func errDie(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	flag.Parse()

	str := fmt.Sprintf("%s/users/%s/keys", *endpoint, *user)
	resp, err := http.Get(str)
	errDie(err)

	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	errDie(err)

	keyList := []Key{}

	err = json.Unmarshal(b, &keyList)
	errDie(err)

	for _, k := range keyList {
		fmt.Println(k)
	}
}

( You could also easily map Github usernames to different local usernames with a few minimal modifications by using a hash table or so. Not included here for simplicity )

Configuring OpenSSH

Pretty straightforward, simply add AuthorizedKeysCommand pointed at our new sshauth command:

AuthorizedKeysCommand /usr/bin/sshauth -e https://api.github.com -u "%u"

Now all you have to do is useradd -M $github_username and they’ll be able to login to your server if they have SSH keys associated with their Github account!

Preventing users from managing their own keys

If you wish to turn off the ability for users to leave behind keys it’s as simple as setting:

AuthorizedKeys none

Leaving AuthorizedKeys on for specific users

It’s as simple as adding a Match directive towards the end of your sshd_config

Match User mitch
	AuthorizedKeys .ssh/authorized_keys

You can also do this on a per group, LocalAddress, Host, or Address, you can read more about it in the documentation.

Additional Thoughts

This obviously doesn’t implement any sort of caching, although it would be possible to write out the keys to a directory in /tmp/ or /etc/ssh, and cache them permanently, for only a few minutes, an hour, etc I choose not to specifically so it would always grab the newest keys for the user, and in the event that an account is disabled and the keys are removed on a private Gogs server there’s no chance that they can login. A central choke point if you will.

Reading through and understanding a little bit of the documentation for the system software you’re using as well as looking at the incentives, and use cases and a little bit of programming ability to make things happen goes a long way.

For instance, most are going to see managing SSH keys in some sort of directory as a hassle, but when integrated with a tool they’re using all the time such as Github it becomes quite painless.

I always encourage more engineers to read the documentation for the tools that they’re using, half the battle of solving a problem is knowing what tools that you have at your disposal.

Find out that you have more than a hammer available and then everything stops looking like a nail.