Commit 188f041f authored by Leigh B Stoller's avatar Leigh B Stoller
Browse files

Attempt to operate in an admin mode for reservations

So, one reason the fast RPC path is fast cause we do not normally
operate with credentials, but with reservations we have to since we want
the reservation creator to be a real user and of course the project has
to exist. Need credentials for that. But when an admin is editing or
creating a reservation in another project, we need the admin user to
exist too, and we might need the project to be created. That requires
different credentials. So in an attempt to deal more generally with the
admin problem, export an entrypoint to create a user (the admin user)
before trying to create a reservation. Not sure this is the best way to
go but its one way to go.

In general, I think we need a more explicit user/project management API
for the Portal. Needs more thought.
parent ff59b87e
#!/usr/bin/perl -wT
#
# Copyright (c) 2007-2016 University of Utah and the Flux Group.
# Copyright (c) 2007-2017 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -487,6 +487,31 @@ sub GenUserCredential($)
return ($credential, $speaksfor);
}
#
# Create a user at a cluster via the Cluster RPC Server. The point
# of this is so that the admin user we are operating as exists at
# the cluster when we make the admin level call via using the root
# certificate. Not the best approach, but best I could think of.
#
sub CreatePortalUser($$)
{
my ($authority, $geniuser) = @_;
my $context = APT_Geni::GeniContext();
my ($credential,$speaksfor) =
APT_Geni::GenUserCredential($geniuser);
return -1
if (!defined($credential));
my $credentials = [$credential->asString()];
if (defined($speaksfor)) {
$credentials = [@$credentials, $speaksfor->asString()];
}
my $args = {"credentials" => $credentials};
my $response = PortalRPC($authority, $context, "CreateUser", $args);
return $response->code();
}
#
# RPC to the Cluster RPC server.
#
......
......@@ -249,14 +249,17 @@ sub DoReserve()
$rpcargs{"reason"}= $reason if (defined($reason));
$rpcargs{"update"}= $update if (defined($update));
$context = APT_Geni::GeniContext();
if ($this_user->IsAdmin()) {
#
# We do not have a very good notion of cross site admin.
# So first we make sure that the user exists at the cluster
# and then we make an "admin" call as the root authority.
#
($credential,$speaksfor) =
APT_Geni::GenUserCredential($geniuser);
if (APT_Geni::CreatePortalUser($authority, $geniuser)) {
fatal("Could not create admin user at remote cluster");
}
$rpcargs{"project_urn"} = $project->urn()->asString();
$rpcargs{"user_urn"} = $geniuser->urn()->asString();
}
else {
if (!$project->AccessCheck($this_user, TB_PROJECT_CREATEEXPT())) {
......@@ -264,15 +267,16 @@ sub DoReserve()
}
($credential,$speaksfor) =
APT_Geni::GenProjectCredential($project, $geniuser);
fatal("Could not generate credentials")
if (!defined($credential));
my $credentials = [$credential->asString()];
if (defined($speaksfor)) {
$credentials = [@$credentials, $speaksfor->asString()];
}
$rpcargs{"credentials"} = $credentials;
$context = APT_Geni::GeniContext();
}
fatal("Could not generate credentials")
if (!defined($credential));
my $credentials = [$credential->asString()];
if (defined($speaksfor)) {
$credentials = [@$credentials, $speaksfor->asString()];
}
$rpcargs{"credentials"} = $credentials;
my $response =
APT_Geni::PortalRPC($authority, $context, "Reserve", \%rpcargs);
if (GeniResponse::IsError($response)) {
......@@ -330,7 +334,7 @@ sub DoList()
my $anon = 0;
my $idx;
my $project;
my $user = $this_user;
my $user;
my %rpcargs = ();
my $context;
......@@ -361,7 +365,7 @@ sub DoList()
fatal("No such project");
}
if (!$this_user->IsAdmin() &&
!$project->AccessCheck($user, TB_PROJECT_CREATEEXPT())) {
!$project->AccessCheck($this_user, TB_PROJECT_CREATEEXPT())) {
fatal("No permission to access reservation list for $project")
}
}
......@@ -435,8 +439,25 @@ sub DoList()
# Strip out unwanted results if we asked as an admin for a specific
# user or project.
#
if ($this_user->IsAdmin()) {
if ($this_user->IsAdmin() && (defined($project) || defined($user))) {
my $tmp = {};
foreach my $key (keys(%$list)) {
my $details = $list->{$key};
if (defined($project)) {
next
if (!defined($details->{'pid_idx'}) ||
$details->{'pid_idx'} != $project->pid_idx());
}
else {
next
if (!defined($details->{'uid_idx'}) ||
$details->{'uid_idx'} != $user->uid_idx());
}
$tmp->{$key} = $details;
}
$list = $tmp;
}
if (defined($webtask)) {
$webtask->value($list);
......
......@@ -127,6 +127,46 @@ sub GetVersion()
return GeniResponse->Create(GENIRESPONSE_SUCCESS, $API_VERSION);
}
#
# Create user/authority from a credential. We need to do this to support
# admin level operations that require that the target user ans authority
# already exist for the purposes of bookkeeping. But they cannot be
# created during the admin level operation since the creds/certs will not
# be what we expect. I do not much like this, but no brighter ideas at
# this time.
#
# So, the caller will make this call just to get the local user/authority
# into the database, and then issue the actual call as the root authority.
# And only admins at the permitted sites (CheckPermission() above) can do
# that.
#
sub CreateUser($)
{
my ($argref) = @_;
my $hasperm = CheckPermission(0);
return $hasperm
if (GeniResponse::IsError($hasperm));
if (!exists($argref->{'credentials'})) {
return GeniResponse->MalformedArgsResponse("Missing credentials")
}
my $credentials = $argref->{'credentials'};
my ($credential,$speaksfor) = GeniStd::CheckCredentials($credentials);
return $credential
if (GeniResponse::IsResponse($credential));
return GeniResponse->MalformedArgsResponse("Not a user credential")
if ($credential->target_urn()->type() ne "user");
my $geniuser = GeniCM::CreateUserFromCertificate($credential);
return $geniuser
if (GeniResponse::IsResponse($geniuser));
return GeniResponse->Create(GENIRESPONSE_SUCCESS);
}
#
# Return the InUse info, which includes the pre-reserve info.
#
......@@ -602,6 +642,8 @@ sub Reserve($)
my ($argref) = @_;
my %blob = ();
my $reserror;
my $asadmin = 0;
my ($geniuser, $project);
my $hasperm = CheckPermission(0);
return $hasperm
......@@ -616,34 +658,43 @@ sub Reserve($)
# Otherwise we get a project credential issued to a user in that
# project.
#
if (!exists($argref->{'credentials'})) {
return GeniResponse->MalformedArgsResponse("Missing credentials")
}
my ($geniuser, $project) =
Credential2UserProject($argref->{'credentials'});
if (exists($argref->{'credentials'})) {
($geniuser, $project) =
Credential2UserProject($argref->{'credentials'});
return $geniuser
if (GeniResponse::IsResponse($geniuser));
return GeniResponse->MalformedArgsResponse("Not a local user")
if (!$geniuser->IsLocal());
return $geniuser
if (GeniResponse::IsResponse($geniuser));
return GeniResponse->MalformedArgsResponse("Not a local user")
if (!$geniuser->IsLocal());
}
else {
#
# Must be the root authority.
#
$hasperm = CheckPermission(1);
return $hasperm
if (GeniResponse::IsError($hasperm));
#
# No project, we need to have a project urn.
#
if (!defined($project)) {
return GeniResponse->MalformedArgsResponse("Missing user URN")
if (!exists($argref->{'user_urn'}));
return GeniResponse->MalformedArgsResponse("Missing project URN")
if (!exists($argref->{'project_urn'}));
return GeniResponse->MalformedArgsResponse("Invalid user URN")
if (!GeniHRN::IsValid($argref->{'user_urn'}));
return GeniResponse->MalformedArgsResponse("Invalid project URN")
if (!GeniHRN::IsValid($argref->{'project_urn'}));
my $hrn = GeniHRN->new($argref->{'project_urn'});
return GeniResponse->MalformedArgsResponse("Mismatching project URN")
if ($hrn->domain() ne
$geniuser->emulab_user()->nonlocalurn()->domain());
# User should already exist; the caller used CreateUser() above first.
$geniuser = GeniUser->Lookup($argref->{'user_urn'}, 1);
return GeniResponse->MalformedArgsResponse("No such user")
if (!defined($geniuser));
my $group = GeniUtil::GetHoldingProject($hrn,undef,1);
my $group = GeniUtil::GetHoldingProject($argref->{'project_urn'},
undef, 1);
return $group
if (GeniResponse::IsResponse($group));
$project = $group->GetProject();
$asadmin = 1;
}
my $pid = $project->pid();
my $uid = $geniuser->emulab_user()->uid();
......@@ -736,7 +787,7 @@ sub Reserve($)
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN)
if ($reservation->pid() ne $project->pid());
my $type = $reservation->type();
$type = $reservation->type();
}
#
......@@ -769,7 +820,8 @@ sub Reserve($)
(defined($start) ? "-s $start " : "") .
(defined($end) ? "-e $end " : "") .
(defined($update) && defined($count) ? "-S $count " : "") .
"-U $uid " . (!defined($update) ? "$pid $count" : "");
($asadmin ? "-U $uid " : "") .
(!defined($update) ? "$pid $count" : "");
# Write the reason to a tempfile to pass in. This will auto unlink.
my $fp;
......@@ -779,9 +831,17 @@ sub Reserve($)
$args = "-N $fp $args";
chmod(0755, "$fp");
}
my $command = ($asadmin ? "$WAP " : "") . "$TB/sbin/reserve -p $args";
print STDERR "$command\n";
GeniUtil::FlipToElabMan();
my $output = GeniUtil::ExecQuiet("$WAP $TB/sbin/reserve -p $args");
if ($asadmin) {
GeniUtil::FlipToElabMan();
}
else {
# We know this is a local user.
$geniuser->FlipTo($project->GetProjectGroup());
}
my $output = GeniUtil::ExecQuiet($command);
if ($? && $? >> 8 != 2) {
GeniUtil::FlipToGeniUser();
......@@ -824,15 +884,18 @@ sub Reservations($)
}
#
# If the root authority is asking, then no perm checks are necessary.
# and we send back the entire list. Otherwise return only those
# reservations granted by the credential, which should be a project
# credential or a user (self) credential.
# If the root authority is asking, then we require a root certificate
# like all the other restricted calls in this library. We send back
# the entire list.
#
# Otherwise return only those reservations granted by the credential,
# which should be a project credential or a user (self) credential.
#
if (!exists($argref->{'credentials'})) {
my $hrn = GeniHRN->new($ENV{"GENIURN"});
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN)
if (! ($hrn->IsAuthority() && $hrn->IsRoot()));
# Root authority check.
$hasperm = CheckPermission(1);
return $hasperm
if (GeniResponse::IsError($hasperm));
$query_result =
DBQueryWarn("select *,UNIX_TIMESTAMP(start) as start, ".
......@@ -952,7 +1015,11 @@ sub DeleteReservation($)
my ($argref) = @_;
my $project;
my $hasperm = CheckPermission(0);
#
# The Portal decides the user has permission and then uses "admin" mode
# to do the deletion.
#
my $hasperm = CheckPermission(1);
return $hasperm
if (GeniResponse::IsError($hasperm));
......@@ -1032,9 +1099,9 @@ sub ReservationPrediction($)
{
my ($argref) = @_;
#my $hasperm = CheckPermission(1);
#return $hasperm
# if (GeniResponse::IsError($hasperm));
my $hasperm = CheckPermission(1);
return $hasperm
if (GeniResponse::IsError($hasperm));
return GeniResponse->MalformedArgsResponse("Missing project list")
if (!(exists($argref->{"projlist"}) && defined($argref->{"projlist"})));
......
......@@ -239,6 +239,7 @@ ProtoGeniDefs::AddModule("cluster",
"DEFVERSION" => "1.0",
"METHODS" => {"1.0" => {
"GetVersion" => \&GeniCluster::GetVersion,
"CreateUser" => \&GeniCluster::CreateUser,
"InUse" => \&GeniCluster::InUse,
"PreReservations" => \&GeniCluster::PreReservations,
"SliceUtilizationData" => \&GeniCluster::SliceUtilizationData,
......
......@@ -44,6 +44,7 @@ use GeniCluster;
$GENI_METHODS = {
"GetVersion" => \&GeniCluster::GetVersion,
"CreateUser" => \&GeniCluster::CreateUser,
"InUse" => \&GeniCluster::InUse,
"PreReservations" => \&GeniCluster::PreReservations,
"SliceUtilizationData" => \&GeniCluster::SliceUtilizationData,
......@@ -52,6 +53,7 @@ $GENI_METHODS = {
"SliceCheckReservation"=> \&GeniCluster::SliceCheckReservation,
"SliceMaxExtension" => \&GeniCluster::SliceMaxExtension,
"Reserve" => \&GeniCluster::Reserve,
"ApproveReservation" => \&GeniCluster::ApproveReservation,
"Reservations" => \&GeniCluster::Reservations,
"DeleteReservation" => \&GeniCluster::DeleteReservation,
"ReservationSystemInfo"=> \&GeniCluster::ReservationSystemInfo,
......
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