Commit 9bbccab5 authored by Mike Hibler's avatar Mike Hibler

More changes to get our root pubkey ducks in a row.

See emulab/emulab-devel issue #303. Ensure we have a controlled set of
pubkeys in root's .ssh/authorized_keys file when we create and load new
images. But allow for a user added key to survive node reboots if they
customize it within an experiment.
parent a1925dda
#!/usr/bin/perl -w #!/usr/bin/perl -w
# #
# Copyright (c) 2004-2012 University of Utah and the Flux Group. # Copyright (c) 2004-2017 University of Utah and the Flux Group.
# #
# {{{EMULAB-LICENSE # {{{EMULAB-LICENSE
# #
...@@ -134,13 +134,13 @@ sub doboot() ...@@ -134,13 +134,13 @@ sub doboot()
} }
# #
# Write new pubkeys to root authkeys file. As a safety mechanism, back # Ensure that the given keys are in root's authorized_keys file,
# up old authkeys to authkeys2 file, which is also used by openssh sshd. # put them there if not.
# #
if (@pubkeys) { if (@pubkeys) {
my $authdir = (WINDOWS() ? "/sshkeys/root" : "/root/.ssh"); my $authdir = (WINDOWS() ? "/sshkeys/root" : "/root/.ssh");
my $authkeys = $authdir . "/authorized_keys"; my $authkeys = $authdir . "/authorized_keys";
my $authkeys2 = $authkeys . "2"; my $authkeysold = $authkeys . ".old";
my $authkeysnew = $authkeys . ".new"; my $authkeysnew = $authkeys . ".new";
my $oldumask = umask(022); my $oldumask = umask(022);
...@@ -159,29 +159,75 @@ sub doboot() ...@@ -159,29 +159,75 @@ sub doboot()
or fatal("Failed to chmod $authdir"); or fatal("Failed to chmod $authdir");
} }
if (-e "$authkeys") { if (-e $authkeys && ! -e $authkeysold) {
system("cp -pf $authkeys $authkeys2") == 0 system("cp -pf $authkeys $authkeysold") == 0
or fatal("Could not backup root ssh authorized_keys file"); or fatal("Could not backup root ssh authorized_keys file");
} }
} }
if (!open(AUTHKEYS, "> $authkeysnew")) { #
# Hash for tracking our keys
#
my %keyhash = ();
foreach my $key (@pubkeys) {
$keyhash{$key} = 0;
}
#
# Read the old keys, taking note of our keys that are already there.
# It is okay for there to be no existing file, we will just make a
# new one with Emulab keys.
#
my @lines = ();
if (open(OKEYS, "<$authkeys")) {
while (my $key = <OKEYS>) {
chomp $key;
if ($key && $key !~ /^\s*#/ && exists($keyhash{$key})) {
$keyhash{$key} = 1;
}
push @lines, $key;
}
close(OKEYS);
}
#
# Add any of our keys that are not already there
#
my $added = 0;
foreach my $key (@pubkeys) {
if (!$keyhash{$key}) {
push @lines, $key;
$added++;
}
}
#
# All the keys are there, nothing to do
#
if (!$added) {
umask($oldumask);
return 0;
}
#
# Otherwise write a new file with missing keys added
#
if (!open(NKEYS, ">$authkeysnew")) {
warning("Could not open $authkeysnew: $!"); warning("Could not open $authkeysnew: $!");
umask($oldumask); umask($oldumask);
return -1; return -1;
} }
umask($oldumask); umask($oldumask);
print AUTHKEYS "#\n";
print AUTHKEYS "# DO NOT EDIT! This file auto generated at bootup.\n";
print AUTHKEYS "#\n";
foreach my $key (@pubkeys) { print NKEYS "# Updated by Emulab on " . scalar(localtime()) . "\n";
print AUTHKEYS "$key\n"; foreach my $line (@lines) {
next
if ($line =~ /^# Updated by Emulab on/);
print NKEYS "$line\n";
} }
close(AUTHKEYS); close(NKEYS);
if (system("cmp -s $authkeysnew $authkeys") && if (system("mv -f $authkeysnew $authkeys")) {
system("mv -f $authkeysnew $authkeys")) {
warning("Could not mv $authkeysnew to $authkeys"); warning("Could not mv $authkeysnew to $authkeys");
} }
return 0; return 0;
......
...@@ -99,26 +99,17 @@ localize_image() { ...@@ -99,26 +99,17 @@ localize_image() {
# Check the host keys. # Check the host keys.
changehostkeys=0 changehostkeys=0
if [ -e /etc/ssh/ssh_host_key ]; then for k in "" dsa_ ecdsa_ ed25519_ rsa_; do
cmp -s /etc/ssh/ssh_host_key $MNT/etc/ssh/ssh_host_key if [ -e /etc/ssh/ssh_host_${k}key ]; then
if [ $? -ne 0 ]; then cmp -s /etc/ssh/ssh_host_${k}key $MNT/etc/ssh/ssh_host_${k}key
changehostkeys=1 if [ $? -ne 0 ]; then
fi changehostkeys=1
fi fi
if [ -e /etc/ssh/ssh_host_rsa_key ]; then
cmp -s /etc/ssh/ssh_host_rsa_key $MNT/etc/ssh/ssh_host_rsa_key
if [ $? -ne 0 ]; then
changehostkeys=1
fi
fi
if [ -e /etc/ssh/ssh_host_dsa_key ]; then
cmp -s /etc/ssh/ssh_host_dsa_key $MNT/etc/ssh/ssh_host_dsa_key
if [ $? -ne 0 ]; then
changehostkeys=1
fi fi
fi done
if [ $changehostkeys -eq 1 ]; then if [ $changehostkeys -eq 1 ]; then
echo " updating /etc/ssh/hostkeys" echo " updating /etc/ssh host keys"
if [ ! -d $MNT/etc/ssh ]; then if [ ! -d $MNT/etc/ssh ]; then
mkdir -m 755 $MNT/etc/ssh || { mkdir -m 755 $MNT/etc/ssh || {
...@@ -127,7 +118,7 @@ localize_image() { ...@@ -127,7 +118,7 @@ localize_image() {
} }
fi fi
cp -pf /etc/ssh/ssh_host_* $MNT/etc/ssh/ || { cp -pf /etc/ssh/ssh_host_* $MNT/etc/ssh/ || {
echo "Failed to create /etc/ssh/hostkeys" echo "Failed to update /etc/ssh host keys"
return 1 return 1
} }
fi fi
......
...@@ -49,6 +49,7 @@ my $ENTROPY = "/var/db/entropy/*"; ...@@ -49,6 +49,7 @@ my $ENTROPY = "/var/db/entropy/*";
my $LOADERCONF = "/boot/loader.conf"; my $LOADERCONF = "/boot/loader.conf";
my $PUBSUBCONF = "/usr/local/etc/pubsubd.conf"; my $PUBSUBCONF = "/usr/local/etc/pubsubd.conf";
my $PUBSUBEXPR = "/usr/local/etc/pubsubd.expr"; my $PUBSUBEXPR = "/usr/local/etc/pubsubd.expr";
my $ROOTSSHDIR = "/root/.ssh";
# #
# #
...@@ -245,6 +246,21 @@ if (-f $HISTORY) { ...@@ -245,6 +246,21 @@ if (-f $HISTORY) {
system("rm -f /root/.saves-*"); system("rm -f /root/.saves-*");
} }
#
# Remove /root/.ssh and then regenerate a clean version with only an
# approved authorized_keys file.
#
print "Cleaning root's .ssh directory ...\n";
if (system("rm -rf $ROOTSSHDIR.bak") ||
system("mv $ROOTSSHDIR $ROOTSSHDIR.bak")) {
die("Could not move $ROOTSSHDIR to $ROOTSSHDIR.bak");
}
if (! -x "$BINDIR/rc/rc.localize" || system("$BINDIR/rc/rc.localize boot")) {
system("rm -rf $ROOTSSHDIR");
system("mv $ROOTSSHDIR.bak $ROOTSSHDIR");
die("Could not clean root .ssh directory");
}
print "Cleaning mail spool files ...\n"; print "Cleaning mail spool files ...\n";
system("rm -rf $MAILDIR/*"); system("rm -rf $MAILDIR/*");
......
...@@ -254,25 +254,166 @@ getloadervar() { ...@@ -254,25 +254,166 @@ getloadervar() {
echo $_val echo $_val
} }
#
# Make sure /root/.ssh contains only an authorized_keys file with the boot
# root pubkey.
#
# Called with arg=1 if you just want to see if anything is wrong (returns
# non-zero if so), 0 to fix.
#
dofixauthkeys() {
_test=$1
if [ $_test -ne 0 ]; then
if [ ! -d /mnt/root/.ssh ]; then
return 1
fi
if [ -x /usr/bin/stat ]; then
_stat=`/usr/bin/stat -f '%u,%g,%p' /mnt/root/.ssh`
if [ "$_stat" != "0,0,40700" ]; then
return 1
fi
fi
if [ ! -e /mnt/root/.ssh/authorized_keys ]; then
return 1
fi
if [ -e /mnt/root/.ssh/authorized_keys2 ]; then
return 1
fi
fi
#
# If we are a localized MFS, we just need to use the authorized_keys2
# file from the MFS. Otherwise we get the key(s) from tmcd and put
# them into the MFS authorized_keys2 file.
#
if ! islocalized; then
rm -f /root/.ssh/authorized_keys2
_key=`$BINDIR/tmcc localization | grep 'ROOTPUBKEY=' | head -1 | \
sed -e "s/^ROOTPUBKEY='//" | sed -e "s/'$//"`
if [ $? -ne 0 -o -z "$_key" ]; then
echo "WARNING: no boss pubkey returned!"
else
echo "$_key" > /root/.ssh/authorized_keys2
fi
fi
if [ $_test -ne 0 ]; then
cmp -s /root/.ssh/authorized_keys2 /mnt/root/.ssh/authorized_keys
if [ $? -ne 0 ]; then
return 1
fi
else
echo " updating /root/.ssh"
# make sure /root/.ssh exists and has proper permissions
mkdir -p /mnt/root/.ssh
chown root:0 /mnt/root/.ssh
chmod 700 /mnt/root/.ssh
rm -f /mnt/root/.ssh/authorized_keys2
#
# XXX no proper pubkey, just leave the current file intact.
# XXX maybe we should just nuke it instead?
#
if [ ! -r /root/.ssh/authorized_keys2 ]; then
return 0
fi
# create authkeys file with just root key(s)
rm -f /mnt/root/.ssh/authorized_keys
cp /root/.ssh/authorized_keys2 /mnt/root/.ssh/authorized_keys
chmod 644 /mnt/root/.ssh/authorized_keys
fi
return 0
}
# #
# Make sshd more secure by default: no password based login. # Make sshd more secure by default: no password based login.
# XXX argh! First use wins, so we have to comment out before adding ours! # We will fix if there are multiple settings of the same variable,
# if it is set incorrectly, or it is not set at all.
#
# Called with arg=1 if you just want to see if anything is wrong (returns
# non-zero if so), 0 to fix.
# #
dofixsshd() { dofixsshd() {
echo " updating /etc/ssh/sshd_config" _test=$1
sed -i .preemulab \
-e 's;^Protocol;#Protocol;' \ if [ $_test -ne 0 ]; then
-e 's;^PasswordAuth;#PasswordAuth;' \ # sshd_config doesn't exist, call it okay
-e 's;^ChallengeResp;#ChallengeResp;' \ if [ ! -f /mnt/etc/ssh/sshd_config ]; then
-e 's;^PermitRootLogin;#PermitRootLogin;' /mnt/etc/ssh/sshd_config echo "WARNING: no sshd_config found!"
cat <<EOF8 >>/mnt/etc/ssh/sshd_config return 0
fi
# find all uncommented instances of variables we care about
OIFS="$IFS"
IFS='
'
_fix=0
_valP=
_valPA=
_valCRA=
_valPRL=
for _opt in `grep -E '^(Protocol|PasswordAuthentication|ChallengeResponseAuthentication|PermitRootLogin) ' /mnt/etc/ssh/sshd_config`; do
_k=${_opt%% *}
_v=${_opt#* }
case $_k in
Protocol)
if [ -n "$_valP" -o "$_v" != "2" ]; then
_fix=1
fi
_valP=$_v
;;
PasswordAuthentication)
if [ -n "$_valPA" -o "$_v" != "no" ]; then
_fix=1
fi
_valPA=$_v
;;
ChallengeResponseAuthentication)
if [ -n "$_valCRA" -o "$_v" != "no" ]; then
_fix=1
fi
_valCRA=$_v
;;
PermitRootLogin)
if [ -n "$_valPRL" -o "$_v" != "without-password" ]; then
_fix=1
fi
_valPRL=$_v
;;
esac
done
IFS=$OIFS
# a var had wrong value or more than one setting, fix
if [ $_fix -ne 0 ]; then
return 1
fi
# a var was not explicitly set, fix
if [ -z "$_valP" -o -z "$_valPA" -o -z "$_valCRA" -o -z "$_valPRL" ]; then
return 1
fi
else
echo " updating /etc/ssh/sshd_config"
sed -i .preemulab \
-e '/^Protocol /d' \
-e '/^PasswordAuthentication /d' \
-e '/^ChallengeResponseAuthentication /d' \
-e '/^PermitRootLogin /d' \
-e '/^# Emulab/d' /mnt/etc/ssh/sshd_config
cat <<EOF8 >>/mnt/etc/ssh/sshd_config
# Emulab config # Emulab config
Protocol 2 Protocol 2
PasswordAuthentication no PasswordAuthentication no
ChallengeResponseAuthentication no ChallengeResponseAuthentication no
PermitRootLogin without-password PermitRootLogin without-password
EOF8 EOF8
fi
return 0
} }
dofreebsd() { dofreebsd() {
...@@ -348,6 +489,7 @@ dofreebsd() { ...@@ -348,6 +489,7 @@ dofreebsd() {
changezone=0 changezone=0
changentp=0 changentp=0
changesshd=0 changesshd=0
changeauth=0
fixit=0 fixit=0
...@@ -511,37 +653,17 @@ dofreebsd() { ...@@ -511,37 +653,17 @@ dofreebsd() {
fi fi
fi fi
# Check the root keys. # Root keys are handled by dofixauthkeys() below
if [ -e /root/.ssh/authorized_keys2 ]; then
cmp -s /root/.ssh/authorized_keys2 /mnt/root/.ssh/authorized_keys
if [ $? -ne 0 ]; then
changerootkeys=1
fixit=1
fi
fi
# Check the host keys. # Check the host keys.
if [ -e /etc/ssh/ssh_host_key ]; then for k in "" dsa_ ecdsa_ ed25519_ rsa_; do
cmp -s /etc/ssh/ssh_host_key /mnt/etc/ssh/ssh_host_key if [ -e /etc/ssh/ssh_host_${k}key ]; then
if [ $? -ne 0 ]; then cmp -s /etc/ssh/ssh_host_${k}key /mnt/etc/ssh/ssh_host_${k}key
changehostkeys=1 if [ $? -ne 0 ]; then
fixit=1 changehostkeys=1
fi fi
fi
if [ -e /etc/ssh/ssh_host_rsa_key ]; then
cmp -s /etc/ssh/ssh_host_rsa_key /mnt/etc/ssh/ssh_host_rsa_key
if [ $? -ne 0 ]; then
changehostkeys=1
fixit=1
fi
fi
if [ -e /etc/ssh/ssh_host_dsa_key ]; then
cmp -s /etc/ssh/ssh_host_dsa_key /mnt/etc/ssh/ssh_host_dsa_key
if [ $? -ne 0 ]; then
changehostkeys=1
fixit=1
fi fi
fi done
# Check the time zone. # Check the time zone.
if [ -e /etc/localtime ]; then if [ -e /etc/localtime ]; then
...@@ -563,12 +685,18 @@ dofreebsd() { ...@@ -563,12 +685,18 @@ dofreebsd() {
fi fi
if [ -r /mnt/etc/ssh/sshd_config ] && \ # See if we need to update sshd_config
! grep -q '^# Emulab config' /mnt/etc/ssh/sshd_config; then if ! dofixsshd 1; then
changesshd=1 changesshd=1
fixit=1 fixit=1
fi fi
# See if we need to update root's .ssh directory
if ! dofixauthkeys 1; then
changeauth=1
fixit=1
fi
if [ $fixit -eq 0 ]; then if [ $fixit -eq 0 ]; then
echo " no changes necessary" echo " no changes necessary"
umount $rootdev umount $rootdev
...@@ -796,29 +924,7 @@ EOF1 ...@@ -796,29 +924,7 @@ EOF1
} }
fi fi
# Copy in new root keys # Copy in new root keys handled by dofixauthkeys() below
if [ $changerootkeys -eq 1 ]; then
echo " updating /root/.ssh/authorized_keys"
if [ ! -d /mnt/root/.ssh ]; then
mkdir -m 700 /mnt/root/.ssh || {
echo "Failed to mkdir /root/.ssh"
umount $rootdev
return 1
}
fi
# copy to both authorized_keys and _keys2
cp -p /root/.ssh/authorized_keys2 /mnt/root/.ssh/authorized_keys || {
echo "Failed to create /root/.ssh/authorized_keys"
umount $rootdev
return 1
}
cp -p /root/.ssh/authorized_keys2 /mnt/root/.ssh/ || {
echo "Failed to create /root/.ssh/authorized_keys2"
umount $rootdev
return 1
}
fi
# Copy in new host keys # Copy in new host keys
if [ $changehostkeys -eq 1 ]; then if [ $changehostkeys -eq 1 ]; then
...@@ -861,7 +967,11 @@ EOF1 ...@@ -861,7 +967,11 @@ EOF1
fi fi
if [ $changesshd -eq 1 ]; then if [ $changesshd -eq 1 ]; then
dofixsshd dofixsshd 0
fi
if [ $changeauth -eq 1 ]; then
dofixauthkeys 0
fi fi
# actually run any postconfig scripts if we're supposed to: # actually run any postconfig scripts if we're supposed to:
...@@ -1425,9 +1535,15 @@ EOF7 ...@@ -1425,9 +1535,15 @@ EOF7
# #
# Fixup sshd config # Fixup sshd config
# #
if [ -r /mnt/etc/ssh/sshd_config ] && \ if ! dofixsshd 1; then
! grep -q '^# Emulab config' /mnt/etc/ssh/sshd_config; then dofixsshd 0
dofixsshd fi
#
# Fixup root authorized keys
#
if ! dofixauthkeys 1; then
dofixauthkeys 0
fi fi
# #
......
...@@ -53,6 +53,7 @@ my $ANACRON = "/usr/sbin/anacron"; ...@@ -53,6 +53,7 @@ my $ANACRON = "/usr/sbin/anacron";
my $GRUBDEF = "/etc/default/grub"; my $GRUBDEF = "/etc/default/grub";
my $PUBSUBCONF = "/usr/local/etc/pubsubd.conf"; my $PUBSUBCONF = "/usr/local/etc/pubsubd.conf";
my $PUBSUBEXPR = "/usr/local/etc/pubsubd.expr"; my $PUBSUBEXPR = "/usr/local/etc/pubsubd.expr";
my $ROOTSSHDIR = "/root/.ssh";
# #
# Dead wood in $BINDIR # Dead wood in $BINDIR
...@@ -351,6 +352,21 @@ if (-f "/root/$HISTORY") { ...@@ -351,6 +352,21 @@ if (-f "/root/$HISTORY") {
die("Could not unlink /root/$HISTORY: $!"); die("Could not unlink /root/$HISTORY: $!");
} }
#
# Remove /root/.ssh and then regenerate a clean version with only an
# approved authorized_keys file.
#
print "Cleaning root's .ssh directory ...\n";
if (system("rm -rf $ROOTSSHDIR.bak") ||
system("mv $ROOTSSHDIR $ROOTSSHDIR.bak")) {
die("Could not move $ROOTSSHDIR to $ROOTSSHDIR.bak");
}
if (! -x "$BINDIR/rc/rc.localize" || system("$BINDIR/rc/rc.localize boot")) {
system("rm -rf $ROOTSSHDIR");
system("mv $ROOTSSHDIR.bak $ROOTSSHDIR");
die("Could not clean root .ssh directory");
}
print "Cleaning mail spool files ...\n"; print "Cleaning mail spool files ...\n";
system("rm -rf $MAILDIR/*"); system("rm -rf $MAILDIR/*");
......
...@@ -269,19 +269,166 @@ getloadervar() { ...@@ -269,19 +269,166 @@ getloadervar() {
echo $_val echo $_val