title: How can I write a program (script) to remove obsolete host keys from ~/.ssh/known_hosts?
body: <p>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):</p>
  
  <pre><code>@    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
  </code></pre>
  
  <p>I can go remove the offending line manually, in which case I get a different complaint about the IP addresss, which requires removing <em>another</em> 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.</p>
  
  <p>So here's my question:</p>
  
  <ul>
  <li><strong>Given a host name and an IP address, how can I write a program to find out which lines of my <code>~/.ssh/known_hosts</code> store an SSH host key for that host or IP address?</strong></li>
  </ul>
  
  <p>If I can recover this info, I think I can do the rest myself.</p>
  
  <hr>
  
  <p>Footnote: I would prefer to code in bash/ksh/sh or C or Lua; my Perl and Python are very rusty.</p>
  
  <hr>
  
  <p>Clarifications:</p>
  
  <ul>
  <li><p>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.</p></li>
  <li><p>Whether I maintain a single master copy or multiple replicas, the problem of scrubbing away a large group of obsolete host keys remains.</p></li>
  </ul>
  
  <h2>Answer</h2>
  
  <p>Here's the Lua script I wrote using <code>ssh-keygen -F</code>:</p>
  
  <pre><code>#!/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
  </code></pre>