Commit e894ec36 authored by Leigh B Stoller's avatar Leigh B Stoller

Add a new localize_mfs script (based on stuff that was in the mfs

install script, but I pulled out to create an independent script).
This works on both freebsd and linux based MFSs. The intent is to do
all of the localization automcatically for site admins, so that they
can import new MFSs more easily. This is also used from the new
install code to bring in the initial MFSs and localize them.

Here is what we localize:

* The timezone is copied from boss:/etc/localtime to mfs:/etc. Ryan
  says the upcoming version of the linux MFS will actually use
  localtime. 

* Copy boss:/usr/testbed/etc/{emulab.pem,client.pem} to mfs:/etc/emulab. 
  The former is for TPM, the later for the ssl version of tmcc.

* Copy out boss root ssh keys (pub) to mfs:/root/.ssh/authorized_keys.
  In an ElabInElab we take care to combine with outer boss keys.

* Copy out the image ssh host keys. These are the keys that we put on
  every image to avoid the ssh host key change sillyness. See notes
  below on how these keys are initialized on an existing emulab. The
  keys are copied from boss:/usr/testbed/etc/image_hostkeys to
  mfs:/etc/ssh directory.
  
* Initialize the root and toor passwords from a new sitevar named
  images/root_password (which is the encryption hash, not plain
  text). See notes below on how this sitevar is initialized on an
  existing emulab.

About initializing the host keys and the root password hash ... I
added a new update script (27) that will go out to the current frisbee
MFS and mount it, grab the current keys and password hash, and put
them into place on boss. At the moment I only look for a FreeBSD
frisbee MFS, since not too many people are running the linux mfs, and
this was hard enough as it is!

For a new installation, a new install phase script will build the them
and install into /usr/testbed/etc/image_hostkeys. I have not dealt
with the password yet.
parent 6ceb538a
......@@ -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:
#
# 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:
......@@ -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
......
#!/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] <path>\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