Skip to content

Instantly share code, notes, and snippets.

@suominen
Created March 5, 2023 20:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save suominen/232318c597ef9a2a925bc2a1dddb5dac to your computer and use it in GitHub Desktop.
Save suominen/232318c597ef9a2a925bc2a1dddb5dac to your computer and use it in GitHub Desktop.
Refresh a host's keys in ~/.ssh/known_hosts
#!/bin/sh
# @(#)$KimmoSuominen: .cfg/home/bin/share/refresh-ssh-host-keys,v 1.2 2023/03/05 20:24:01 kim Exp $
#
# Refresh a host's keys in ~/.ssh/known_hosts
#
# 20221122 Kimmo Suominen
#
#
# Copyright (c) 2022-2023 Kimmo Suominen
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
set -eu
umask 077
PATH=/bin:/usr/bin
export PATH
PROG="${0##*/}"
KNOWN_HOSTS="$(realpath "${HOME}/.ssh/known_hosts")"
die()
{
echo "${PROG}: ${@}" 1>&2
exit 1
}
msg()
{
echo "${PROG}: ${@}" 1>&2
}
usage()
{
cat <<EOF
Usage: ${PROG} [-n] [-p port] host
Retrieve a new SSH host key using ssh-keyscan, and replace the old key
in ~/.ssh/known_hosts with it.
Options:
-n Make no changes (no-op)
-p Port number to connect to
EOF
}
# 34567890123456789012345678901234567890123456789012345678901234567890123456789
find_host_entry()
{
awk -v HOST="${1}" '$1 ~ HOST {print $1}' "${KNOWN_HOSTS}" \
| sort -u
}
retrieve_keys()
{
{ ssh-keyscan ${port:+-p "${port}"} "${1}" || true ; } \
| cut -d' ' -f 2- \
| sort -u \
| awk -v ENTRY="${1}" '{print ENTRY, $0}'
}
# 34567890123456789012345678901234567890123456789012345678901234567890123456789
noop=
port=
while getopts hnp: opt
do
case "${opt}" in
h) usage; exit 0;;
n) noop=true;;
p) port="${OPTARG}";;
*) usage 1>&2; exit 1;;
esac
done
shift $((${OPTIND} - 1))
WRKDIR="$(mktemp -d "${TMPDIR-/tmp}/${PROG}.XXXXXXXXXX")"
OLD="${WRKDIR}/old"
NEW="${WRKDIR}/new"
trap "rm -rf ${WRKDIR}" 0 1 2 15
cp "${KNOWN_HOSTS}" "${NEW}"
for host
do
ENTRY="$(find_host_entry "${host}")"
case "${ENTRY}" in
'') msg "'${host}' matched no entries"; continue;;
esac
COUNT="$(echo "${ENTRY}" | wc -l)"
case "${COUNT}" in
1) ;;
*) die "'${host}' matched ${COUNT} entries";;
esac
msg "Found ${ENTRY}"
NEWKEYS="$(retrieve_keys "${ENTRY}")"
case "${NEWKEYS}" in
'') msg "No keys received"; continue;;
esac
awk -v HOST="${ENTRY}" '$1 != HOST {print}' "${NEW}" > "${OLD}"
if [ ! -s "${OLD}" ]
then
die "Oops, old file became empty, aborting"
fi
{ cat ${OLD} ; echo "${NEWKEYS}" ; } | sort > "${NEW}"
if [ ! -s "${NEW}" ]
then
die "Oops, new file became empty, aborting"
fi
done
if cmp -s "${KNOWN_HOSTS}" "${NEW}"
then
msg "Keys did not change"
else
msg "Keys updated"
if ${noop:-false}
then
diff -u "${KNOWN_HOSTS}" "${NEW}"
else
mv "${NEW}" "${KNOWN_HOSTS}"
fi
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment