Stop using passwords to login with ssh!

Want to stop using passwords to log into your various remote accounts? You may have read on other web pages that you can use ssh-keygen on your unix/linux box to generate a public/private key pair which SSH will then offer to remote machine when you try to log into them. The annoying part is that you have to copy/paste the public key into the authorized_keys2 file on every remote host that you want to log into. If you manage n machines, this means that you'll have to perform n*(n-1) copy/paste operations. I manage about 5 machines these days... that's 20 copy/pastes. I don't care to do that manually... expecially since you have to make sure that you don't have stray newline characters in your keys file, and you also have to make sure that the permissions on the key file are set correctly, or the public-key login process won't work and you'll have little indication of why.

Enter SSHMigrate. This is a script that I wrote that lets you just type sshmigrate username@otherhost.com and the script will find all of your SHH1 and SSH2 public keys in the current account and will migrate them into the keys files on the remote machine. It also lets you send over all of the keys in your local authorized_keys* files to the remote host, to make it easier to get all of your public keys on all of your other hosts.

Download SSHMigrate

Here's what the script contains, in case you're interested...

#!/bin/sh
#
# SSHMigrate - v0.1
#
# Joe Emenaker (joe@emenaker.com) 
# 2007-02-16
#
# This script lets you specify any remote user/host combination
# (ie, sshmigrate someuser@some.host.com) and it will send
# your ssh public keys and import them into the authorized_keys
# files on the remote host... thereby eliminating the need
# for you to supply a password when ssh'ing to that host
# from this one.

# Some options:

# SEND_AUTHKEYS (0|1)
#   Whether to just send the public keys for *this* host or to also
#   send all public keys in authorized_keys and authorized_keys2
#   This is handy if you have a group of hosts/accounts that
#   you want to seamlessly login to/from.
SEND_AUTHKEYS=1

# SEND_SCRIPT (0|1)
#   Whether to scp this script to the remote host (for further migration)
#   after this migration is complete
SEND_SCRIPT=1

# REFLECT (0|1)
#   Whether to attempt to run SSHMigrate on the *remote* host back
#   to this one. (Implies SEND_SCRIPT=1)
REFLECT=0

# REMOTE_HOSTS (space-separated string)
#   A list of remote hosts to copy our keys to. If this is empty,
#   then the user must supply the remote host on the command-line
#   If this is *not* empty, supplying a host(s) on the command-line
#   over-rides this.
REMOTE_HOSTS=""


SSH1PUBKEY="$HOME/.ssh/identity.pub"
SSH2RSAPUBKEY="$HOME/.ssh/id_rsa.pub"
SSH2DSAPUBKEY="$HOME/.ssh/id_dsa.pub"

SSH1AUTHKEYS="$HOME/.ssh/authorized_keys"
SSH2AUTHKEYS="$HOME/.ssh/authorized_keys2"

WHOAMI=`whoami`
WHEREAMI=`hostname -f`
SCRIPTNAME=`echo $0 | sed 's!.*/!!'`

###################################
###################################
###################################
# REMOTE SCRIPT START
###################################
###################################
###################################
send_remote_script() {
cat << 'ENDOFSCRIPT'
#!/bin/sh
#
# Joe Emenaker (joe@emenaker.com)
# 2007-02-16

# Where are our authorized keys stored?
TYPE1STORE="$HOME/.ssh/authorized_keys"
TYPE2STORE="$HOME/.ssh/authorized_keys2"

MYNAME=`whoami`
MYGROUP=`groups | cut -d" " -f1`

set_perms() {
	FILE="$1"
	chmod 600 "$FILE"
	chown "$MYNAME.$MYGROUP" "$FILE"
}

add_type_1() {
	KEY="$1"	

	# Adds an SSL1 public key to $HOME/.ssh/authorized_keys
	# Is there a file to put it in already?
	if [ ! -f "$TYPE1STORE" ]
	then
		echo "  Couldn't find \"$TYPE1STORE\". Creating empty one."
		touch "$TYPE1STORE"
	fi

	# Get the key to add
	MYID=`echo "$KEY" | cut -d" " -f4`
	
	# Make sure the key is somewhat formatted properly
	if (echo "$KEY" | egrep -q "^[0-9]{3,4} [0-9]{2} [0-9]{100}")
	then	
	# Is it already in the key store?
	if ( cat "$TYPE1STORE" | cut -d" " -f4 | grep -q "$MYID" "$TYPE1STORE" )
	then
		echo "  \"$MYID\" already exists in \"$TYPE1STORE\""
	else
		echo "  Adding \"$MYID\" to \"$TYPE1STORE\"..."
		echo "$KEY" >> "$TYPE1STORE"
	fi
	else
		echo "  I don't recognize \"$KEY\" as a valid SSH1 public key"
	fi

	# SSH will refuse to use world/group-readable authorized_hosts* files. Novice users can take a
	# *long* time to track down why SSH is still requiring a password. Let's fix it now, just in case.
	set_perms "$TYPE1STORE"
}

add_type_2() {
	KEY="$1"

	# Is there a file to put it in already?
	if [ ! -f "$TYPE2STORE" ]
	then
		echo "  Couldn't find \"$TYPE2STORE\". Creating empty one."
		touch "$TYPE2STORE"
	fi

	# Get the key to add
	MYID=`echo "$KEY" | cut -d" " -f3`
	# Is this DSA or RSA?
	MYTYPE=`echo "$KEY" | cut -d" " -f1`
	case "$MYTYPE" in
		ssh-rsa | ssh-dss)
			if ( cat "$TYPE2STORE" | cut -d" " -f4 | grep -q "^$MYTYPE .* $MYID" "$TYPE2STORE" )
			then
				echo "  \"$MYID\" already exists in \"$TYPE2STORE\""
			else
				echo "  Adding $MYTYPE key for \"$MYID\" to \"$TYPE2STORE\"..."
				echo "$KEY" >> "$TYPE2STORE"
			fi
			;;
		*)
			echo "  I don't recognize \"$KEY\" as a valid SSH2 public key"
			;;
	esac

	# SSH will refuse to use world/group-readable authorized_hosts* files. Novice users can take a
	# *long* time to track down why SSH is still requiring a password. Let's fix it now, just in case.
	set_perms "$TYPE2STORE"
}
ENDOFSCRIPT
}

###################################
###################################
###################################
# REMOTE SCRIPT END
###################################
###################################
###################################

###############
# Beginning of local script
###############

echo ""

# We need a host name
if [ "$#" -gt 0 ]
then
	REMOTE_HOSTS="$1"
	shift
	while [ "$#" -gt 0 ]
	do
		REMOTE_HOSTS="$REMOTE_HOSTS $1"
		shift
	done
fi

if [ -z "$REMOTE_HOSTS" ]
then
	echo "Usage: $0 [remoteuser@]remotehost ..."
	exit 1
fi

# Check to see if the user even has any public keys to migrate
if [ ! -f "$SSH1PUBKEY" -a ! -f "$SSH2RSAPUBKEY" -a ! -f "$SSH2DSAPUBKEY" ]
then
	echo "It doesn't appear that you have any public keys to migrate."
	echo "You need one of the following files:"
	echo "  $SSH1PUBKEY"
	echo "  $SSH2RSAPUBKEY"
	echo "  $SSH2DSAPUBKEY"
	echo ""
	echo "Perhaps you need to run \"ssh-keygen\" first."
	exit 1
fi

# SSH1 key
if [ -f "$SSH1PUBKEY" ]
then
	echo "  SSH1 key     : Found"
	TYPE1KEY=`cat "$SSH1PUBKEY"` 
else
	echo "  SSH1 key     : NOT Found"
fi

# SSH2 DSA key
if [ -f "$SSH2DSAPUBKEY" ]
then
	echo "  SSH2-DSA key : Found"
	TYPE2DSA=`cat "$SSH2DSAPUBKEY"` 
else
	echo "  SSH2-DSA key : NOT Found (Create one with \"ssh-keygen -t dsa\""
fi

# SSH2 RSA key
if [ -f "$SSH2RSAPUBKEY" ]
then
	echo "  SSH2-RSA key : Found"
	TYPE2RSA=`cat "$SSH2RSAPUBKEY"` 
else
	echo "  SSH2-RSA key : NOT Found (Create one with \"ssh-keygen -t rsa\""
fi
echo ""

echo "SSHMigrate is now going to attempt to send your ssh keys to all"
echo "of the remote accounts that you have chosen. Ssh will probably"
echo "ask for your password on the remote host. If all goes as planned,"
echo "this will be the last time you'll be asked for a password when"
echo "connecting to them from here..."
echo ""

for REMOTEHOST in $REMOTE_HOSTS
do
	echo "Migrating to $REMOTEHOST..."
	# Copy our script and our public keys to the remote host
(
	send_remote_script
	[ -n "$TYPE1KEY" ] && echo "add_type_1 \"$TYPE1KEY\""
	[ -n "$TYPE2DSA" ] && echo "add_type_2 \"$TYPE2DSA\""
	[ -n "$TYPE2RSA" ] && echo "add_type_2 \"$TYPE2RSA\""
	
	# Do we also send the authorized_keys* files?
	if [ "$SEND_AUTHKEYS" -eq 1 ]
	then
		# SSH1 keys
		if [ -f "$SSH1AUTHKEYS" ]
		then
			cat "$SSH1AUTHKEYS" | while read line
			do
				echo "add_type_1 \"$line\""
			done
		fi
		# SSH2 keys
		if [ -f "$SSH2AUTHKEYS" ]
		then
			cat "$SSH2AUTHKEYS" | while read line
			do
				echo "add_type_2 \"$line\""
			done
		fi
	fi

) | ssh -T "$REMOTEHOST"

	# Are we supposed to run this script on the remote site, or merely copy it?
	if [ "$REFLECT" -eq 1 -o "$SEND_SCRIPT" -eq 1 ]
	then
		echo "  Copying this script to the remote host"
		scp "$0" $REMOTEHOST
		echo "chmod +x \"$SCRIPTNAME\"" | ssh -T "$REMOTEHOST"

		# Are we supposed to run it there, too?
		if [ "$REFLECT" -eq 1 ]
		then
			# This isn't implemented, yet, because we need to prevent infinite recursion
			# echo "\"./$SCRIPTNAME\" \"$WHOAMI@$WHEREAMI\"" | ssh -T "$REMOTEHOST"
			echo "  REFLECT isn't implemented, yet"
		fi
	fi

	echo "Done with $REMOTEHOST"
	echo ""
done
echo "SSHMigrate has finished"
echo ""

The End - joe@emenaker.com