Commit dc10d326 authored by David Johnson's avatar David Johnson

Add a new client side script, osconfig, that can update an MFS or a

frisbee-loaded slice based on a tarball downloaded from boss.  For now,
the tarball is dynamically created by boss based on params sent to the
osconfig_dump.php script; it is populated with files and a MANIFEST based
on the files and constraints in the osconfig_* tables, which are pretty
self-explanatory.  Transport is not secure, nor intended to be -- nodes on
the control net or widearea nodes auth'd with a privkey can grab stuff
destined to them based on their IP addr.  For the MFS case, the tarball is
unpacked and the MANIFEST entries are executed/copied/extracted, and
(nearly all of) the client side is re-run.  For the slicefix case, we just
execute/copy/extract the MANIFEST entries in the mounted slice... there
are some useful env vars set for scripts to use.

If this mechanism ever becomes generally useful, or we're pushing big update
tarballs, we'll have to add a caching mechanism (doh).  Right now, it's just
for dongle-booted nodes or widearea nodes on which we cannot update the
physical boot media without much pain; as well as for making major whacks
to frisbee-loaded slices, which we need for the widearea case.

Also, call this from rc.cdboot (to update a "read-only" (real media is
mounted ro, but other parts of the fs are rw via unionfs or mfs) MFS),
and from slicefix.

NOTE: the client side osconfig script does not get installed from the
makefile; this is intentional.  This script should not be placed in our
local tftp'd MFSes, at least until there's some need for it!
parent 3ac6d7af
This diff is collapsed.
......@@ -26,6 +26,8 @@ my $optlist = "";
my $action = "boot";
my $debug = 1;
my $cmdline = "$0 " . join(" ",@ARGV);
# Turn off line buffering on output
$| = 1;
......@@ -222,6 +224,15 @@ sub doboot()
# This is allowed to fail; ipod might not be supported.
}
if (-x "$BINDIR/osconfig" &&
(!defined($ENV{"ELAB_UPD_DONE"}) || !$ENV{"ELAB_UPD_DONE"})) {
if (!system("$BINDIR/osconfig premfs")) {
# success -- must set our marker and exec this script
$ENV{"ELAB_UPD_DONE"} = 1;
exec($cmdline);
}
}
# Will not return until it gets something it likes.
$bootdev = WhichRawDisk();
print("Using $bootdev for config sector ...\n");
......
......@@ -226,6 +226,16 @@ dofreebsd() {
esac
fi
# check to see if we need to download any postconfig scripts:
doosconfig=0
if [ -x $BINDIR/osconfig ]; then
$BINDIR/osconfig -c -m /mnt -D $rootdev -s FreeBSD postload
if [ $? -eq 0 ]; then
doosconfig=1
fixit=1
fi
fi
if [ $fixit -eq 0 ]; then
echo " no changes necessary"
umount $rootdev
......@@ -322,6 +332,11 @@ EOF2
fi
fi
# actually run any postconfig scripts if we're supposed to:
if [ $doosconfig -eq 1 -a -x $BINDIR/osconfig ]; then
$BINDIR/osconfig -m /mnt -D $rootdev -s FreeBSD postload
fi
umount $rootdev
# XXX need to fixup FS, see big comment above
......@@ -477,6 +492,13 @@ dolinux() {
dd if=/dev/urandom of=/mnt/var/lib/random-seed bs=512 count=1 >/dev/null 2>&1
fi
# run any postconfig scripts:
if [ -x $BINDIR/osconfig ]; then
echo "Checking for dynamic client-side updates to slice...";
$BINDIR/osconfig -m /mnt -M '-t ext2fs' -f ext2fs \
-D $rootdev -s Linux postload
fi
umount $rootdev
if [ "x${linux}" != x ]; then
......
......@@ -35,6 +35,7 @@ $MAILMANSUPPORT = @MAILMANSUPPORT@;
$CHATSUPPORT = @CHATSUPPORT@;
$ISOLATEADMINS = @ISOLATEADMINS@;
$CONTROL_NETWORK= "@CONTROL_NETWORK@";
$CONTROL_NETMASK= "@CONTROL_NETMASK@";
$WIKIHOME = "https://${USERNODE}/twiki";
$WIKIURL = "${WIKIHOME}/bin/newlogon";
$WIKICOOKIENAME = "WikiCookie";
......
<?php
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2008 University of Utah and the Flux Group.
# All rights reserved.
#
require("defs.php3");
require("node_defs.php");
$reqargs = RequiredPageArguments("ip", PAGEARG_STRING);
$optargs = OptionalPageArguments("privkey", PAGEARG_STRING,
"check", PAGEARG_BOOLEAN,
"os", PAGEARG_STRING,
"os_version", PAGEARG_STRING,
"distro", PAGEARG_STRING,
"distro_version", PAGEARG_STRING,
"arch", PAGEARG_STRING,
"env", PAGEARG_STRING);
#
# The point of this page is to figure out what osconfig scripts
# the node ought to execute, and return them in a tarball. We attempt to
# cache the tarballs as much as possible and only rebuild when necessary.
#
# NOTE! This page is only accessible over SSL, but it is only "secure" for
# widearea nodes (which provide a privkey). Local connections are not secure
# at all -- eventually this could move to tmcc. And when you get down to it,
# the widearea privkeys are not well protected. Consequently, NO PRIVATE DATA
# should go into these scripts. There should not be a need for that, anyway.
# This is meant to be an operator mechanism, not a user mechanism, and it is
# not meant for private data, just client-side config stuff.
#
#
# Constraints that we satisfy. Note that different constraint types are ANDed;
# if there are multiple values to a constraint type, those are ORed. Had to
# do something...
#
$constraints = array("node_id","class","type",
"os","os_version","distro","distro_version","arch");
#
# Available states in which we support an osconfig
#
$stages = array("premfs","postload");
$FILEROOT = "$TBDIR/etc/osconfig";
#
# A few helper functions.
#
function SPITERROR($msg) {
header("Content-Type: text/plain");
echo "TBERROR: $msg\n";
exit(1);
}
function SPITMSG($msg) {
header("Content-Type: text/plain");
echo "TBMSG: $msg\n";
exit(1);
}
function SPITNOTHING() {
header("Content-Type: text/plain");
header("Content-Length: 0");
exit(0);
}
function IsControlNetIP($myIP) {
global $CONTROL_NETWORK,$CONTROL_NETMASK;
if (ip2long($CONTROL_NETWORK) == (ip2long($CONTROL_NETMASK)
&& ip2long($myIP)))
return 1;
return 0;
}
function match_constraint($constraint,$value,$data) {
if (!isset($data[$constraint]))
return FALSE;
if (!strncmp($value,"/",1)) {
# a pcre regex
if (preg_match($value,$data[$constraint]))
return TRUE;
}
else if ($value == $data[$constraint]) {
return TRUE;
}
return FALSE;
}
#
# do stuff!
#
# if we do not have a stage, assume one
if (!isset($env) || !in_array($env,$stages)) {
$env = "postload";
}
# if check is not set, don't just check
if (!isset($check)) {
$check = 0;
}
# Must use https
if (!isset($_SERVER["SSL_PROTOCOL"])) {
SPITERROR("insecure");
}
# make sure args are sane
if (!preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $ip) ||
(isset($privkey) && !preg_match('/^[\w]+$/', $privkey))) {
SPITERROR("invalid args");
}
# XXX for now, we assert if REMOTE_ADDR is not the same as $ip
# obviously, this needs to be changed for remote nodes with proxies...
if ($_SERVER["REMOTE_ADDR"] != $ip) {
SPITERROR("no proxies allowed");
}
# need this below
$node = Node::LookupByIP($ip);
if (!$node) {
SPITERROR("no such node");
}
$node_id = $node->node_id();
#
# Make sure this is a valid request before we return anything.
#
if (isset($privkey)) {
$qres = DBQueryFatal("select node_id from widearea_nodeinfo" .
" where node_id='" . addslashes($node_id) . "' and" .
" privkey='" . addslashes($privkey) . "'");
if (! mysql_num_rows($qres)) {
SPITERROR("noauth");
}
}
else {
if (!IsControlNetIP($ip)) {
SPITERROR("notlocal");
}
}
#
# Load up and match constraints.
#
$qres = DBQueryFatal("select target_file_idx,constraint_name,constraint_value" .
" from osconfig_targets" .
" where target_apply='" . addslashes($env) . "'" .
" order by constraint_idx");
if (!mysql_num_rows($qres))
SPITNOTHING();
$match_targets = array();
$conproc = array();
$data = array("node_id" => $node_id,"class" => $node->TypeClass(),
"type" => $node->type());
if (isset($os))
$data["os"] = $os;
if (isset($os_version))
$data["os_version"] = $os_version;
if (isset($distro))
$data["distro"] = $distro;
if (isset($distro_version))
$data["distro_version"] = $distro_version;
if (isset($arch))
$data["arch"] = $arch;
while ($row = mysql_fetch_array($qres)) {
$c = $row["constraint_name"];
if (!in_array($c,$constraints))
continue;
$val = match_constraint($c,$row["constraint_value"],$data);
if (!isset($conproc[$row["target_file_idx"]]))
$conproc[$row["target_file_idx"]] = array();
if (!isset($conproc[$row["target_file_idx"]][$c]))
$conproc[$row["target_file_idx"]][$c] = $val;
else
$conproc[$row["target_file_idx"]][$c] =
($conproc[$row["target_file_idx"]][$c] || $val);
}
#
# Look through the results; for each target_idx, if all constraints
# have been met, then we can call this target matched.
#
foreach ($conproc as $target_idx => $target) {
$cmatch = FALSE;
foreach ($target as $constraint => $match)
$cmatch = ($cmatch || $match);
if ($cmatch)
array_push($match_targets,$target_idx);
}
#
# If we have no matches and the client is asking for a tarball, spit nothing;
# if the client is just checking if we have a tarball for them, spit an
# "update=(yes|no)" msg.
#
if (!count($match_targets)) {
if ($check) {
SPITMSG("update=no");
}
else {
SPITNOTHING();
}
}
else if ($check) {
SPITMSG("update=yes");
}
#
# Now, for each target matched, grab the file-to-send info, and pack them up.
# We just grab everything cause it's cheap and there's no better way.
#
$qres = DBQueryFatal("select of.type as type,of.path as path," .
" of.dest as dest,of.file_idx as file_idx" .
" from osconfig_targets as ot" .
" left join osconfig_files as of" .
" on ot.target_file_idx=of.file_idx" .
" where ot.target_apply='" . addslashes($env) . "'" .
" group by of.file_idx" .
" order by of.prio desc");
$done = array();
$files = array();
$manifest_str = "";
$i = 0;
while ($row = mysql_fetch_array($qres)) {
if (in_array($row['file_idx'],$match_targets)
&& !in_array($row['file_idx'])) {
$files[$i++] = $row["path"];
$manifest_str .= $row["path"] . "\t" . $row["type"] . "\t" .
$row["dest"] . "\n";
array_push($done,$row['file_idx']);
}
}
# make a random dir for tar use.
while (TRUE) {
$randdir = "/tmp/osconfig." . rand(100,10000);
if (mkdir($randdir)) {
break;
}
}
$archive = "$randdir/archive";
$manifest = "$randdir/MANIFEST";
$fd = fopen($manifest,"w");
fwrite($fd,$manifest_str);
fclose($fd);
$origwd = getcwd();
chdir($FILEROOT);
$retval = system("tar -cf $archive " . implode(" ",$files));
if ($retval)
SPITERROR("cdata");
chdir("$randdir");
$retval = system("tar -rf $archive MANIFEST");
if ($retval)
SPITERROR("cdata");
system("gzip $archive");
$archive .= ".gz";
#
# Phew! Blow chunk and finish.
#
$fstat = stat($archive);
header("Content-Type: application/x-gzip");
header("Content-Length: " . $fstat["size"]);
readfile($archive);
# cleanup
chdir($origwd);
unlink($archive);
unlink($manifest);
rmdir($randdir);
return;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment