SSH Authentication with AuthorizedKeysCommand

Imagine a world where adding or removing a user’s SSH access was as simple as a single button in AWS. I’m not sure about you, but the problem of provisioning and de-provisioning SSH users on a fleet of nodes is a giant pain in the ass. The conventional wisdom is have a file called ~/.ssh/authorized_keys in the…

Adeeb Aslam

Imagine a world where adding or removing a user’s SSH access was as simple as a single button in AWS.

I’m not sure about you, but the problem of provisioning and de-provisioning SSH users on a fleet of nodes is a giant pain in the ass. The conventional wisdom is have a file called ~/.ssh/authorized_keys in the home folder of the user on the server you want to log into. That file will contain the SSH public key of authorized users. When a new engineer joins, you’ll have to add her to the system including adding her SSH public key.

If your company is old-school and doesn’t use a configuration management system, you know this pain. If you want to provision or deprovision a user, you’ll have to execute an SSH script and execute it on every node, then validate that that person is actually out of the system. Typically what you’ll see with a configuration management solution like Chef, as an example, is one where each user has their own databag with their public key in it, or there’s a flat file of users and public keys. The problem with this approach is that you have a explicitly do a Chef deploy to be able to add or remove users. From a security perspective this is a really bad thing, because in the event of someone’s laptop and SSH key being compromised, every second counts.

I’d like to propose a better way of handling SSH keys with any generic public key store. The key to this is the AuthorizedKeysCommand in the sshd_config. From the main page:

AuthorizedKeysCommand

Specifies a program to be used to look up the user's public keys. 
The program must be owned by root, not writable by group or others 
and specified by an absolute path. Arguments to AuthorizedKeysCommand 
accept the tokens described in the TOKENS section. If no arguments 
are specified then the username of the target user is used.

The program should produce on standard output zero or more lines of 
authorized_keys output (see AUTHORIZED_KEYS in sshd(8)). If a key 
supplied by AuthorizedKeysCommand does not successfully authenticate 
and authorize the user then public key authentication continues using 
the usual AuthorizedKeysFile files. By default, no 
AuthorizedKeysCommand is run.

AuthorizedKeysCommand

Specifies a program to be used
to look up the user's public 
keys. The program must be owned
by root, not writable by 
group or others and specified 
by an absolute path. Arguments 
to Authorized KeysCommand accept
the tokens described in the 
TOKENS section. If no arguments 
are specified then the username
of the target user is used.

The program should produce on
standard output zero or more 
lines of authorized_keys output
(see AUTHORIZED_KEYS in 
sshd(8)). If a key supplied by 
AuthorizedKeysCommand does not 
successfully authenticate and 
authorize the user then public 
key authentication continues 
using the usual AuthorizedKeys
File files. By default, no 
AuthorizedKeysCommand is run.

All this means is instead of giving a SSH public key statically to the server, you give ssh a command that is executed every time someone logs in. The first argument of this command must be the username of the user attempting to log in and the output is effectively an authorized keys file. Let’s get your wheels turning on some examples of how this could work at your company.

Let’s say you work at a company that uses AWS. Take a look at this very simple script that will authorize users based on SSH public keys that are entered in their IAM user profile.

#!/usr/bin/env ruby
require ‘aws-sdk-iam’
client = Aws::IAM::Client.new
resp = client.list_ssh_public_keys(user_name: ARGV[1], max_items: 1)
pub_key_id = resp.ssh_public_keys.first.ssh_public_key_id
puts client.get_ssh_public_key({
user_name: ARGV[1],
ssh_public_key_id: pub_key_id,
encoding: “SSH”
}).ssh_public_key_body
view rawiam-ssh-auth hosted with ❤ by GitHub

Then in your /etc/sshd_config you need only add:

 # /etc/ssh/sshd_config

    AuthorizedKeysCommand /path/to/iam-ssh-auth
    AuthorizedKeysCommandUser nobody

 # /etc/ssh/sshd_config

AuthorizedKeysCommand 
/path/to/iam-ssh-auth
AuthorizedKeysCommandUser
 nobody

I hope you can see that this is not at all tied to AWS. You could just as easily use this method, with Github, Consul, SQL, or almost any data store you like. Some considerations though is that if you replace your authorized_keys file with this, you need to be very sure that wherever you’re pulling data from is always accessible. I’d suggest something that chooses partition tolerance and availability from CAP theorem for this use case.

I hope this has shed some light on a really interesting and not often used feature of SSH.