Commit 646b64f6 authored by Leigh B Stoller's avatar Leigh B Stoller

Add support for sharing images between projects. New table called

image_permissions stores access info for images. You can share an
image with a user or a group (project), and you can specify write
access to allow updating the image in place. Note that write access
does not allow the descriptor to be modified, only the image itself.
Well, that is how it will be after Mike changes mfrisbeed.

The front end script to modify permissions is grantimage:

	boss> grantimage -u stoller -w tbres,myimage
	boss> grantimage -u stoller -w tbres,myimage

which grants write access to stoller. Or:

	boss> grantimage -g testbed,testbed tbres,myimage

which grants access to the testbed project. Notice that you can
specify subgroups this way.

	boss> grantimage -l tbres,myimage

will give you a list of current permissions. To revoke, just add -r
option:

	boss> grantimage -g testbed,testbed -r tbres,myimage

Who is allowed to grant access to an image? 1) An adminstrator of
course, 2) the image creator, and 3) any group_root in the group that
the image belongs to. Being granted access to use an image does not
confer permission to grant access to others.

One last task; while the web interface displays the permissions, there
is no web interface to modify the permissions; users will still have
to ask us for now.
parent 6e910597
......@@ -58,7 +58,7 @@ use vars qw(@ISA @EXPORT);
TB_OSID_DESTROY TB_OSID_MIN TB_OSID_MAX
TB_OSID_OSIDLEN TB_OSID_OSNAMELEN TB_OSID_VERSLEN
TB_IMAGEID_READINFO TB_IMAGEID_MODIFYINFO
TB_IMAGEID_READINFO TB_IMAGEID_MODIFYINFO TB_IMAGEID_EXPORT
TB_IMAGEID_CREATE TB_IMAGEID_DESTROY
TB_IMAGEID_ACCESS TB_IMAGEID_MIN TB_IMAGEID_MAX
TB_IMAGEID_IMAGEIDLEN TB_IMAGEID_IMAGENAMELEN
......@@ -344,7 +344,8 @@ sub TB_IMAGEID_READINFO() { 1; }
sub TB_IMAGEID_MODIFYINFO() { 2; }
sub TB_IMAGEID_CREATE() { 3; }
sub TB_IMAGEID_DESTROY() { 4; }
sub TB_IMAGEID_ACCESS() { 5; }
sub TB_IMAGEID_EXPORT() { 5; }
sub TB_IMAGEID_ACCESS() { 6; }
sub TB_IMAGEID_MIN() { TB_IMAGEID_READINFO(); }
sub TB_IMAGEID_MAX() { TB_IMAGEID_ACCESS(); }
sub TB_IMAGEID_IMAGEIDLEN() { 45; }
......
......@@ -15,6 +15,7 @@ use vars qw(@ISA @EXPORT);
# Must come after package declaration!
use libdb;
use EmulabConstants;
use libtestbed;
use English;
use Data::Dumper;
......@@ -576,10 +577,12 @@ sub AccessCheck($$$)
print "*** Invalid access type $access_type!\n";
return 0;
}
my $isadmin = ((defined($user) && $user->IsAdmin()) ||
($UID == 0 || $UID eq "root") ? 1 : 0);
# Admins and root do whatever they want.
return 1
if ((defined($user) && $user->IsAdmin()) ||
($UID == 0 || $UID eq "root"));
if ($isadmin);
my $mintrust;
......@@ -590,6 +593,9 @@ sub AccessCheck($$$)
if ($access_type == TB_IMAGEID_READINFO) {
return 1;
}
if ($access_type == TB_IMAGEID_EXPORT && $isadmin) {
return 1;
}
return 0;
}
......@@ -612,6 +618,15 @@ sub AccessCheck($$$)
$group = $project->GetProjectGroup();
}
}
elsif ($access_type == TB_IMAGEID_EXPORT) {
#
# Owner or root in the project.
#
return 1
if ($user->uid_idx() == $self->creator_idx());
$mintrust = PROJMEMBERTRUST_GROUPROOT;
}
else {
$mintrust = PROJMEMBERTRUST_LOCALROOT;
}
......@@ -621,8 +636,64 @@ sub AccessCheck($$$)
# This lets group_roots muck with other people's experiments, including
# those in groups they do not belong to.
#
return TBMinTrust($group->Trust($user), $mintrust) ||
TBMinTrust($project->Trust($user), PROJMEMBERTRUST_GROUPROOT);
return 1
if (TBMinTrust($group->Trust($user), $mintrust) ||
TBMinTrust($project->Trust($user), PROJMEMBERTRUST_GROUPROOT));
# No point in looking further; never allowed.
return 0
if ($access_type == TB_IMAGEID_EXPORT);
#
# Look in the image permissions. First look for a user permission,
# then look for a group permission.
#
my $uid_idx = $user->uid_idx();
my $imageid = $self->imageid();
my $query_result =
DBQueryWarn("select allow_write from image_permissions ".
"where imageid='$imageid' and ".
" permission_type='user' and ".
" permission_idx='$uid_idx'");
return 0
if (!$query_result);
if ($query_result->numrows) {
# READINFO is read-only access to the image and its contents.
return 1
if ($access_type == TB_IMAGEID_READINFO());
if ($access_type == TB_IMAGEID_ACCESS()) {
my ($allow_write) = $query_result->fetchrow_array();
return 1
if ($allow_write);
}
}
my $trust_none = TBDB_TRUSTSTRING_NONE();
$query_result =
DBQueryFatal("select allow_write from group_membership as g ".
"left join image_permissions as p on ".
" p.permission_type='group' and ".
" p.permission_idx=g.gid_idx ".
"where g.uid_idx='$uid_idx' and ".
" p.imageid='$imageid' and ".
" trust!='$trust_none'");
if ($query_result->numrows) {
# READINFO is read-only access to the image and its contents.
return 1
if ($access_type == TB_IMAGEID_READINFO());
if ($access_type == TB_IMAGEID_ACCESS()) {
my ($allow_write) = $query_result->fetchrow_array();
return 1
if ($allow_write);
}
}
return 0;
}
#
......@@ -678,6 +749,87 @@ sub MarkUpdateTime($)
return 0;
}
#
# Get the type list.
#
sub TypeList($)
{
my ($self) = @_;
require NodeType;
my @result = ();
my $imageid = $self->imageid();
my $query_result =
DBQueryWarn("select distinct type from osidtoimageid ".
"where imageid='$imageid'");
return undef
if (!defined($query_result));
while (my ($type) = $query_result->fetchrow_array()) {
my $typeinfo = NodeType->Lookup($type);
push(@result, $typeinfo)
if (defined($typeinfo));
}
return @result;
}
#
# Grant/Revoke permission to access an image.
#
sub GrantAccess($$$)
{
my ($self, $target, $writable) = @_;
$writable = ($writable ? 1 : 0);
my $imageid = $self->imageid();
my $imagename = $self->imagename();
my ($perm_idx, $perm_id, $perm_type);
if (ref($target) eq "User") {
$perm_idx = $target->uid_idx();
$perm_id = $target->uid();
$perm_type = "user";
}
if (ref($target) eq "Group") {
$perm_idx = $target->gid_idx();
$perm_id = $target->pid() . "/" . $target->gid();
$perm_type = "group";
}
return -1
if (!DBQueryWarn("replace into image_permissions set ".
" imageid='$imageid', imagename='$imagename', ".
" permission_type='$perm_type', ".
" permission_id='$perm_id', ".
" permission_idx='$perm_idx', ".
" allow_write='$writable'"));
return 0;
}
sub RevokeAccess($$)
{
my ($self, $target) = @_;
my $imageid = $self->imageid();
my ($perm_idx, $perm_type);
if (ref($target) eq "User") {
$perm_idx = $target->uid_idx();
$perm_type = "user";
}
if (ref($target) eq "Group") {
$perm_idx = $target->gid_idx();
$perm_type = "group";
}
return -1
if (!DBQueryWarn("delete from image_permissions ".
"where imageid='$imageid' and ".
" permission_type='$perm_type' and ".
" permission_idx='$perm_idx'"));
return 0;
}
# _Always_ make sure that this 1 is at the end of the file...
1;
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2007-2010 University of Utah and the Flux Group.
# Copyright (c) 2007-2011 University of Utah and the Flux Group.
# All rights reserved.
#
package OSinfo;
......
......@@ -1661,6 +1661,21 @@ CREATE TABLE `image_history` (
KEY `node_history_id` (`node_history_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `image_permissions`
--
DROP TABLE IF EXISTS `image_permissions`;
CREATE TABLE `image_permissions` (
`imageid` int(8) unsigned NOT NULL default '0',
`imagename` varchar(30) NOT NULL default '',
`permission_type` enum('user','group') NOT NULL default 'user',
`permission_id` varchar(128) NOT NULL default '',
`permission_idx` mediumint(8) unsigned NOT NULL default '0',
`allow_write` tinyint(1) NOT NULL default '0',
PRIMARY KEY (`imageid`,`permission_type`,`permission_idx`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `images`
--
......
#
# New table to allow cross-project image access.
#
use strict;
use libdb;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
if (! DBTableExists("image_permissions")) {
DBQueryFatal("CREATE TABLE `image_permissions` ( ".
" `imageid` int(8) unsigned NOT NULL default '0', ".
" `imagename` varchar(30) NOT NULL default '', ".
" `permission_type` enum('user','group') NOT NULL default 'user', ".
" `permission_id` varchar(128) NOT NULL default '', ".
" `permission_idx` mediumint(8) unsigned NOT NULL default '0', ".
" `allow_write` tinyint(1) NOT NULL default '0', ".
" PRIMARY KEY (`imageid`,`permission_type`,`permission_idx`) ".
") ENGINE=MyISAM DEFAULT CHARSET=latin1");
}
return 0;
}
1;
......@@ -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
prereserve grantimage
WEB_SBIN_SCRIPTS= webnewnode webdeletenode webspewconlog webarchive_list \
webwanodecheckin webspewimage
......
......@@ -15,11 +15,12 @@ use Data::Dumper;
#
sub usage()
{
print("Usage: dumpimage [-d] [-i <imageid>] | [-o <osid>] \n");
print("Usage: dumpimage [-d] [-i <imageid> [-t]] | [-o <osid>] \n");
exit(-1);
}
my $optlist = "di:o:";
my $optlist = "di:o:t";
my $debug = 0;
my $dotypes = 0;
#
# Configure variables
......@@ -68,6 +69,9 @@ if (! getopts($optlist, \%options)) {
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"t"})) {
$dotypes = 1;
}
if (@ARGV) {
usage();
}
......@@ -156,6 +160,14 @@ sub DumpImage($)
}
}
}
if ($dotypes) {
my @typelist = $image->TypeList();
foreach my $nodetype (@typelist) {
my $type = $nodetype->type();
$xmlfields{"mtype_$type"} = "1";
}
}
print "<image>\n";
foreach my $key (sort keys(%xmlfields)) {
......
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2003-2011 University of Utah and the Flux Group.
# All rights reserved.
#
use strict;
use English;
use Getopt::Std;
#
# Grant and revoke permission to use specific images.
#
sub usage()
{
print STDERR "Usage: grantimage [-r] [-w] [-g <gid> | -u <uid>] <imageid>\n";
print STDERR " grantimage -l <imageid>\n";
print STDERR " -h This message\n";
print STDERR " -l List permissions\n";
print STDERR " -w Grant write permission; defaults to read only\n";
print STDERR " -r Revoke access instead of grant\n";
exit(-1);
}
my $optlist = "hg:dnru:wl";
my $impotent = 0;
my $debug = 0;
my $revoke = 0;
my $writable = 0;
my $listonly = 0;
my $gid;
my $uid;
my $target;
# Protos
sub fatal($);
#
# Please do not run as root. Hard to track what has happened.
#
if ($UID == 0) {
die("*** $0:\n".
" Please do not run this as root!\n");
}
#
# Configure variables
#
my $TB = "@prefix@";
#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use emdb;
use EmulabConstants;
use libtestbed;
use Experiment;
use Project;
use Group;
use User;
use Image;
#
# Turn off line buffering on output
#
$| = 1;
#
# Untaint the path
#
$ENV{'PATH'} = "/bin:/sbin:/usr/bin:";
#
# 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{h})) {
usage();
}
if (defined($options{l})) {
$listonly = 1;
}
if (defined($options{n})) {
$impotent = 1;
}
if (defined($options{r})) {
$revoke = 1;
}
if (defined($options{d})) {
$debug = 1;
}
if (defined($options{w})) {
$writable = 1;
}
if (defined($options{g})) {
$gid = $options{g};
}
if (defined($options{u})) {
$uid = $options{u};
}
usage()
if (@ARGV != 1);
usage()
if (! ($listonly || defined($gid) || defined($uid)));
my $imageid = $ARGV[0];
#
# Verify user.
#
my $this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
if (defined($gid)) {
$target = Group->Lookup($gid);
if (!defined($target)) {
fatal("No such project or group $gid\n");
}
}
elsif (defined($uid)) {
$target = User->Lookup($uid);
if (!defined($target)) {
fatal("No such user $uid\n");
}
}
my $image = Image->Lookup($imageid);
if (!defined($image)) {
fatal("No such image exists");
}
$imageid = $image->imageid();
# User must have permission.
if (! $image->AccessCheck($this_user, TB_IMAGEID_EXPORT())) {
fatal("You do not have permission to change the external permissions");
}
if ($listonly) {
my $query_result =
DBQueryFatal("select * from image_permissions ".
"where imageid='$imageid'");
while (my $row = $query_result->fetchrow_hashref()) {
my $perm_type = $row->{'permission_type'};
my $perm_id = $row->{'permission_id'};
my $perm_idx = $row->{'permission_idx'};
my $write = $row->{'allow_write'};
print "$perm_type: $perm_id ($perm_idx) ";
print "writable" if ($write);
print "\n";
}
exit(0);
}
elsif ($revoke) {
$image->RevokeAccess($target) == 0
or fatal("Could not revoke permission for $target");
}
else {
$image->GrantAccess($target, $writable) == 0
or fatal("Could not grant permission for $target");
}
exit(0);
sub fatal($)
{
my ($mesg) = $_[0];
die("*** $0:\n".
" $mesg\n");
}
......@@ -340,9 +340,13 @@ class Image
$shared = $this->shared();
$global = $this->isglobal();
$imageid= $this->imageid();
$pid = $this->pid();
$gid = $this->gid();
$uid = $user->uid();
$uid_idx= $user->uid_idx();
$pid_idx= $user->uid_idx();
$gid_idx= $user->uid_idx();
#
# Global ImageIDs can be read by anyone but written by Admins only.
......@@ -369,8 +373,50 @@ class Image
$mintrust = $TBDB_TRUST_LOCALROOT;
}
return TBMinTrust(TBGrpTrust($uid, $pid, $gid), $mintrust) ||
TBMinTrust(TBGrpTrust($uid, $pid, $pid), $TBDB_TRUST_GROUPROOT);
if (TBMinTrust(TBGrpTrust($uid, $pid, $gid), $mintrust) ||
TBMinTrust(TBGrpTrust($uid, $pid, $pid), $TBDB_TRUST_GROUPROOT)) {
return 1;
}
# No point in looking further; never allowed.
if ($access_type == $TB_IMAGEID_EXPORT) {
return 0;
}
#
# Look in the image permissions. First look for a user permission,
# then look for a group permission.
#
$query_result =
DBQueryFatal("select allow_write from image_permissions ".
"where imageid='$imageid' and ".
" permission_type='user' and ".
" permission_idx='$uid_idx'");
if (mysql_num_rows($query_result)) {
$row = mysql_fetch_array($query_result);
# Only allowed to read.
if ($access_type == $TB_IMAGEID_READINFO ||
$access_type == $TB_IMAGEID_ACCESS)
return 1;
}
$trust_none = TBDB_TRUSTSTRING_NONE;
$query_result =
DBQueryFatal("select allow_write from group_membership as g ".
"left join image_permissions as p on ".
" p.permission_type='group' and ".
" p.permission_idx=g.gid_idx ".
"where g.uid_idx='$uid_idx' and ".
" p.imageid='$imageid' and ".
" trust!='$trust_none'");
if (mysql_num_rows($query_result)) {
# Only allowed to read.
if ($access_type == $TB_IMAGEID_READINFO ||
$access_type == $TB_IMAGEID_ACCESS)
return 1;
}
return 0;
}
#
......@@ -404,7 +450,7 @@ class Image
return $this->group;
}
function Show() {
function Show($showperms = 0) {
$imageid = $this->imageid();
$imagename = $this->imagename();
$pid = $this->pid();
......@@ -575,6 +621,58 @@ class Image
<td class=left>$uuid</td>
</tr>\n";
#
# Show who all can access this image outside the project.
#
if ($showperms) {
$query_result =
DBQueryFatal("select * from image_permissions ".
"where imageid='$imageid' ".
"order by permission_type,permission_id");
if (mysql_num_rows($query_result)) {
echo "<tr>
<td align=center colspan=2>
External permissions
</td>
</tr>\n";
while ($row = mysql_fetch_array($query_result)) {
$perm_type = $row['permission_type'];
$perm_idx = $row['permission_idx'];
$writable = $row['allow_write'];
if ($writable) {
$writable = "(read/write)";
}
else {
$writable = "(read only)";
}
if ($perm_type == "user") {
$user = User::Lookup($perm_idx);
if (isset($user)) {
$uid = $user->uid();
echo "<tr>
<td>User: </td>
<td class=left>$uid $writable</td>
</tr>\n";
}
}
elseif ($perm_type == "group") {
$group = Group::Lookup($perm_idx);
if (isset($group)) {
$pid = $group->pid();
$gid = $group->gid();
echo "<tr>
<td>Group: </td>
<td class=left>$pid/$gid $writable</td>
</tr>\n";
}
}
}
}
}
echo "</table>\n";
}
......
<?php
#
# EMULAB-COPYRIGHT
# Copyright (c) 2006, 2007, 2009, 2010 University of Utah and the Flux Group.
# Copyright (c) 2006-2011 University of Utah and the Flux Group.
# All rights reserved.
#
......@@ -255,7 +255,50 @@ class OSinfo
if (! ($project = Project::Lookup($pid))) {
TBERROR("Could not map project $pid to its object", 1);
}
return TBMinTrust($project->UserTrust($user), $mintrust);
if (TBMinTrust($project->UserTrust($user), $mintrust)) {
return 1;
}
elseif (!$this->ezid()) {
return 0;
}
#
# If this is an ez image, look in the image permissions.
# First look for a user permission, then look for a group permission.
#
$osid = $this->osid();
$uid_idx = $user->uid_idx();
$trust_none = TBDB_TRUSTSTRING_NONE;
$query_result =
DBQueryFatal("select allow_write from image_permissions ".
"where imageid='$osid' and ".
" permission_type='user' and ".
" permission_idx='$uid_idx'");
if (mysql_num_rows($query_result)) {
$row = mysql_fetch_array($query_result);
# Only allowed to read.
if ($access_type == $TB_OSID_READINFO)
return 1;
}
$trust_none = TBDB_TRUSTSTRING_NONE;
$query_result =
DBQueryFatal("select allow_write from group_membership as g ".
"left join image_permissions as p on ".
" p.permission_type='group' and ".
" p.permission_idx=g.gid_idx ".
"where g.uid_idx='$uid_idx' and ".
" p.imageid='$osid' and ".
" trust!='$trust_none'");
if (mysql_num_rows($query_result)) {
# Only allowed to read.
if ($access_type == $TB_OSID_READINFO)
return 1;
}
return 0;
}
#
......