Blog What we do Support Community
Login Sign up

Flexible, secure SSH with DNSSEC

by Etienne Labaume.

UPDATE: Corrected the paragraph about the permissions of the AuthorizedKeys file.

If you read this blog on a regular basis, you probably use the little tool called SSH, especially its ubiquitous and most popular implementation OpenSSH.

Maybe you’re savvy enough to only use it with public/private keys, and therefore protect yourself from dictionary attacks. If you do then you know that in order to configure access to a new host, you need to make a copy of a public key available to that host (usually by writing it to its disk). Managing keys can be painful if you have many hosts, especially when you need to renew one of the keys. What if DNSSEC could help?

Managing keys can be painful
CC BY 2.0 image by William Neuheisel

With version 6.2 of OpenSSH came a feature that allows the remote host to retrieve a public key in a customised way, instead of the typical authorized_keys file in the ~/.ssh/ directory. For example, you can gather the keys of a group of users that require access to a number of machines on a single server (for example, an LDAP server), and have all the hosts query that server when they need the public key of the user attempting to log in. This saves a lot of editing of authorized_keys files on each and every host. The downside is that it's necessary to trust the source these hosts retrieve public keys from. An LDAP server on a private network is probably trustworthy (when looked after properly) but for hosts running in the cloud, that’s not really practical.

DNSSEC is helpful here. That's right: now that we can verify responses from a DNS server, we can safely store public keys in DNS records!

So let's say we administer example.com and want to give Alice and Bob access to machines foo, bar and baz in that domain. We'll store their respective public keys in TXT[1] records named alice_pubkey.example.com and bob_pubkey.example.com. To be entirely accurate, it doesn’t really matter which zone these records belong to, but I’ll consider here that we only have one domain. The requirements are:

  • the machines need to run OpenSSH server version 6.2 or later
  • they also need a DNSSEC validating resolver (we'll use unbound-host)
  • Alice and Bob's keys need to be less than 256 characters long (ECDSA or Ed25519 keys will work)
  • DNSSEC needs to be correctly set up on the domain example.com (surprise!)

Alice and Bob generate keys like this:

foo:~$ ssh-keygen -t ecdsa

or like this:

foo:~$ ssh-keygen -t ed25519

and then follow the instructions. They will of course provide a non-empty passphrase. Then they send us (or whoever administers the zone file for example.com) the public key file, which may look like this:

ssh-ed25519 AAAAC3N...VY4A= alice@foo

We can strip the comment alice@foo out of that file, and use the rest as the value to create a TXT record with the name alice_pubkey in the domain example.com. Then, retrieving the key is as easy as this:

 foo:~$ unbound-host -t TXT alice_pubkey.example.com
 alice_pubkey.example.com has TXT record “ssh-ed25519 AAAAC3N…”

With -v, unbound-host will show us whether the signature has been verified

foo:~$ unbound-host -v -t TXT alice_pubkey.example.com
alice_pubkey.example.com has TXT record “ssh-ed25519 AAAAC…” (insecure)

With -D, it will actually check the signature:

foo:~$ unbound-host -D -v -t TXT alice_pubkey.example.com
alice_pubkey.example.com has TXT record “ssh-ed25519 AAAAC3N…” (secure)

If no record exists, it will show this:

foo:~$ unbound-host -D -v -t TXT charlie_pubkey.example.com
charlie_pubkey.example.com has no TXT record (secure)

Note that the absence of record is also labelled “secure”, thanks to NSEC.

Let’s prepare to parse that output. The sshd_config man page shows that sshd needs a specific user to run the program that will retrieve public keys. This is following the best practices of privilege separation. Let's call that user pubkeygrab and create an account on foo, bar and baz, giving it just the permissions it needs to work and nothing more:

foo:~$ useradd -m -d /var/empty -s /sbin/nologin pubkeygrab

Then create the script pubkeygrab.sh, and store it on each of the machines. Obviously, we'll make sure only root can edit it:

foo:~$ cat /usr/local/bin/pubkeygrab.sh
#!/bin/sh

USER=$1

/usr/sbin/unbound-host -v -D -t TXT ${USER}_pubkey.example.com \\
     | /usr/bin/grep -v "no TXT record" \\
     | /usr/bin/grep ' (secure)$' \\
     | /usr/bin/sed 's/.* "\(.*\)" (secure)$/\1/'

Now I'm certain that a lot of readers will have something to say about the style or the efficiency of this shell script, I just wrote it that way to highlight what steps need to be taken:

  • it retrieves a TXT record, and doesn't output anything if the record doesn't exist
  • if unbound-host has not confirmed that the record was correctly DNSSEC signed, it doesn't output anything
  • if the above is successful, it filters out the text to return only the public key
  • it doesn't try to do anything complex, because complexity is the enemy of security (or at least, that’s a point of view that I share with a few people)
  • it works with multiple records

I'm sure you will write your own program to do the above. Just make sure it works only when you want it to. It is critical to ensure that it doesn't return anything at least when:

  • a record for the corresponding user doesn't exist
  • the records are not signed or not properly signed
  • the local copy of the root key (/var/unbound/root.key, here) is corrupted.

Bonus points to you if you find more cases.

Now that you have read the warning above, add the following to /etc/ssh/sshd_config on foo, bar and baz:

AuthorizedKeysCommand /usr/local/bin/pubkeygrab.sh
AuthorizedKeysCommandUser pubkeygrab

and restart sshd. Check that the users alice and bob exist on each machine too. Note that the above change will also apply to all existing users. Now you can go to your CloudFlare account, select the domain example.com, and create the TXT records alice_pubkey and bob_pubkey. Paste their respective public keys in the value field. Soon after, Alice and Bob can log in. Ask Charlie to try too. If the above works for Alice and Bob, but fails for Charlie, congratulations, you have turned CloudFlare into a PKI for SSH.

If you remove the TXT records, Alice and Bob’s access should be revoked, and they should be unable to login, once the TTL of the TXT record is expired. However, note that when the output of pubkeygrab.sh is empty, sshd reverts to the usual AuthorizedKeysFile parameter to find a public key. If Alice and Bob are cheeky and want to keep their access after you removed their TXT records, they just need to copy their public key into that file any time before you ban them. If you don't want that, make sure the AuthorizedKeysFile parameter points to a place Alice and Bob can't write to.

I hope this is showing how interesting DNSSEC can be, and that we have more news on this topic soon.


  1. Yes, it would be better to have a dedicated record, instead of overloading TXT records. ↩︎

comments powered by Disqus