Validate contents of .ssh/known_hosts file
Asked Answered
E

2

8

Is there some cli tool I can use to validate the contents of known_hosts? Maybe try to ping all the hosts in there and see if I can connect to each?

Probably using either ssh-keygen or ssh-keyscan?

Ervinervine answered 14/5, 2019 at 0:25 Comment(0)
N
5

If you have list of all hosts available you can do it like this:

ssh-keyscan -t rsa,dsa -f hosts_list > ~/.ssh/known_hosts_revised

This will generate a new known_hosts_revised which you can make a diff with your current know_hosts to see the differences.

If you don't need to compare it you can simply do ... > ~/.ssh/known_hosts to overwrite it (WARNING: the original known_hosts will be lost!)

The source of information are the OpenBSD man pages for ssh-keyscan(1).

Edit The hosts_list expected in for:

1.2.3.4,1.2.4.4 name.my.domain,name,n.my.domain,n,1.2.3.4,1.2.4.4
Nixon answered 16/5, 2019 at 6:18 Comment(0)
S
1

At least for my setup, using ssh-keyscan is impossible due to my extensive ~/.ssh/config file. I use lots of proxy commands, jump hosts, and alternate Hostname declarations.

For example:

# Connect to Tor nodes
Host *.onion
  ProxyCommand socat - SOCKS4A:localhost:%h:%p,socksport=9050

# Work jump box
Host bastion
  Hostname bastion.work.com

# Office system, e.g. bob.office -> bastion -> bob.work.com
Host *.office
  ProxyCommand ssh bastion nc -w600s $(echo "%h" |sed 's/\.office$/work.com/') %p

# Home system, e.g. adam -> home.com -> adam-laptop.local
Host adam
  Hostname adam-laptop.local
  ProxyJump home.com

None of the above will work.

Here's a script that should work for the rest though:

#!/usr/bin/awk -f

!/^#/ && NF > 2 {
  split($1, hosts, ",")
  key_type = $2
  gsub(/^ssh-/, "", key_type)
  gsub(/-.*/, "", key_type)
  for (h in hosts) {
    p = index(hosts[h], "]:")   # [host]:port (supports raw IPv6 hosts)
    if (!p && hosts[h] ~ /^[^:]+:[0-9]+$/) p = index(hosts[h], ":")  # host:port
    if (p > 0) {
      port = substr(hosts[h], p + 2)
      gsub("\[|\]?:" port, "", hosts[h])
    } else {
      port = 22
    }
    if (seen[key_type,port,hosts[h]]++) next  # prevent duplicate lookups
    if (port_list[key_type,port]) { comma = "," } else { comma = "" }
    port_list[key_type,port] = port_list[key_type,port] comma hosts[h]
  }
}
END {
  for (tp in port_list) {
    split(tp, a, SUBSEP)
    system("echo ssh-keyscan -t " a[1] " -p " a[2] " " port_list[tp])
  }
}

Remove the echo parts to run once you're convinced this will do what you desire.

This parses non-commented lines and with 3+ fields (since the format is host_list key_type key_hash). It splits the host list since it can be comma-delimited, and further parsing is needed because it can contain ports but ssh-keyscan cannot accept hosts in the format used by known_hosts.

There are two ways a port can be specified:

  1. The old style, which does not work with bare IPv6 addresses, is host:port
  2. The new style, which is required for bare IPv6 addresses, is [host]:port

p is set to the position of ]: if present (the new style). If that string isn't present, we check for the old style and reset p.

If p is positive, we have a port specification. Extract the port and remove (it and the square brackets) from the host name. Otherwise, the port is 22.

Just in case there are duplicate entries, we check for them and continue if we've already seen the type,port,host combination (x++ is false (0) only when first run). Finally, we push the host to a comma-delimited list string in the port_list array as keyed by a tuple of type and port.

After reading in the entire known_hosts file, we iterate on the type,port tuple pairs that key the port_list array, split them into an array named a, and run ssh-keyscan on them.

Run this like awk -f 'this_script.awk' ~/.ssh/known_hosts and if you like the ssh-keyscan commands that it spits out, remove the echo from the system command and re-run.

Do not pipe this output into ~/.ssh/known_hosts! You will want to manually review the results (and probably filter out the comments). Also, you can't redirect output onto one of the files used in the input.

Splutter answered 22/5, 2019 at 16:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.