diff --git a/install/phases/boss/mfs b/install/phases/boss/mfs index 6be7fba5805b9d9b757dfca3a00079131f66857d..1f322b53da3845a1e766750bb077d8f2150ee6d2 100755 --- a/install/phases/boss/mfs +++ b/install/phases/boss/mfs @@ -7,9 +7,9 @@ use installvars; my $MFSTARBALL = "tftpboot-elabinelab.tar.gz"; my $MFSURL = "http://www.emulab.net/downloads/$MFSTARBALL"; -my $MFSVERSION = "62"; my $MFSCONSOLE = $NODECONSOLE; my $MFSDESCRIPTORS = "$TOP_SRCDIR/install/descriptors-mfs.xml"; +my $LOCALIZE_MFS = "$PREFIX/sbin/localize_mfs"; my $ZONEINFO = "/usr/share/zoneinfo"; sub Install($$$) @@ -20,8 +20,14 @@ sub Install($$$) return 0 if ($isupdate); - if ($FBSD_MAJOR >= 7.2) { - $MFSVERSION = "72"; + # Configure can override setting it here. + if ($MFSVERSION eq "") { + if ($FBSD_MAJOR >= 7.2) { + $MFSVERSION = "72"; + } + else { + $MFSVERSION = "62"; + } } Phase "mfs", "Setting up MFSs", sub { @@ -74,16 +80,6 @@ sub Install($$$) }; } - # - # Copy the inner SSL cert and root's public ssh keys to the MFSes - # so that they will talk to the inner boss properly. - # At Berkeley the frisbee MFS copies these to disk images, - # needing changes to slicefix, which always needs to be run now. - # - MungeMfsRoot("frisbee"); - MungeMfsRoot("freebsd"); - MungeMfsRoot("freebsd.newnode"); - # # Fix the console device in the MFSes # @@ -102,19 +98,15 @@ sub Install($$$) }; # - # Finally, create the compressed versions of the MFS files + # Localize the MFSs # - Phase "prepare1", "Compressing the frisbee MFS", sub { - DoneIfExists("$TFTP_DIR/frisbee/boot/mfsroot.gz"); - ExecQuietFatal("cd $TFTP_DIR/frisbee/boot; ./prepare"); - }; - Phase "prepare2", "Compressing the freebsd MFS", sub { - DoneIfExists("$TFTP_DIR/freebsd/boot/mfsroot.gz"); - ExecQuietFatal("cd $TFTP_DIR/freebsd/boot; ./prepare"); - }; - Phase "prepare3", "Compressing the newnode MFS", sub { - DoneIfExists("$TFTP_DIR/freebsd.newnode/boot/mfsroot.gz"); - ExecQuietFatal("cd $TFTP_DIR/freebsd.newnode/boot; ./prepare"); + Phase "localize", "Localizing the MFSes", sub { + foreach my $mfs ("frisbee", "freebsd", "freebsd.newnode") { + Phase "$mfs", "Localizing $mfs", sub { + DoneIfExists("$TFTP_DIR/$mfs/boot/mfsroot.gz"); + ExecQuietFatal("$LOCALIZE_MFS $TFTP_DIR/$mfs"); + }; + } }; # @@ -132,83 +124,6 @@ sub Install($$$) return 0; } -sub MungeMfsRoot($) -{ - my $tftpdir = shift; - my $MFSROOT = "$TFTP_DIR/$tftpdir/boot/mfsroot"; - my $status = 0; - my @status = (); - - Phase "Munge", "Munging the $tftpdir root file system", sub { - PhaseSkip("already munged") - if (-e "${MFSROOT}.gz"); - - if ($FBSD_MAJOR >= 5) { - ExecQuietFatal("mdconfig -a -t vnode -f $MFSROOT -u 2"); - ExecQuietFatal("mount /dev/md2 /mnt"); - } - else { - ExecQuietFatal("vnconfig -c vn1 $MFSROOT"); - ExecQuietFatal("mount /dev/vn1 /mnt"); - } - - if (! -e "/mnt/root/.ssh" && - ExecQuiet("$MKDIR -m 700 /mnt/root/.ssh")) { - $status = 1; - goto done; - } - if ($ELABINELAB && - # Combine with outer boss keys. - ExecQuiet("$CAT $AUTHKEYS > /mnt/root/.ssh/authorized_keys2")) { - $status = 1; - goto done; - } - if (ExecQuiet("cat /root/.ssh/*.pub >> /mnt/root/.ssh/authorized_keys2") || - ExecQuiet("$CHMOD 600 /mnt/root/.ssh/authorized_keys2") || - ExecQuiet("cp -p $ETCDIR/emulab.pem $ETCDIR/client.pem ". - " /mnt/etc/emulab") || - ExecQuiet("cp -p $IMAGEKEYS_DIR/* /mnt/etc/ssh") || - ExecQuiet("cp -p $ZONEINFO/$OURTIMEZONE /mnt/etc/localtime") || - ExecQuiet("cp /dev/null /mnt/.localized")) { - $status = 1; - goto done; - } - - # - # XXX tmp hack. - # If console is VGA, create the magic file that tells slicefix - # to ensure that serial console is disabled in any FreeBSD image - # that is loaded (in case the machine has no serial port). - # We should have a more general way to set the console on a per - # node basis. - # - if ($MFSCONSOLE eq "vga" && - ExecQuiet("cp /dev/null /mnt/etc/emulab/isvgaonly")) { - $status = 1; - goto done; - } - - done: - # Save actual error till after unmounting the mfs. - if ($status) { - @output = libinstall::LastOutput(); - } - ExecQuietFatal("umount /mnt"); - if ($FBSD_MAJOR >= 5) { - ExecQuietFatal("mdconfig -d -u 2"); - } - else { - ExecQuietFatal("vnconfig -u vn1"); - } - if ($status) { - my $msg = join(' ', @output); - PhaseFail("Unable to execute: '$msg'"); - } - PhaseSucceed("Munged"); - }; - return 0; -} - # Local Variables: # mode:perl # End: diff --git a/install/updates/5/27 b/install/updates/5/27 new file mode 100644 index 0000000000000000000000000000000000000000..d26f33fa25c697afe45c2991894e36a2b740177a --- /dev/null +++ b/install/updates/5/27 @@ -0,0 +1,146 @@ +# +# Extract the current image hostkeys from the frisbee MFS. +# Also extract the current root password from the MFS and +# insert into the sitevar. +# +use strict; +use libinstall; +use installvars; +use libEmulab; + +my $MFSROOT = "$TFTP_DIR/frisbee/boot/mfsroot"; + +sub InstallUpdate($$) +{ + my ($version, $phase) = @_; + + if ($phase eq "pre") { + Phase "hostkeys", "Grabbing existing keys and root password", sub { + my $failed = 0; + my $done = 1; + my @output = (); + my $passhash; + + # + # If the root password sitevar is not set, we need to proceed. + # + if (!GetSiteVar("images/root_password", \$passhash)) { + PhaseFail("Could not get sitevar images/root_password"); + } + $done = 0 + if ($passhash eq ""); + + Phase "keydir", "Creating $IMAGEKEYS_DIR", sub { + DoneIfExists($IMAGEKEYS_DIR); + mkdir "$IMAGEKEYS_DIR",0755 or + PhaseFail("Unable to create $IMAGEKEYS_DIR: $!"); + }; + my %keytypes = ( + "rsa1" => "ssh_host_key", + "rsa" => "ssh_host_rsa_key", + "dsa" => "ssh_host_dsa_key"); + + foreach my $type (keys(%keytypes)) { + my $name = $keytypes{$type}; + $done = 0 + if (! -e "$IMAGEKEYS_DIR/$name"); + } + PhaseSkip("keys/password exist") + if ($done); + + Phase "point", "Creating mountpoint for MFS", sub { + DoneIfExists("/mfsmnt"); + mkdir "/mfsmnt", 0755 or + PhaseFail("Unable to create /mfsmnt: $!"); + }; + + + if (-e $MFSROOT) { + if ($FBSD_MAJOR >= 5) { + ExecQuietFatal("mdconfig -a -t vnode -f $MFSROOT -u 2"); + if (ExecQuiet("mount /dev/md2 /mfsmnt")) { + ExecQuiet("mdconfig -d -u 2"); + PhaseFail("mount error"); + } + } + else { + ExecQuietFatal("vnconfig -c vn1 $MFSROOT"); + if (ExecQuiet("mount /dev/vn1 /mfsmnt")) { + ExecQuiet("vnconfig -u vn1"); + PhaseFail("mount error"); + } + } + } + # Lets hope they are there. If not, we just make up new ones. + Phase "imagekeys", "Copying/Creating image host keys", sub { + foreach my $type (keys(%keytypes)) { + my $name = $keytypes{$type}; + + Phase $type, "$type host key", sub { + DoneIfExists("$IMAGEKEYS_DIR/$name"); + + if (-e "/mfsmnt/etc/ssh/$name") { + $failed = ExecQuiet("$CP -p /mfsmnt/etc/ssh/$name ". + " /mfsmnt/etc/ssh/$name.pub ". + " $IMAGEKEYS_DIR"); + } + else { + $failed = ExecQuiet("$SSH_KEYGEN -t $type -N '' ". + " -f $IMAGEKEYS_DIR/$name"); + } + }; + # Stop if we have any problems. + if ($failed) { + @output = LastOutput(); + last; + } + } + }; + if ($passhash eq "") { + my $master = "/etc/master.passwd"; + $master = "/mfsmnt/$master" if (-e $MFSROOT); + + my ($status,@lines) = ExecQuiet("grep root $master"); + if ($status) { + $failed = 1; + @output = LastOutput(); + } + else { + $passhash = $lines[0]; + if ($passhash =~ /root:([^:]*)/) { + $passhash = $1; + } + else { + $failed = 1; + @output = ("Could not parse root line"); + } + if (!SetSiteVar("images/root_password", $passhash)) { + $failed = 1; + @output = ("SetSiteVar failed"); + } + } + } + + if (-e $MFSROOT) { + ExecQuietFatal("umount /mfsmnt"); + if ($FBSD_MAJOR >= 5) { + ExecQuietFatal("mdconfig -d -u 2"); + } + else { + ExecQuietFatal("vnconfig -u vn1"); + } + } + if ($failed) { + my $msg = join(' ', @output); + PhaseFail("Unable to execute: '$msg'"); + } + PhaseSucceed("got all keys"); + }; + } + return 0; +} +1; + +# Local Variables: +# mode:perl +# End: diff --git a/utils/GNUmakefile.in b/utils/GNUmakefile.in index 55393d95e2161a91c34ab60462a92da246703cc3..3bed4be7b7ac3b0b1b883239154a823f2fc67c8f 100644 --- a/utils/GNUmakefile.in +++ b/utils/GNUmakefile.in @@ -27,7 +27,7 @@ SBIN_SCRIPTS = vlandiff vlansync withadminprivs export_tables cvsupd.pl \ anonsendmail epmodeset fixexpinfo node_traffic \ dumpdescriptor subboss_tftpboot_sync testbed-control \ archive-expinfo grantfeature emulabfeature addblob readblob \ - prereserve grantimage getimages + prereserve grantimage getimages localize_mfs WEB_SBIN_SCRIPTS= webnewnode webdeletenode webspewconlog webarchive_list \ webwanodecheckin webspewimage diff --git a/utils/localize_mfs.in b/utils/localize_mfs.in new file mode 100644 index 0000000000000000000000000000000000000000..0290131624076d775960bc7ef6f5c4a69936fac0 --- /dev/null +++ b/utils/localize_mfs.in @@ -0,0 +1,413 @@ +#!/usr/bin/perl -w +# +# EMULAB-COPYRIGHT +# Copyright (c) 2010-2012 University of Utah and the Flux Group. +# All rights reserved. +# +use English; +use strict; +use Getopt::Std; +use Data::Dumper; + +# +# Localize an MFS (FreeBSD or Linux variants). +# +sub usage() +{ + print("Usage: localize_mfs [-d] \n"); + exit(-1); +} +my $optlist = "d"; +my $debug = 0; + +# +# Configure variables +# +my $TB = "@prefix@"; +my $ETCDIR = "$TB/etc"; +my $ELABINELAB = @ELABINELAB@; +my $MFSCONSOLE = "@NODECONSOLE@"; +my $OURTIMEZONE = "@OURTIMEZONE@"; + +# Need these below. +my $FBSD_MAJOR; +my $FBSD_MINOR; + +# +# Untaint the path +# +$ENV{'PATH'} = "$TB/bin:$TB/sbin:/bin:/usr/bin:/usr/bin:/usr/sbin"; +delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; + +# +# Turn off line buffering on output +# +$| = 1; + +# +# Load the Testbed support stuff. +# +use lib "@prefix@/lib"; +use emdb; +use libEmulab; + +my $CHGRP = "/usr/bin/chgrp"; +my $CHMOD = "/bin/chmod"; +my $MKDIR = "/bin/mkdir"; +my $CHOWN = "/usr/sbin/chown"; +my $CAT = "/bin/cat"; +my $CP = "/bin/cp"; +my $MV = "/bin/mv"; +my $RM = "/bin/rm"; +my $PW = "/usr/sbin/pw"; +my $CPIO = "/usr/bin/cpio"; +my $SED = "/usr/bin/sed"; +my $MOUNT = "/sbin/mount"; +my $UMOUNT = "/sbin/umount"; +my $MDCONFIG = "/sbin/mdconfig"; +my $AUTHKEYS = "/root/.ssh/authorized_keys"; +my $ZONEINFO = "/usr/share/zoneinfo"; +my $IMAGEKEYS_DIR = "$ETCDIR/image_hostkeys"; + +# Protos +sub fatal($); +sub LocalizeBSD(); +sub LocalizeLinux($); +sub ExecQuiet($); + +# +# Parse command arguments. Once we return from getopts, all that should be +# left are the required arguments. +# +my %options = (); +if (! getopts($optlist, \%options)) { + usage(); +} +if (defined($options{"d"})) { + $debug = 1; +} +usage() + if (@ARGV != 1); +my $path = $ARGV[0]; + +fatal("$path does not exist") + if (! -e $path); + +if (`uname -r` =~ /^(\d+)\.(\d+)/) { + $FBSD_MAJOR = $1; + $FBSD_MINOR = $2; +} +else { + fatal("Could not determine what version of FreeBSD you are running!"); +} + +chdir($path) or + fatal("Could not chdir to $path"); + +# +# We must have a password hash in the DB. +# +my $passhash; + +if (!GetSiteVar("images/root_password", \$passhash)) { + fatal("Could not get sitevar images/root_password"); +} +if ($passhash eq "") { + fatal("The sitevar images/root_password is not set!"); +} + +# +# Figure out what kind of thing to localize. +# +if (-e "boot" && -e "boot/mfsroot") { + LocalizeBSD(); + exit(0); +} +foreach my $extension ("lzma", "bz2", "gz") { + if (-e "initramfs.${extension}") { + LocalizeLinux($extension); + exit(0); + } + if (-e "initramfs") { + LocalizeLinux(""); + exit(0); + } +} +# Oops, do not know what to do. +fatal("Do not know what to do with $path"); + +# +# Localize a FreeBSD MFS. +# +sub LocalizeBSD() +{ + my $configed = 0; + my $mounted = 0; + my $mpoint = "/mfsmount"; + my $mfsroot = "mfsroot"; + + if (-e "boot") { + chdir("boot") or + fatal("Could not chdir into boot directory"); + } + if (! -e $mpoint) { + system("$MKDIR $mpoint") == 0 + or fatal("Could not mkdir $mpoint"); + } + + my $Undo = sub () { + if ($mounted) { + ExecQuiet("$UMOUNT $mpoint") == 0 + or fatal("Could not unmount $mpoint: mfsroot is still active!"); + } + if ($configed) { + my $cmd = ($FBSD_MAJOR >= 5 ? + "$MDCONFIG -d -u 2" : "vnconfig -u vn1"); + + ExecQuiet($cmd) == 0 + or fatal("Could not unconfig: mfsroot is still active!"); + } + }; + + # + # Work on a copy. + # + ExecQuiet("$CP -pf $mfsroot ${mfsroot}.new") == 0 + or fatal("Could not make a copy of $mfsroot"); + + # + # Mount up the MFS. + # + my $cmd = ($FBSD_MAJOR >= 5 ? + "$MDCONFIG -a -t vnode -f ${mfsroot}.new -u 2" : + "vnconfig -c vn1 ${mfsroot}.new"); + ExecQuiet($cmd) == 0 + or goto bad; + $configed = 1; + + $cmd = ($FBSD_MAJOR >= 5 ? + "$MOUNT /dev/md2 $mpoint" : "$MOUNT /dev/vn1 $mpoint"); + ExecQuiet($cmd) == 0 + or goto bad; + $mounted = 1; + + # + # Okay, now we can localize + # + if (! -e "$mpoint/root/.ssh" && + ExecQuiet("$MKDIR -m 700 $mpoint/root/.ssh")) { + goto bad; + } + if ($ELABINELAB && + # Combine with outer boss root user ssh keys. + ExecQuiet("$CAT $AUTHKEYS > $mpoint/root/.ssh/authorized_keys2")) { + goto bad; + } + # And add the current boss root user ssh keys. + if (ExecQuiet("$CAT /root/.ssh/*.pub >> $mpoint/root/.ssh/authorized_keys2") || + ExecQuiet("$CHMOD 600 $mpoint/root/.ssh/authorized_keys2")) { + goto bad; + } + # Boss certificate. Need emulab.pem for TPM. + ExecQuiet("$CP -p $ETCDIR/emulab.pem $ETCDIR/client.pem $mpoint/etc/emulab") + == 0 or goto bad; + # All MFSs and images get the same ssh host keys. + ExecQuiet("$CP -p $IMAGEKEYS_DIR/* $mpoint/etc/ssh") + == 0 or goto bad; + # Copy boss timezone into the MFS. + ExecQuiet("$CP -p $ZONEINFO/$OURTIMEZONE $mpoint/etc/localtime") + == 0 or goto bad; + # Localize the root/toor password from the sitevar + ExecQuiet("echo '$passhash' | $PW -V $mpoint/etc usermod toor -h 0") + == 0 or goto bad; + ExecQuiet("echo '$passhash' | $PW -V $mpoint/etc usermod root -h 0") + == 0 or goto bad; + + # + # XXX tmp hack. + # If console is VGA, create the magic file that tells slicefix + # to ensure that serial console is disabled in any FreeBSD image + # that is loaded (in case the machine has no serial port). + # We should have a more general way to set the console on a per + # node basis. + # + if ($MFSCONSOLE eq "vga" && + ExecQuiet("$CP /dev/null $mpoint/etc/emulab/isvgaonly")) { + goto bad; + } + + # + # Mark as "localized". This tells slicefix that it should copy all + # the above stuff into the image. + # + ExecQuiet("$CP /dev/null $mpoint/.localized") + == 0 or goto bad; + + &$Undo(); + + # + # Now copy back and compress. + # + ExecQuiet("$MV -f $mfsroot ${mfsroot}.old") == 0 + or fatal("Could not save old $mfsroot"); + ExecQuiet("$MV -f ${mfsroot}.new ${mfsroot}") == 0 + or fatal("Could not rename new $mfsroot"); + ExecQuiet("./prepare") == 0 + or fatal("Could not prepare the MFS"); + + return 0; + + bad: + &$Undo(); + exit(1); +} + +# +# Localize a Lnux MFS +# +sub LocalizeLinux($) +{ + my ($extension) = @_; + my $compression; + my $mpoint = "extracted_initramfs"; + my $initfs = "initramfs"; + + if ($extension eq "lzma") { + $compression = "lzma"; + } + elsif ($extension eq "bz2") { + $compression = "bzip2"; + } + elsif ($extension eq "gz") { + $compression = "gzip"; + } + + ExecQuiet("$RM -rf $mpoint") == 0 + or fatal("Could not remove old $mpoint directory"); + ExecQuiet("$MKDIR $mpoint") == 0 + or fatal("Could not create $mpoint directory"); + chdir("$mpoint") or + fatal("Could not chdir into $mpoint directory"); + + # Extract + if (defined($compression)) { + ExecQuiet("$compression -dc | cpio -idu < ../${initfs}.${extension}") + == 0 or fatal("Could not extract ${initfs}.${extension}"); + } + else { + ExecQuiet("$CPIO -idu < ../${initfs}") == 0 + or fatal("Could not extract ${initfs}"); + } + + # + # Okay, now we can localize + # + if (! -e "$mpoint/root/.ssh" && + ExecQuiet("$MKDIR -m 700 $mpoint/root/.ssh")) { + goto bad; + } + if ($ELABINELAB && + # Combine with outer boss root user ssh keys. + ExecQuiet("$CAT $AUTHKEYS > $mpoint/root/.ssh/authorized_keys")) { + goto bad; + } + # And add the current boss root user ssh keys. + if (ExecQuiet("$CAT /root/.ssh/*.pub >> $mpoint/root/.ssh/authorized_keys") || + ExecQuiet("$CHMOD 600 $mpoint/root/.ssh/authorized_keys")) { + goto bad; + } + # Boss certificate. Need emulab.pem for TPM. + ExecQuiet("$CP -p $ETCDIR/emulab.pem $ETCDIR/client.pem $mpoint/etc/emulab") + == 0 or goto bad; + # All MFSs and images get the same ssh host keys. + ExecQuiet("$CP -p $IMAGEKEYS_DIR/* $mpoint/etc/ssh") + == 0 or goto bad; + # Copy boss timezone into the MFS. + ExecQuiet("$CP -p $ZONEINFO/$OURTIMEZONE $mpoint/etc/localtime") + == 0 or goto bad; + + # And the root/toor passwords. + ExecQuiet("$SED -i .orig -e 's,^root:\([^:]*\),root:$passhash,' ". + " -e 's,^toor:\([^:]*\),toor:$passhash,' ". + " $mpoint/etc/shadow") + == 0 or goto bad; + + # + # Mark as "localized". This tells slicefix that it should copy all + # the above stuff into the image. + # + ExecQuiet("$CP /dev/null $mpoint/.localized") + == 0 or goto bad; + + # + # Compress it back. + # + if (defined($compression)) { + ExecQuiet("find . | $CPIO -H newc -o | $compression -c9 ". + " > ../${initfs}.${extension}.new") + == 0 or fatal("Could not compress ${initfs}.${extension}.new"); + ExecQuiet("$MV -f ../${initfs}.${extension} ../${initfs}.${extension}.old") + == 0 or fatal("Could not back up ${initfs}.${extension}"); + ExecQuiet("$MV -f ../${initfs}.${extension}.new ../${initfs}.${extension}") + == 0 or fatal("Could not rename new ${initfs}.${extension}"); + } + else { + ExecQuiet("find . | $CPIO -H newc -o > ../${initfs}.new") == 0 + or fatal("Could not compress ${initfs}"); + ExecQuiet("$MV -f ../${initfs} ../${initfs}.old") + == 0 or fatal("Could not back up ${initfs}"); + ExecQuiet("$MV -f ../${initfs}.new ../${initfs}") + == 0 or fatal("Could not rename new ${initfs}"); + } + system("$RM -rf $mpoint") + if (!$debug); + + bad: + # Noting to undo, but leave the extracted stuff around for debugging. + exit(1); +} + +# +# Run a command, being sure to capture all output. +# +sub ExecQuiet($) +{ + # + # Use a pipe read, so that we save away the output + # + my ($command) = @_; + my $output = ""; + + if ($debug) { + print STDERR "exec('$command')\n"; + } + + if (!open(PIPE,"$command 2>&1 |")) { + print STDERR "Failed pipe('$command')\n"; + return -1; + } + while () { + $output .= $_; + } + close(PIPE); + my $exit_value = $? >> 8; + + if ($exit_value) { + print STDERR "Failed: exec('$command'):\n"; + } + if ($debug || $exit_value) { + print STDERR "$output\n"; + } + + return $exit_value; +} + +sub fatal($) +{ + my ($mesg) = @_; + + print STDERR "*** $0:\n". + " $mesg\n"; + exit(-1); +} +