I use a cluster of about 30 machines that have all recently been reconfigured with new OpenSSH host keys. When I try to log into one, I get this error message (many lines removed for brevity):
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
The fingerprint for the RSA key sent by the remote host is
52:bb:71:83:7e:d0:e2:66:92:0e:10:78:cf:a6:41:49.
Add correct host key in /home/nr/.ssh/known_hosts to get rid of this message.
Offending key in /home/nr/.ssh/known_hosts:50
I can go remove the offending line manually, in which case I get a different complaint about the IP addresss, which requires removing another line manually, and I have no desire to repeat this exercise 29 times. I would like to write a program to do it. Unfortunately, the line in the .ssh file no longer contains the host name and IP address in clear text, as it did in earlier versions.
So here's my question:
- Given a host name and an IP address, how can I write a program to find out which lines of my
~/.ssh/known_hosts
store an SSH host key for that host or IP address?
If I can recover this info, I think I can do the rest myself.
Footnote: I would prefer to code in bash/ksh/sh or C or Lua; my Perl and Python are very rusty.
Clarifications:
I don't want to remove the whole file and repopulate it; it contains over a hundred validated keys that I prefer not to re-validate.
Whether I maintain a single master copy or multiple replicas, the problem of scrubbing away a large group of obsolete host keys remains.
Answer
Here's the Lua script I wrote using ssh-keygen -F
:
#!/usr/bin/env lua
require 'osutil'
require 'ioutil'
local known = os.getenv 'HOME' .. '/.ssh/known_hosts'
local function lines(name)
local lines = { }
for l in io.lines(name) do
table.insert(lines, l)
end
return lines
end
local function remove_line(host)
local f = io.popen('ssh-keygen -F ' .. os.quote(host))
for l in f:lines() do
local line = l:match '^# Host %S+ found: line (%d+) type %u+$'
if line then
local thelines = lines(known)
table.remove(thelines, assert(tonumber(line)))
table.insert(thelines, '')
io.set_contents(known, table.concat(thelines, '\n'))
return
end
end
io.stderr:write('Host ', host, ' not found in ', known, '\n')
end
for _, host in ipairs(arg) do
local ip = os.capture('ipaddress ' .. host)
remove_line(host)
remove_line(ip)
end