Tag: openssh

Applying OpenSSH host settings on a per-network basis

Up through the 1980s, the Internet was a neatly organized place, clearly split into “class A”, “class B” and “class C” networks. In this classful environment, numerical IP addresses were split between the network and the host portion at an octet boundary, and where this boundary was placed was defined by the first few bits of the first octet. For example, by definition was host 100 on the “class C” network 192.0.2. Even today, people occasionally speak of “class A”, “class B” or “class C” networks when what they really mean is a /8, /16 or a /24 network respectively.

In the early 1990s, we got what is today known as CIDR, or classless inter-domain routing. This is the slash-notation for network prefix length that is commonly seen today, and which does not need to line up with an octet boundary at all or have any connection with the bits of the first octet;,,,, 2001:200::/48 and 2001:db8::/33 are all examples of perfectly valid CIDR network prefix specifications, but only the first matches the old classful assignment scheme (as 192 was part of the old class C space, and what used to be termed a class C network in modern terms is called a /24).

Also, at times, it is useful to apply specific OpenSSH configuration directives to every host on a network. For example, you might want StrictHostKeyChecking yes for a production network, but StrictHostKeyChecking ask or maybe even no for a testing network where you regularly replace machines.

If these networks are segregated on an IP address octet boundary, it’s fairly easy. For example, if testing is, you can feel fairly certain that OpenSSH will do the right thing if you give it something like:

Host 10.99.*.*
StrictHostKeyChecking ask

Host *
StrictHostKeyChecking yes

However, if your testing network is, say, an IPv4 /28 which straddles a hundreds boundary, it quickly gets unwieldy; already for, you might need:

Host ! ! ! !
StrictHostKeyChecking ask

Thankfully, OpenSSH can execute external commands to determine whether a configuration block should be applied. Enter Match exec combined with grepcidr.

Match exec "echo %h | grepcidr -sx >/dev/null 2>/dev/null"
StrictHostKeyChecking ask

The redirections are needed because unfortunately grepcidr has no equivalent to GNU grep’s -q (quiet or, if you wish, query) option.

Some sites suggest using bash-isms:

Match exec "grepcidr -sx <(echo %h) &>/dev/null"

which I found will probably work well when run from a typical interactive shell, but maybe not so well when ssh is being run from automated scripts or background processes such as cron or at. This can affect even connections which wouldn’t match that network at all.

There are two big things to keep in mind with this.

First, in OpenSSH configuration parlace, %h expands to the host name as given (either on the command line or through for example a Hostname directive); if this is not an IP address but rather a DNS name, the above examples won’t match because grepcidr does no DNS resolution, resulting in those settings not being applied to the connection. If this is a consideration, you’ll probably need to wrap it in some kind of helper script to detect a non-IP-address and resolve it before passing the resultant IP addresses to grepcidr. If you do, then beware of time-of-check-to-time-of-use issues!

Second, and simpler, the above snippets all rely on the user’s $PATH to find the tools involved. Especially if putting this in a system-wide configuration file, it’s advisable to extend the full paths to each respective binary; which might be, for example, /bin/echo and /usr/bin/grepcidr.

Using per-account SSH key files with OpenSSH

OpenSSH is a SSH server and client, in current incarnations originating on OpenBSD but used on many Unix-like operating systems, including being a common choice of SSH server and client alike on many Linux systems.

Unfortunately, it (and perhaps other SSH clients as well) in the default configuration and typical use has a somewhat nasty information leak when used with key pair authentication.

This is because of the interaction between three things.

First, a SSH client will, during authentication, offer a series of keys to the server, effectively asking for each “will you let me authenticate as this user using this key?”.

Second, the initial exchange that offers each key in turn contains enough information that the key can, effectively, be uniquely identified. It must for the server to reach a meaningful answer.

Third, the OpenSSH SSH client will, by default, try every key that it knows about to find one that the server is willing to accept for a connection attempt.

All of this would already be bad enough from a potential information leak perspective, but in isolation, it still largely only allows a rogue server to learn what keys exist on the connecting system and user account while the user is actively connecting to it, but nothing more about them or what other context those keys exist within. Not great, but not horrible.

However, additional information exists. For example, as noted by Andrew Ayer, GitHub actually publishes each user’s authorized SSH keys. This in itself isn’t a huge problem either; only the public keys are published, so as long as the keys are secure enough, there’s no real risk of compromise of a person’s GitHub access.

Put all of this together, though, and it becomes quite possible for a SSH server to derive the GitHub username of a connecting user, if that person uses OpenSSH with its defaults.

All of a sudden, a SSH server can potentially deanonymize a connecting person by, with a rather high degree of certainty, associating the connecting user with a GitHub user account.

Similarly, if multiple services publish keys in this manner, it’s fairly easy to collate them together and look for matches. If the same key is authorized for more than one account, or for accounts with more than one service, there is a rather high probability that those accounts belong to the same person, even if there is nothing else to suggest this.

A necessary first step to protect against this information leak is to use different key pairs for each such service. ssh-keygen has -f to specify the base file name to which to save the newly generated key pair; ssh has -i to specify the identity file to use; and ssh_config (usually ~/.ssh/config and /etc/ssh/ssh_config) has the IdentityFile directive. However, this doesn’t necessarily prevent the SSH client from presenting other known keys during connection key exchange.

To prevent the latter, use the IdentitiesOnly yes directive in ssh_config. This causes the SSH client to only present any explicitly configured identities during public key authentication, protecting against the server you are connecting to learning more about what keys you have on the system you are connecting from than you intended.

Unfortunately, setting these on a per-host basis in the SSH client configuration quickly gets tiresome if you have multiple accounts, and is error-prone.

Thankfully, OpenSSH offers macro expansion in the IdentityFile value based on information about, among other things, your local user account and the connection you are making. (See the ssh_config(5) manual page for a full list and description of the macro expansion tokens.) This is especially useful in conjunction with wildcard Host stanzas to provide a set of defaults.

Putting all this together you can, for example, put at the bottom of your ssh_config something like

Host *
IdentitiesOnly yes
IdentityFile %d/.ssh/keys/%h/%r/current
PasswordAuthentication yes
PubkeyAuthentication yes
PreferredAuthentications publickey,password
User nobody

and together with it (I prefer above, with the Host * providing the defaults), a Host stanza to simply set the correct username

Host ssh.example.com
User myself

With this in place, when you connect to ssh.example.com, OpenSSH will offer only the key pair in ~/.ssh/keys/ssh.example.com/myself/current for authentication. (%d expands to the path to your local home directory; %h expands to the name of the host you are connecting to; and %r expands to the remote username.)

To then add keys for a new account, use something like

$ mkdir -p ~/.ssh/keys/sftp.example.net/u1234567
$ ssh-keygen -f ~/.ssh/keys/sftp.example.net/u1234567/current

and either specify the username when connecting (for example, sftp u1234567@sftp.example.net ...), or add another Host stanza to your ssh_config specifying the username

Host sftp.example.net
User u1234567

If you don’t do either, the OpenSSH client will try to read the key pair from ~/.ssh/keys/sftp.example.net/nobody/current (because of the Host * stanza’s User nobody), find nothing at that file location, and not offer any key pair at all for authentication to the server. In the example case above, it will then fall back to password authentication. Since nobody likely doesn’t have a valid password, this effectively blocks the login attempt in a non-destructive manner while leaking minimal information either over the network or to the remote server.

Setting this in your ssh_config as defaults like this also neatly fits into many tools’ SSH integration, where it can be tricky to pass additional parameters, especially if those are dependent on for example where you are connecting to.

With this in place, you can still use the same key pair for more than one account by putting the actual key pair files in some location and symlinking from the location expanded to based on the IdentityFile directive. However, instead of the same key pair being used by default for every account everywhere unless you take special care to use separate key pairs for each account, using the same key pair for multiple accounts now becomes the active, rather than passive and by default, choice.

It also becomes much easier to rotate a key pair if you ever have reason to, because with this in place, you don’t need to stop to consider where it’s used; where it’s stored locally tells you the one remote account for which it’s being used.

Someone could still look at your authorized SSH keys on GitHub, but now it’s very little more than an anonymous blob of encoded public key data that can’t be matched against any other keys that they might encounter.

Powered by WordPress & Theme by Anders Norén