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, 192.0.2.100 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; 192.0.2.0/24, 10.0.0.0/13, 198.18.0.0/15, 172.16.0.192/27, 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 10.99.0.0/16, 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 192.0.2.192/28, you might need:
Host !192.0.2.190 !192.0.2.191 192.0.2.19? !192.0.2.208 !192.0.2.209 192.0.2.20?
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 192.0.2.192/27 >/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 192.0.2.192/27 <(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
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,