23

Many services (like GitHub) use a wide range of IPs, and obviously the same public key.

How can I add an IP range (preferably in a single) to known_hosts file?

For the GitHub example, it uses the following ranges:

  • 207.97.227.224/27
  • 173.203.140.192/27
  • 204.232.175.64/27
  • 72.4.117.96/27
  • 192.30.252.0/22

And the key is:

AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==

1
  • 1
    those IPs are completely public here
    – RSFalcon7
    Oct 14, 2013 at 14:14

7 Answers 7

19

As noted in other answers, known_hosts does not have support for IP address ranges. It does, however, support wildcards. Of course wildcards aren't quite the same thing, so you need to be really careful about how you use them in IP addresses, but in the particular case of GitHub this can be done safely.

The situation seems to have gotten simpler since the question was asked. According to GitHub's official documentation there is only one IP address range in use (at least as far as IPv4 goes). This is the 192.30.252.0/22 range. That makes for 1020 possible IP addresses that conveniently span the entire possible range for the last octet in just four different C blocks.

From man 8 sshd, this is what we have to work with in known_hosts:

Hostnames is a comma-separated list of patterns (* and ? act as wildcards); each pattern in turn is matched against the canonical host name (when authenticating a client) or against the user-supplied name (when authenticating a server). A pattern may also be preceded by ! to indicate negation: if the host name matches a negated pattern, it is not accepted (by that line) even if it matched another pattern on the line. A hostname or address may optionally be enclosed within [ and ] brackets then followed by : and a non-standard port number.

Using this info, we can construct an entry using the * wildcard for the last octet that matches all possible GitHub endpoints (and only those endpoints) like so:

github.com,192.30.252.*,192.30.253.*,192.30.254.*,192.30.255.* ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==

If the IP range you needed to construct did not fill a full C block (and thus all possible values for an octet), it would be impossible to use wildcards for such an accurate match.

3
  • 2
    This is no longer so simple; the list of IPv4 CIDRs is currently quite massive, including 140.82.112.0/20 and 192.30.252.0/22 and 185.199.108.0/22 plus a big pile of individual IPs, and known_hosts doesn't support character classes like 140.82.11[23].*. Also, this permits hosts like 192.30.252.example.com
    – Adam Katz
    Dec 10, 2020 at 16:56
  • 1
    This host list is slightly safer: github.com,gist.github.com,github-server-pool.github.com,140.82.1??.?,140.82.1??.??,140.82.1??.???,192.30.25?.?,192.30.25?.??,192.30.25?.???,185.199.1??.?,185.199.1??.??,185.199.1??.???. Hopefully Github will return github-server-pool.github.com to its former glory so we can go back to using the HostKeyAlias solution rather than hard-coding matchers for the larger IP ranges.
    – Adam Katz
    Dec 10, 2020 at 23:57
  • That list is now too unwieldy, so I suggest using CheckHostIp no for just github hosts as outlined in my answer.
    – Adam Katz
    Oct 12, 2021 at 15:34
11

I do not think you can easily add the ranges, but I think (can't test this right now) that the same effect can be achieved by adding the following to .ssh/ssh_config:

Host *.github.com
HostKeyAlias github-server-pool.github.com

Next, you would add the key to the known_hosts file under the name github-server-pool.github.com.

Assumption: the host github-server-pool.github.com does not exist or is never connected to through SSH.

The idea behind it, is that ssh will use the key github-server-pool.github.com as the key to lookup the public host key for all hosts of the github.com domain.

2
  • This is a great answer, and easier than the original.
    – kael
    Feb 14, 2018 at 19:15
  • Sadly, github-server-pool.github.com is now an alias to github.github.io which only specifies four IPs, none of which seem to be in the rotations of IPs that I'm encountering (I listed the current CIDRs in my other comment, via Github's official documented CIDRs).
    – Adam Katz
    Dec 10, 2020 at 17:01
4

There is no support for IP address sets in the known_hosts file. You'll have to have one line per address.

Although the host name part of entries is hashed by default, this is only for privacy so that someone getting hold of your .known_hosts wouldn't be able to easily find out which hosts you've been connecting to. (They can still verify guesses.) You can use a plain host name or IP address.

for net in 207.97.227.224/27 173.203.140.192/27 204.232.175.64/27 72.4.117.96/27 192.30.252.0/24 192.30.252.1/24 192.30.252.2/24 192.30.252.3/24; do
  base=${net%/*}; d=${base##*.}; abc=${base%.*}
  bits=$((32 - ${net#*/}))
  e=0
  while [ $e -lt $((2 ** bits) ]; do
    echo "$abc.$((d + e)) ssh-rsa AAAAB3NzaC1yc…" >>~/.ssh/known_hosts
    e=$((e + 1))
  done
done

Note that this may add duplicates.

4
  • 1
    fixed, but only working for networks up to 0.0.0.0/24
    – RSFalcon7
    Oct 14, 2013 at 14:27
  • @RSFalcon7 Indeed. IP address arithmetic is annoying. I made a quick-and-dirty workaround… If you need support for larger networks, convert the IP address to a 32-bit number before doing the enumeration. Oct 14, 2013 at 17:44
  • You can comma-delimit hosts, including IP addresses, in this file. You can also use ? and * glob characters (but not […]). That means it's still messy and redundant, but not quite as bad as this answer implies. (Also, as noted in the first comment to this answer, this won't work for networks larger than /24. Github currently specifies two /22s.
    – Adam Katz
    Dec 10, 2020 at 21:02
  • 1
    Github's IP list is now too unwieldy, so I suggest using CheckHostIp no for just github hosts as outlined in my answer.
    – Adam Katz
    Oct 12, 2021 at 15:35
4

I asked on Twitter about CIDR support for known_hosts and ssh_config, citing this question and a remark on ServerFault to a similar question that cites an OpenSSH feature request for CIDR notation (in Host declarations, not known_hosts).

OpenSSH developer Damien Miller answered:

I think we're more likely to turn off CheckHostIP (the thing that spams addresses into known_hosts) in the short term, as nobody has satisfactorily explained what problem it solves to me.

CIDR in ssh_config probably is less likely, as there are too many chicken and egg problems in config parsing vs address resolution.

Maybe we could do it with another parsing pass through the configuration, but that's an ugly solution...

So there's no proper solution to this question though there is a solution you can implement either globally (which, as Damien said, may be fine) or else on a per-host basis (a reasonable compromise): remove the IP checks in your ~/.ssh/config:

Host github.com *.github.com
  CheckHostIp no

As noted in a comment on this page and as I noted later in that Twitter thread, Github is not following best practices here. When deploying multiple hosts, each host should have a unique private key. In this case, Github is almost certainly copying that key across a wide pool of hosts. OpenSSH expects servers to have unique keys, which is a part of the design behind CheckHostIP.

Since Github shares a single private key across all of these hosts, it'll need be replaced across all of those hosts the moment just one of them gets compromised. Who knows how many systems that'll break worldwide when the key fingerprint no longer matches. (Compare that to a single host changing its key; cron jobs that SSH to Github would periodically fail but would still usually work fine.)

4
  • 1
    I disagree that GitHub should use one key per host. If I push some code and then take a train to a nearby city (or VPN to anywhere), my DNS lookup for GitHub might change. Next time I connect, ssh is going to freak out when the key for github.com:22 is different. And if I don't know what's going on, I'm going to freak out, too. Sure, every single GitHub user could hard-code their ~/.ssh/config to permanently use the first IP that their DNS lookup used for GitHub, but that's total madness. Aug 26, 2022 at 15:11
  • @Michael – If just one of these hosts is compromised, they all are. The response would of course be to change SSH keys on all of them, which would create "total madness". OpenSSH doesn't have elegant solutions here, though the new UpdateHostKeys feature is a step in the right direction (see Damien Miller's blog post on Hostkey rotation, redux).
    – Adam Katz
    Aug 26, 2022 at 18:32
  • If a GitHub private key is compromised, we're already in total madness freak-out territory, but for a real reason instead of an illusory one. There may be a clever way to appropriately accept new per-server keys for a host, but everything I've thought of so far is functionally equivalent to "just use the same key for all the servers." I'd be happy to hear better solutions. Aug 26, 2022 at 19:08
  • FWIW, I am with you on the CheckHostIp no for server farms with shared keys and anything behind dynamic DNS. I'd upvote for that, but I really disagree with the second half of your answer. Aug 26, 2022 at 19:23
1

I wrote a small Ruby script, github_known_hosts, that can expand the IP address ranges GitHub publishes from their meta API endpoint (https://api.github.com/meta).

#!/usr/bin/env ruby

require "ipaddr"
require "json"

pubkey = "AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ=="
ips    = JSON.parse(STDIN.read)["git"].flat_map { |net| IPAddr.new(net.chomp).to_range.map(&:to_s) }

puts "github.com,gist.github.com,#{ips.join(',')} ssh-rsa #{pubkey}"

Use it like this, and it will produce a very long line you can stick in your ~/.ssh/known_hosts file:

curl --silent https://api.github.com/meta | github_known_hosts >> ~/.ssh/known_hosts
0

SSH seem to have no concept of IP ranges for known_hosts. I think the assumption is that each host would have a unique key for security reasons.

Two ways I can see to pre-populate your known_hosts:

  1. ssh-keyscan - Write a brief script to iterate through all those addresses and either feed it to ssh-keyscan or a file for ssh-keyscan to read. ssh-keyscan can scan multiple hosts per invocation, either via specifying on one line or specifying a list of the hosts.

  2. Populate known_hosts yourself with a script or editor. The format is fairly simple if you use the non-hashed version. It is:

    hostname,IP address ssh-keytype key

hostname is the hostname you contact, and would be the same for all the GitHub addresses. IP address would be what a script would iterate through. key is the key you provided above.

Neither is elegant, but I think the SSH folks assumed no one would do what GitHub is doing.

3
  • 1
    Nobody should be doing what github is doing. It's a host key, not a "group of similar hosts" key.
    – user41515
    Oct 14, 2013 at 14:57
  • @WumpusQ.Wumbley I agree. But I doubt GitHub will change on my say so.
    – kurtm
    Oct 14, 2013 at 14:58
  • @user41515 The thing is, if you had a concept of a "group of similar hosts" key, it would have just as much power as a shared host key. If you steal the "g.o.s.h." key, you can add your malicious server to the list and attempt to poison DNS to point victims to it, just like if you steal a shared key. Aug 26, 2022 at 19:19
0

Hi I found the script from Gilles quite usefull, but only working for networks up to 0.0.0.0/24 was a limitation, I've extended the script to work with larger networks up to 0.0.0.0/16 perhaps it will be useful for someone else.

#!/bin/sh
# http://unix.stackexchange.com/questions/94448/how-to-add-an-ip-range-to-known-hosts
# answered Oct 11 '13 at 0:21  Gilles
# only working for networks up to 0.0.0.0/24
# Declan Forde - Increased the range up to 0.0.0.0/16 networks


NETWORKS="127.0.0.0/30 127.0.0.0/29 127.0.0.0/28 127.0.0.0/27 127.0.0.0/26 127.0.0.0/25 127.0.0.0/24 127.0.0.0/23 127.0.0.0/22 127.0.0.0/21 127.0.0.0/16"

for net in ${NETWORKS}
do
  base=${net%/*}
  bits=$((32 - ${net#*/}))

  abc=${base%.*}
  ab=${abc%.*}
  c=${abc##*.}
  d=${base##*.}

  if [ $bits -gt 8 ] && [ $bits -le 16 ]
  then
    netbits=$((bits - 8))
    bits=8
  else
    netbits=0
  fi

  netcount=0
  while [ $netcount -lt $((2 ** netbits)) ]
  do
    count=0
    while [ $count -lt $((2 ** bits)) ]
    do
      echo "$ab.$c.$((d + count))"
      echo "$ab.$c.$((d + count)) ssh-rsa AAAAB3NzaC1yc." >>~/.ssh/known_hosts
    count=$((count + 1))
    done
    netcount=$((netcount + 1))
    c=$((c + 1))
  done
done

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .