Commit 4c56daf6 authored by Leigh B. Stoller's avatar Leigh B. Stoller

Add "gid" slot to the images table for changing permission scheme from

only pid, to pid/gid like most other things in the testbed. Also add a
"global" slot to denote images that are globally available to all
projects (system images). The older "shared" attribute is now used to
denote images that are shared within a project (available to all
subgroups in the project). The migration path for existing DBs is
given in the migrate file. Be sure to run those commands on an
existing testbed or things will break!

www/newimageid, www/newimageid_ez: A bunch of changes for
shared/global attributes. Added a group menu to the form so users can
create images in subgroups. Beefed up the Java code that constructs
the path name to use the gid, shared, and global attributes of the
form to give the user the best possible path that we can. Improved the
pathname checking code so that we do not allow just any old path in
case the user elects to disregard the path we carefully constructed
for them. Also check the proj/group membership, and setup defaults for
users that have permission in just one pid/gid to create images.

libdb.in: Changed permission check in TBImageIDAccessCheck() to
reflect shared/global attribute changes.

os_load: Get rid of test that checked path of the image. The path
checking is done in the web interface anyway, so why duplicate in 4
places. Other minor changes reflecting shared->global name change.
Also note that images can come from the group directory now.

create_image: Get rid of test that checked path of the image. The path
checking is done in the web interface anyway, so why duplicate in 4
places. Also note that images can come from the group directory now.

www/dbdefs: Changed permission check in TBImageIDAccessCheck() to
reflect shared/global attribute changes.

www/showimageid_list, www/showstuff: Minor global/shared attribute
changes.

www/menu: Change osids/imageids pointer to point to the image list,
not the osid list. This is more reasonable for mere users who have
access to the EZ form, and thus never really need to concern
themselves with osids.

www/editimageid: Add proper pathname checking. There were no checks at
all before!
parent 52396569
...@@ -842,19 +842,22 @@ sub TBImageIDAccessCheck($$$) ...@@ -842,19 +842,22 @@ sub TBImageIDAccessCheck($$$)
# No GIDs yet. # No GIDs yet.
# #
my $query_result = my $query_result =
DBQueryFatal("SELECT pid,shared FROM images WHERE imageid='$imageid'"); DBQueryFatal("SELECT pid,gid,shared,global FROM images ".
"WHERE imageid='$imageid'");
if ($query_result->numrows == 0) { if ($query_result->numrows == 0) {
return 0; return 0;
} }
my @row = $query_result->fetchrow_array(); my @row = $query_result->fetchrow_array();
my $pid = $row[0]; my $pid = $row[0];
my $shared = $row[1]; my $gid = $row[1];
my $shared = $row[2];
my $global = $row[3];
# #
# Global ImageIDs can be read by anyone. # Global ImageIDs can be read by anyone.
# #
if ($shared) { if ($global) {
if ($access_type == TB_IMAGEID_READINFO) { if ($access_type == TB_IMAGEID_READINFO) {
return 1; return 1;
} }
...@@ -866,12 +869,18 @@ sub TBImageIDAccessCheck($$$) ...@@ -866,12 +869,18 @@ sub TBImageIDAccessCheck($$$)
# #
if ($access_type == TB_IMAGEID_READINFO) { if ($access_type == TB_IMAGEID_READINFO) {
$mintrust = PROJMEMBERTRUST_USER; $mintrust = PROJMEMBERTRUST_USER;
#
# Shared imageids are readable by anyone in the project.
#
if ($shared) {
$gid = $pid;
}
} }
else { else {
$mintrust = PROJMEMBERTRUST_LOCALROOT; $mintrust = PROJMEMBERTRUST_LOCALROOT;
} }
return TBMinTrust(TBProjTrust($uid, $pid), $mintrust); return TBMinTrust(TBGrpTrust($uid, $pid, $gid), $mintrust);
} }
# #
......
...@@ -303,6 +303,7 @@ CREATE TABLE iface_counters ( ...@@ -303,6 +303,7 @@ CREATE TABLE iface_counters (
CREATE TABLE images ( CREATE TABLE images (
imagename varchar(30) NOT NULL default '', imagename varchar(30) NOT NULL default '',
pid varchar(12) NOT NULL default '', pid varchar(12) NOT NULL default '',
gid varchar(12) NOT NULL default '',
imageid varchar(45) NOT NULL default '', imageid varchar(45) NOT NULL default '',
creator varchar(8) default NULL, creator varchar(8) default NULL,
created datetime default NULL, created datetime default NULL,
...@@ -320,10 +321,12 @@ CREATE TABLE images ( ...@@ -320,10 +321,12 @@ CREATE TABLE images (
load_busy tinyint(4) NOT NULL default '0', load_busy tinyint(4) NOT NULL default '0',
ezid tinyint(4) NOT NULL default '0', ezid tinyint(4) NOT NULL default '0',
shared tinyint(4) NOT NULL default '0', shared tinyint(4) NOT NULL default '0',
global tinyint(4) NOT NULL default '0',
updated datetime default NULL, updated datetime default NULL,
max_concurrent int(11) default NULL, max_concurrent int(11) default NULL,
PRIMARY KEY (imagename,pid), PRIMARY KEY (imagename,pid),
KEY imageid (imageid) KEY imageid (imageid)
KEY gid (gid)
) TYPE=MyISAM; ) TYPE=MyISAM;
-- --
......
...@@ -104,3 +104,16 @@ last_net_act,last_cpu_act,last_ext_act); ...@@ -104,3 +104,16 @@ last_net_act,last_cpu_act,last_ext_act);
user_pubkeys_new to user_pubkeys; user_pubkeys_new to user_pubkeys;
drop table user_pubkeys_old; drop table user_pubkeys_old;
1.120: Add gid slot to images table for per-subgroup image support
Also add global global flag, to replace shared flag. Global
means testbed wide, while shared means within a project. To
migrate an existing DB, just need to set pid=gid,global=shared
for all existing images, and then set shared=0.
alter table images add gid varchar(12) NOT NULL default '' after pid;
alter table images add INDEX (gid);
alter table images add global tinyint(4) NOT NULL default '0' \
after shared;
update images set gid=pid,global=shared;
update images set shared=0;
...@@ -196,7 +196,6 @@ foreach my $node (@nodes) { ...@@ -196,7 +196,6 @@ foreach my $node (@nodes) {
my $loadlen = $imageid_row{'loadlength'}; my $loadlen = $imageid_row{'loadlength'};
my $imagepath = $imageid_row{'path'}; my $imagepath = $imageid_row{'path'};
my $defosid = $imageid_row{'default_osid'}; my $defosid = $imageid_row{'default_osid'};
my $shared = $imageid_row{'shared'};
my $max_concurrent = $imageid_row{'max_concurrent'}; my $max_concurrent = $imageid_row{'max_concurrent'};
# Check for a few errors early! # Check for a few errors early!
...@@ -248,15 +247,6 @@ foreach my $node (@nodes) { ...@@ -248,15 +247,6 @@ foreach my $node (@nodes) {
if ($loadpart) { $diskpart = "wd0:s${loadpart}"; } if ($loadpart) { $diskpart = "wd0:s${loadpart}"; }
else {$diskpart = "wd0"; } else {$diskpart = "wd0"; }
# For now, all testbed default images come from boss and all pid specific
# images come from ops:/proj.
if (! $shared && $mereuser) {
if (! ($imagepath =~ /^\/proj\//)) {
die("*** $0:\n".
" Your image must reside in /proj\n");
}
}
print STDOUT "Changing default OS for $node to $defosid\n"; print STDOUT "Changing default OS for $node to $defosid\n";
if (!$TESTMODE) { if (!$TESTMODE) {
system("$osselect -m PXEBOOT $node"); system("$osselect -m PXEBOOT $node");
...@@ -444,7 +434,7 @@ sub dolisting() { ...@@ -444,7 +434,7 @@ sub dolisting() {
$query_result = $query_result =
DBQueryFatal("select distinct i.* from images as i ". DBQueryFatal("select distinct i.* from images as i ".
"left join group_membership as g on g.pid=i.pid ". "left join group_membership as g on g.pid=i.pid ".
"where g.uid='$me' or i.shared ". "where g.uid='$me' or i.global ".
"order by i.pid,i.imageid"); "order by i.pid,i.imageid");
} else { } else {
$query_result = $query_result =
......
...@@ -32,7 +32,6 @@ my $TB = "@prefix@"; ...@@ -32,7 +32,6 @@ my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@"; my $TBOPS = "@TBOPSEMAIL@";
my $TBLOGS = "@TBLOGSEMAIL@"; my $TBLOGS = "@TBLOGSEMAIL@";
my $BOSSNODE = "@BOSSNODE@"; my $BOSSNODE = "@BOSSNODE@";
my $PROJROOT = "/proj";
my $TFTPDIR = "/tftpboot"; my $TFTPDIR = "/tftpboot";
# #
...@@ -170,9 +169,8 @@ if ($mereuser && ...@@ -170,9 +169,8 @@ if ($mereuser &&
} }
# #
# Make sure that the filename is a /proj/$pid filename and a directory that # Make sure that the directory exists and is writeable for the user.
# exists and is writeable for the user. We test this by creating the file. # We test this by creating the file. Its going to get wiped anyway.
# Its going to get wiped anyway.
# #
my $filename = $imageid_row{'path'}; my $filename = $imageid_row{'path'};
...@@ -184,9 +182,6 @@ else { ...@@ -184,9 +182,6 @@ else {
fatal("Bad filename: $filename"); fatal("Bad filename: $filename");
} }
if (! ($filename =~ /^$PROJROOT\/$pid\/.*/)) {
fatal("File $filename for must reside someplace in $PROJROOT/$pid\n");
}
open(FILE, "> $filename") or open(FILE, "> $filename") or
fatal("Could not create $filename: $!"); fatal("Could not create $filename: $!");
close(FILE) or close(FILE) or
......
...@@ -721,23 +721,23 @@ function TBImageIDAccessCheck($uid, $imageid, $access_type) ...@@ -721,23 +721,23 @@ function TBImageIDAccessCheck($uid, $imageid, $access_type)
return 1; return 1;
} }
#
# No GIDs yet.
#
$query_result = $query_result =
DBQueryFatal("SELECT pid,shared FROM images WHERE imageid='$imageid'"); DBQueryFatal("SELECT pid,gid,shared,global FROM images ".
"WHERE imageid='$imageid'");
if (mysql_num_rows($query_result) == 0) { if (mysql_num_rows($query_result) == 0) {
return 0; return 0;
} }
$row = mysql_fetch_array($query_result); $row = mysql_fetch_array($query_result);
$shared = $row[shared]; $shared = $row[shared];
$global = $row["global"];
$pid = $row[pid]; $pid = $row[pid];
$gid = $row[gid];
# #
# Global ImageIDs can be read by anyone but written by Admins only. # Global ImageIDs can be read by anyone but written by Admins only.
# #
if ($shared) { if ($global) {
if ($access_type == $TB_IMAGEID_READINFO) { if ($access_type == $TB_IMAGEID_READINFO) {
return 1; return 1;
} }
...@@ -749,12 +749,17 @@ function TBImageIDAccessCheck($uid, $imageid, $access_type) ...@@ -749,12 +749,17 @@ function TBImageIDAccessCheck($uid, $imageid, $access_type)
# #
if ($access_type == $TB_IMAGEID_READINFO) { if ($access_type == $TB_IMAGEID_READINFO) {
$mintrust = $TBDB_TRUST_USER; $mintrust = $TBDB_TRUST_USER;
#
# Shared imageids are readable by anyone in the project.
#
if ($shared)
$gid = $pid;
} }
else { else {
$mintrust = $TBDB_TRUST_LOCALROOT; $mintrust = $TBDB_TRUST_LOCALROOT;
} }
return TBMinTrust(TBProjTrust($uid, $pid), $mintrust); return TBMinTrust(TBGrpTrust($uid, $pid, $gid), $mintrust);
} }
# #
......
<?php <?php
# #
# EMULAB-COPYRIGHT # EMULAB-COPYRIGHT
# Copyright (c) 2000-2002 University of Utah and the Flux Group. # Copyright (c) 2000-2003 University of Utah and the Flux Group.
# All rights reserved. # All rights reserved.
# #
include("defs.php3"); include("defs.php3");
...@@ -38,6 +38,16 @@ if (!TBImageIDAccessCheck($uid, $imageid, $TB_IMAGEID_MODIFYINFO)) { ...@@ -38,6 +38,16 @@ if (!TBImageIDAccessCheck($uid, $imageid, $TB_IMAGEID_MODIFYINFO)) {
USERERROR("You do not have permission to access ImageID $imageid!", 1); USERERROR("You do not have permission to access ImageID $imageid!", 1);
} }
#
# Need the gid for path checking.
#
$query_result =
DBQueryFatal("select * from images where imageid='$imageid'");
$row = mysql_fetch_array($query_result);
$gid = $row['gid'];
$pid = $row['pid'];
$shared = $row['shared'];
# #
# Sanitize values and create string pieces. # Sanitize values and create string pieces.
# #
...@@ -60,11 +70,24 @@ else { ...@@ -60,11 +70,24 @@ else {
} }
if (isset($path) && strcmp($path, "")) { if (isset($path) && strcmp($path, "")) {
$foo = addslashes($path); if (! ereg("^[-_a-zA-Z0-9\/\.+]+$", $path)) {
if (strcmp($path, $foo)) {
USERERROR("The path must not contain special characters!", 1); USERERROR("The path must not contain special characters!", 1);
} }
if (!$isadmin) {
$pdef = "";
if (!$shared && strcmp($gid, $pid)) {
$pdef = "/groups/" . $pid . "/" . $gid . "/";
}
else {
$pdef = "/proj/" . $pid . "/images/";
}
if (strpos($path, $pdef) === false) {
USERERROR("Invalid path! Must reside in /proj or /groups.", 1);
}
}
$path = "'$path'"; $path = "'$path'";
} }
else { else {
......
...@@ -201,8 +201,8 @@ function WRITESIDEBAR() { ...@@ -201,8 +201,8 @@ function WRITESIDEBAR() {
$TBBASE, "showexp_list.php3"); $TBBASE, "showexp_list.php3");
WRITESIDEBARBUTTON("Begin an Experiment", WRITESIDEBARBUTTON("Begin an Experiment",
$TBBASE, "beginexp.php3"); $TBBASE, "beginexp.php3");
WRITESIDEBARBUTTON("OSIDs and ImageIDs", WRITESIDEBARBUTTON("ImageIDs and OSIDs",
$TBBASE, "showosid_list.php3"); $TBBASE, "showimageid_list.php3");
WRITESIDEBARBUTTON("Update User Information", WRITESIDEBARBUTTON("Update User Information",
$TBBASE, "moduserinfo.php3"); $TBBASE, "moduserinfo.php3");
WRITESIDEBARBUTTON("Node Reservation Status", WRITESIDEBARBUTTON("Node Reservation Status",
......
...@@ -130,21 +130,53 @@ function SPITFORM($formfields, $errors) ...@@ -130,21 +130,53 @@ function SPITFORM($formfields, $errors)
echo "<SCRIPT LANGUAGE=JavaScript> echo "<SCRIPT LANGUAGE=JavaScript>
function SetPrefix(theform) function SetPrefix(theform)
{ {
var idx = theform['formfields[pid]'].selectedIndex; var pidx = theform['formfields[pid]'].selectedIndex;
var pid = theform['formfields[pid]'].options[idx].value; var pid = theform['formfields[pid]'].options[pidx].value;
var gidx = theform['formfields[gid]'].selectedIndex;
if (pid == '') { var gid = theform['formfields[gid]'].options[gidx].value;
theform['formfields[path]'].value = '/proj/'; var shared = theform['formfields[shared]'].checked;
\n";
if ($isadmin)
echo "var global = theform['formfields[global]'].checked;";
else
echo "var global = 0;";
echo "if (pid == '') {
theform['formfields[path]'].value = '/proj';
} }
else if (theform['formfields[imagename]'].value == '') { else if (theform['formfields[imagename]'].value == '') {
theform['formfields[imagename]'].defaultValue = ''; theform['formfields[imagename]'].defaultValue = '';
theform['formfields[path]'].value =
'/proj/' + pid + '/images/'; if (global) {
theform['formfields[path]'].value =
'/usr/testbed/images/';
}
else if (gid == '' || gid == pid || shared) {
theform['formfields[path]'].value =
'/proj/' + pid + '/images/';
}
else {
theform['formfields[path]'].value =
'/groups/' + pid + '/' + gid + '/';
}
} }
else if (theform['formfields[imagename]'].value != '') { else if (theform['formfields[imagename]'].value != '') {
theform['formfields[path]'].value = var filename = theform['formfields[imagename]'].value +
'/proj/' + pid + '/images/' + '.ndz';
theform['formfields[imagename]'].value + '.ndz';
if (global) {
theform['formfields[path]'].value =
'/usr/testbed/images/' + filename;
}
else if (gid == '' || gid == pid || shared) {
theform['formfields[path]'].value =
'/proj/' + pid + '/images/' + filename;
}
else {
theform['formfields[path]'].value =
'/groups/' + pid + '/' + gid + '/' +
filename;
}
} }
} }
</SCRIPT>\n"; </SCRIPT>\n";
...@@ -179,6 +211,38 @@ function SPITFORM($formfields, $errors) ...@@ -179,6 +211,38 @@ function SPITFORM($formfields, $errors)
echo " </td> echo " </td>
</tr>\n"; </tr>\n";
#
# Select a group
#
echo "<tr>
<td >Group:</td>
<td><select name=\"formfields[gid]\"
onChange='SetPrefix(idform);'>
<option value=''>Default Group </option>\n";
reset($projlist);
while (list($project, $grouplist) = each($projlist)) {
for ($i = 0; $i < count($grouplist); $i++) {
$group = $grouplist[$i];
if (strcmp($project, $group)) {
$selected = "";
if (isset($formfields[gid]) &&
isset($formfields[pid]) &&
strcmp($formfields[pid], $project) == 0 &&
strcmp($formfields[gid], $group) == 0)
$selected = "selected";
echo "<option $selected value=\"$group\">
$project/$group</option>\n";
}
}
}
echo " </select>
</td>
</tr>\n";
# #
# Image Name: # Image Name:
# #
...@@ -311,20 +375,41 @@ function SPITFORM($formfields, $errors) ...@@ -311,20 +375,41 @@ function SPITFORM($formfields, $errors)
</td> </td>
</tr>\n"; </tr>\n";
#
# Shared?
#
echo "<tr>
<td>Shared?:<br>
(available to all subgroups)</td>
<td class=left>
<input type=checkbox
onClick='SetPrefix(idform);'
name=\"formfields[shared]\"
value=Yep";
if (isset($formfields[shared]) &&
strcmp($formfields[shared], "Yep") == 0)
echo " checked";
echo " > Yes
</td>
</tr>\n";
if ($isadmin) { if ($isadmin) {
# #
# Shared? # Global?
# #
echo "<tr> echo "<tr>
<td>Shared?:<br> <td>Global?:<br>
(available to all projects)</td> (available to all projects)</td>
<td class=left> <td class=left>
<input type=checkbox <input type=checkbox
name=\"formfields[shared]\" onClick='SetPrefix(idform);'
name=\"formfields[global]\"
value=Yep"; value=Yep";
if (isset($formfields[shared]) && if (isset($formfields["global"]) &&
strcmp($formfields[shared], "Yep") == 0) strcmp($formfields["global"], "Yep") == 0)
echo " checked"; echo " checked";
echo " > Yes echo " > Yes
...@@ -395,6 +480,32 @@ if (! $submit) { ...@@ -395,6 +480,32 @@ if (! $submit) {
$defaults = array(); $defaults = array();
$defaults[loadpart] = "X"; $defaults[loadpart] = "X";
$defaults[path] = "/proj/"; $defaults[path] = "/proj/";
#
# For users that are in one project and one subgroup, it is usually
# the case that they should use the subgroup, and since they also tend
# to be in the clueless portion of our users, give them some help.
#
if (count($projlist) == 1) {
list($project, $grouplist) = each($projlist);
if (count($grouplist) <= 2) {
$defaults[pid] = $project;
if (count($grouplist) == 1 || strcmp($project, $grouplist[0]))
$group = $grouplist[0];
else {
$group = $grouplist[1];
}
$defaults[gid] = $group;
if (!strcmp($project, $group))
$defaults[path] = "/proj/$project/images/";
else
$defaults[path] = "/groups/$project/$group/";
}
reset($projlist);
}
SPITFORM($defaults, 0); SPITFORM($defaults, 0);
PAGEFOOTER(); PAGEFOOTER();
return; return;
...@@ -526,6 +637,26 @@ else { ...@@ -526,6 +637,26 @@ else {
$errors["Boot OS"] = "Invalid; Must be one of the partitions"; $errors["Boot OS"] = "Invalid; Must be one of the partitions";
} }
#
# Only admin types can set the global bit for an image. Ignore silently.
#
$global = 0;
if ($isadmin &&
isset($formfields["global"]) &&
strcmp($formfields["global"], "Yep") == 0) {
$global = 1;
}
$shared = 0;
if (isset($formfields[shared]) &&
strcmp($formfields[shared], "Yep") == 0) {
$shared = 1;
}
# Does not make sense to do this.
if ($global && $shared) {
$errors["Global"] = "Image declared both shared and global";
}
# #
# The path must not contain illegal chars and it must be more than # The path must not contain illegal chars and it must be more than
# the original /proj/$pid we gave the user. We allow admins to specify # the original /proj/$pid we gave the user. We allow admins to specify
...@@ -538,14 +669,20 @@ if (!isset($formfields[path]) || ...@@ -538,14 +669,20 @@ if (!isset($formfields[path]) ||
elseif (! ereg("^[-_a-zA-Z0-9\/\.+]+$", $formfields[path])) { elseif (! ereg("^[-_a-zA-Z0-9\/\.+]+$", $formfields[path])) {
$errors["Path"] = "Contains invalid characters"; $errors["Path"] = "Contains invalid characters";
} }
else { elseif (! $isadmin) {
$pdef = "/proj/$formfields[pid]/"; $pdef = "";
if (strcmp($formfields[path], "$pdef") == 0) { if (!$shared &&
$errors["Path"] = "Incomplete Path"; isset($formfields[gid]) &&
strcmp($formfields[gid], "") &&
strcmp($formfields[gid], $formfields[pid])) {
$pdef = "/groups/" . $formfields[pid] . "/" . $formfields[gid] . "/";
}
else {
$pdef = "/proj/" . $formfields[pid] . "/images/";
}