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

The bulk of Emulab Portal Support! See this wiki page for a high level

view of what Portal support does:

https://users.emulab.net/trac/emulab/wiki/Portal

Notes:

* New DB tables to store the list of peer emulabs, and exports info
  for users and projects.

* Backend script to manage user and projects exports.

* Cross site login from the portal; users do not login into the peer
  sites directly.

* New portal_daemon to run on the portal, to handle exports and
  updates.
parent 7b62628f
# #
# EMULAB-COPYRIGHT # EMULAB-COPYRIGHT
# Copyright (c) 2000-2009 University of Utah and the Flux Group. # Copyright (c) 2000-2011 University of Utah and the Flux Group.
# All rights reserved. # All rights reserved.
# #
...@@ -13,9 +13,10 @@ UNIFIED = @UNIFIED_BOSS_AND_OPS@ ...@@ -13,9 +13,10 @@ UNIFIED = @UNIFIED_BOSS_AND_OPS@
include $(OBJDIR)/Makeconf include $(OBJDIR)/Makeconf
SBIN_STUFF = tbacct addsfskey addpubkey mkusercert quotamail genpubkeys \ SBIN_STUFF = tbacct addsfskey addpubkey mkusercert quotamail genpubkeys \
newuser newproj mksyscert spewcert newuser newproj mksyscert spewcert dumpuser dumpproject \
manageremote
LIBEXEC_STUFF = webtbacct webaddsfskey webaddpubkey webmkusercert \ LIBEXEC_STUFF = webtbacct webaddsfskey webaddpubkey webmkusercert \
webnewuser webnewproj webspewcert webnewuser webnewproj webspewcert webmanageremote
CTRLSBIN_STUFF = adduserhook CTRLSBIN_STUFF = adduserhook
# These scripts installed setuid, with sudo. # These scripts installed setuid, with sudo.
...@@ -23,6 +24,11 @@ SETUID_BIN_SCRIPTS = ...@@ -23,6 +24,11 @@ SETUID_BIN_SCRIPTS =
SETUID_SBIN_SCRIPTS = tbacct addpubkey mkusercert mksyscert SETUID_SBIN_SCRIPTS = tbacct addpubkey mkusercert mksyscert
SETUID_LIBX_SCRIPTS = SETUID_LIBX_SCRIPTS =
ifeq ($(PROTOGENI_SUPPORT),1)
SBIN_STUFF += manageremote
SETUID_SBIN_SCRIPTS += manageremote
endif
# #
# Targets # Targets
# #
......
#!/usr/bin/perl -w
#
# GENIPUBLIC-COPYRIGHT
# Copyright (c) 2010-2011 University of Utah and the Flux Group.
# All rights reserved.
#
use strict;
use English;
use Getopt::Std;
use Data::Dumper;
use CGI;
#
#
#
sub usage()
{
print "Usage: manageremote version <remote>\n";
print " manageremote addpeer <remote> <urn> <url> [is_primary]\n";
print " manageremote adduser <remote> <uid>\n";
print " manageremote deluser <remote> <uid>\n";
print " manageremote moduser <remote> <uid>\n";
print " manageremote setgroups <remote> <uid>\n";
print " manageremote xlogin <remote> <uid>\n";
print " manageremote addproject <remote> <pid>\n";
print " manageremote addgroup <remote> <gid>\n";
exit(1);
}
my $optlist = "dnf";
my $debug = 0;
my $force = 0;
my $impotent = 0;
#
# Function prototypes
#
sub Version();
sub AddUser(;$);
sub AddPeer();
sub DeleteUser();
sub ModifyUser();
sub SetGroups();
sub CrossLogin();
sub AddProject();
sub AddGroup();
sub fatal($);
sub do_method($$;$);
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $TBLOGS = "@TBLOGSEMAIL@";
my $PGENISUPPORT = @PROTOGENI_SUPPORT@;
my $PORTAL_ENABLE = @PORTAL_ENABLE@;
my $PORTAL_PRIMARY= @PORTAL_ISPRIMARY@;
my $OURDOMAIN = "@OURDOMAIN@";
my $DUMPUSER = "$TB/sbin/dumpuser";
my $DUMPPROJ = "$TB/sbin/dumpproject";
my $SACERT = "$TB/etc/genisa.pem";
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin:/usr/site/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# Turn off line buffering on output
#
$| = 1;
use lib '@prefix@/lib';
use emdb;
use libtestbed;
use User;
use Project;
use Group;
use emutil;
use GeniHRN;
use Genixmlrpc;
use GeniResponse;
use GeniCredential;
use GeniAuthority;
#
# Check args.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"f"})) {
$force = 1;
}
if (defined($options{"n"})) {
$impotent = 1;
}
usage()
if (@ARGV < 2 || @ARGV > 4);
my $cmd = shift(@ARGV);
my $peername = shift(@ARGV);
my $peerurn;
if (! $PORTAL_ENABLE) {
fatal("Portal mode is not enabled");
}
if (! ($PORTAL_PRIMARY || $cmd eq "addpeer")) {
fatal("You can only run addpeer on this boss");
}
#
# Map invoking user to object.
#
my $this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
#
# Mere users can call only one function.
#
if (!$this_user->IsAdmin() && $cmd ne "xlogin") {
fatal("You must be a TB administrator to run this script!");
}
#
# Load the SA cert to act as caller context.
#
my $certificate = GeniCertificate->LoadFromFile($SACERT);
if (!defined($certificate)) {
fatal("Could not load certificate from $SACERT\n");
}
my $context = Genixmlrpc->Context($certificate);
if (!defined($context)) {
fatal("Could not create context to talk to clearinghouse");
}
Genixmlrpc->SetContext($context);
my $me = GeniAuthority->Lookup($certificate->uuid());
if (!defined($me)) {
fatal("Could not find my own authority object");
}
my $credential = GeniCredential->GetSelfCredential($me);
if (!defined($credential)) {
fatal("Could not create self credential for $me");
}
#
# All operations other then AddPeer require that the peer be
# in the DB.
#
if ($cmd eq "addpeer") {
AddPeer();
exit(0);
}
else {
my $query_result =
DBQueryFatal("select name,urn from emulab_peers ".
"where name='$peername' or urn='$peername'");
fatal("Unknown peer")
if (!$query_result->numrows);
($peername,$peerurn) = $query_result->fetchrow_array();
}
my $authority = GeniAuthority->CreateFromRegistry("sa", $peerurn);
if (!defined($authority)) {
fatal("Could not locate authority for $peername");
}
#
# Now dispatch operation.
#
SWITCH: for ($cmd) {
/^version$/ && do {
Version();
last SWITCH;
};
/^adduser$/ && do {
AddUser();
last SWITCH;
};
/^deluser$/ && do {
DeleteUser();
last SWITCH;
};
/^moduser$/ && do {
ModifyUser();
last SWITCH;
};
/^setgroups$/ && do {
SetGroups();
last SWITCH;
};
/^xlogin$/ && do {
CrossLogin();
last SWITCH;
};
/^addproject$/ && do {
AddProject();
last SWITCH;
};
/^addgroup$/ && do {
AddGroup();
last SWITCH;
};
# Default
usage();
}
exit(0);
#
# Get the version.
#
sub Version()
{
my $response = do_method($authority, "GetVersion");
fatal("Could not get version from $authority")
if (! (defined($response) &&
$response->code() == GENIRESPONSE_SUCCESS));
my $version = $response->value();
print "Version: $version\n";
return 0;
}
#
# Add a peer to the list
#
sub AddPeer()
{
usage()
if (@ARGV < 2);
my $urn = shift(@ARGV);
my $url = shift(@ARGV);
my $primary = (@ARGV ? shift(@ARGV) : 0);
my $safe_url = DBQuoteSpecial($url);
$primary = ($primary ? 1 : 0);
fatal("Invalid URN")
if (!GeniHRN::IsValid($urn));
fatal("Invalid peer name")
if (! ($peername =~ /^[-\w]*$/));
my $authority = GeniAuthority->CreateFromRegistry("sa", $urn);
if (!defined($authority)) {
fatal("Could not locate authority for $peername");
}
my $query_result =
DBQueryFatal("select * from emulab_peers ".
"where name='$peername' or urn='$urn' or ".
" weburl=$safe_url");
fatal("Peer already exists. Please delete first")
if ($query_result->numrows);
fatal("Could not add new peer")
if (!DBQueryWarn("insert into emulab_peers set ".
" name='$peername', urn='$urn', weburl=$safe_url, ".
" is_primary='$primary'"));
return 0;
}
#
# Add a user.
#
sub AddUser(;$)
{
my ($token) = @_;
if (! defined($token)) {
usage()
if (! @ARGV);
$token = $ARGV[0];
}
my $user = User->Lookup($token);
if (!defined($user)) {
fatal("No such user");
}
my $uid_idx = $user->uid_idx();
my $uid = $user->uid();
# Check for existing export.
my $query_result =
DBQueryFatal("select * from user_exports ".
"where uid_idx='$uid_idx' and peer='$peername'");
if ($query_result->numrows && !$force) {
fatal("User already exported to peer. Use -f option");
}
my $urn = GeniHRN::Generate($OURDOMAIN, "user", $user->uid());
my $xmlgoo = emutil::ExecQuiet("$DUMPUSER -p $uid");
if ($?) {
fatal("$DUMPUSER failed");
}
my $args = {"xmlstring" => $xmlgoo,
"urn" => $urn};
my $response = do_method($authority, "AddUser", $args);
fatal("Could not add user to $authority")
if (! (defined($response) &&
($response->code() == GENIRESPONSE_SUCCESS ||
$response->code() == GENIRESPONSE_ALREADYEXISTS)));
DBQueryFatal("replace into user_exports set ".
" uid='$uid', uid_idx='$uid_idx', peer='$peername', ".
" exported=now()");
return 0;
}
#
# Delete a user.
#
sub DeleteUser()
{
usage()
if (! @ARGV);
my $user = User->Lookup($ARGV[0]);
if (!defined($user)) {
fatal("No such user");
}
my $uid = $user->uid();
my $uid_idx = $user->uid_idx();
my $query_result =
DBQueryFatal("select * from user_exports ".
"where uid_idx='$uid_idx' and peer='$peername'");
if (!$query_result->numrows && !$force) {
fatal("User has not been exported to peer. Use -f option");
}
my $urn = GeniHRN::Generate($OURDOMAIN, "user", $user->uid());
my $args = {"urn" => $urn};
my $response = do_method($authority, "DeleteUser", $args);
fatal("Could not delete user from $authority")
if (! (defined($response) &&
($response->code() == GENIRESPONSE_SUCCESS ||
$response->code() == GENIRESPONSE_SEARCHFAILED)));
DBQueryFatal("delete from user_exports ".
"where uid_idx='$uid_idx' and peer='$peername'");
return 0;
}
#
# Modify a user.
#
sub ModifyUser()
{
usage()
if (! @ARGV);
my $user = User->Lookup($ARGV[0]);
if (!defined($user)) {
fatal("No such user");
}
my $uid = $user->uid();
my $uid_idx = $user->uid_idx();
my $query_result =
DBQueryFatal("select * from user_exports ".
"where uid_idx='$uid_idx' and peer='$peername'");
if (!$query_result->numrows && !$force) {
fatal("User has not been exported to peer. Use -f option");
}
my $urn = GeniHRN::Generate($OURDOMAIN, "user", $user->uid());
my $xmlgoo = emutil::ExecQuiet("$DUMPUSER -p $uid");
if ($?) {
fatal("$DUMPUSER failed");
}
my $args = {"xmlstring" => $xmlgoo,
"urn" => $urn};
my $response = do_method($authority, "ModifyUser", $args);
fatal("Could not modify user at $authority")
if (! (defined($response) &&
$response->code() == GENIRESPONSE_SUCCESS));
return 0;
}
#
# Set the groups for a user.
#
sub SetGroups(;$)
{
my ($token) = @_;
if (! defined($token)) {
usage()
if (! @ARGV);
$token = $ARGV[0];
}
my $user = User->Lookup($token);
if (!defined($user)) {
fatal("No such user");
}
my $uid = $user->uid();
my $uid_idx = $user->uid_idx();
my $query_result =
DBQueryFatal("select * from user_exports ".
"where uid_idx='$uid_idx' and peer='$peername'");
if (!$query_result->numrows && !$force) {
fatal("User has not been exported to peer. Use -f option");
}
my $urn = GeniHRN::Generate($OURDOMAIN, "user", $user->uid());
my @grouplist = ();
if ($user->GroupMembershipList(\@grouplist)) {
fatal("Could not get group list for user");
}
if (! @grouplist) {
print STDERR "$user is not a member of any groups";
return 0;
}
my %grouparray = ();
foreach my $group (@grouplist) {
my $pid_idx = $group->pid_idx();
my $gid_idx = $group->gid_idx();
#
# See if this group has been exported. Skip if not.
#
$query_result =
DBQueryFatal("select pid_idx,gid_idx from group_exports ".
"where pid_idx='$pid_idx' and gid_idx='$gid_idx' and ".
" peer='$peername'");
next
if (!$query_result->numrows);
my $membership = $group->LookupUser($user);
if (!defined($membership)) {
fatal("Could not get membership for $user in $group");
}
my $pid = $group->pid();
my $gid = $group->gid();
my $trust = $membership->trust();
$grouparray{"$pid,$gid"} = $trust;
}
print STDERR Dumper(\%grouparray) if ($debug);
my $args = {"groups" => \%grouparray,
"urn" => $urn};
my $response = do_method($authority, "SetGroups", $args);
fatal("Could not setgroups for user at $authority")
if (! (defined($response) &&
$response->code() == GENIRESPONSE_SUCCESS));
return 0;
}
#
# Cross Login
#
sub CrossLogin()
{
usage()
if (! @ARGV);
my $user = User->Lookup($ARGV[0]);
if (!defined($user)) {
fatal("No such user");
}
my $uid = $user->uid();
my $uid_idx = $user->uid_idx();
my $query_result =
DBQueryFatal("select * from user_exports ".
"where uid_idx='$uid_idx' and peer='$peername'");
if (!$query_result->numrows && !$force) {
fatal("User has not been exported to peer. Use -f option");
}
my $urn = GeniHRN::Generate($OURDOMAIN, "user", $user->uid());
my $args = {"urn" => $urn};
#
# Since this is coming from the web interface, want to limit
# how long we wait, and return status if timed out.
#
Genixmlrpc->SetTimeout(20);
my $response = do_method($authority, "CrossLogin", $args);
fatal("Could not xlogin user at $authority")
if (! (defined($response) &&
$response->code() == GENIRESPONSE_SUCCESS));
my $key = $response->value();
print "$key\n";
return 0;
}
#
# Add a Project
#
sub AddProject()
{
usage()
if (! @ARGV);
my $project = Project->Lookup($ARGV[0]);
if (!defined($project)) {
fatal("No such project");
}
my $pid = $project->pid();
my $pid_idx = $project->pid_idx();
my $leader_uid = $project->head_uid();
my $leader_idx = $project->head_idx();
my $query_result =
DBQueryFatal("select * from group_exports ".
"where pid_idx='$pid_idx' and gid_idx='$pid_idx' and ".
" peer='$peername'");
if ($query_result->numrows && !$force) {
fatal("Project has already been exported to peer. Use -f option");
}
#
# Check that the leader has been exported, and if not do that first.
#
my $leader_result =
DBQueryFatal("select * from user_exports ".
"where uid_idx='$leader_idx' and peer='$peername'");
if (!$leader_result->numrows) {
AddUser($leader_idx);
}
my $xmlgoo = emutil::ExecQuiet("$DUMPPROJ $pid");
if ($?) {
fatal("$DUMPPROJ failed");
}
my $args = {"xmlstring" => $xmlgoo};
my $response = do_method($authority, "AddProject", $args);
fatal("Could not add project to $authority")
if (! (defined($response) &&
($response->code() == GENIRESPONSE_SUCCESS ||
$response->code() == GENIRESPONSE_ALREADYEXISTS)));
DBQueryFatal("replace into group_exports set ".
" pid='$pid', pid_idx='$pid_idx', ".
" gid='$pid', gid_idx='$pid_idx', ".
" exported=now(), updated=now(), ".
" peer='$peername'");
if (!$leader_result->numrows) {
SetGroups($leader_idx);
}
return 0;
}
#
# Add a Group
#
sub AddGroup()
{
usage()
if (! @ARGV);
my $group = Group->Lookup($ARGV[0]);
if (!defined($group)) {
fatal("No such group");
}
if ($group->IsProjectGroup()) {
fatal("Please use addproject instead.");
}
my $pid_idx = $group->pid_idx();
my $gid_idx = $group->gid_idx();
my $pid = $group->pid();
my $gid = $group->gid();
my $query_result =
DBQueryFatal("select * from group_exports ".
"where pid_idx='$pid_idx' and gid_idx='$pid_idx' and ".
" peer='$peername'");
if (!$query_result->numrows) {
fatal("Project has not been exported to peer.\n");
}
$query_result =
DBQueryFatal("select * from group_exports ".
"where pid_idx='$pid_idx' and gid_idx='$gid_idx' and ".
" peer='$peername'");
if ($query_result->numrows && !$force) {
fatal("Group has already been exported to peer. Use -f option");
}
my %tags = (
"project" => $group->pid(),
"group_id" => $group->gid(),
"group_leader" => $group->leader(),
"group_description" => $group->description() || "",
);
my $args = {"tags" => \%tags};
my $response = do_method($authority, "AddGroup", $args);
fatal("Could not add group to $authority")
if (! (defined($response) &&
($response->code() == GENIRESPONSE_SUCCESS ||
$response->code() == GENIRESPONSE_ALREADYEXISTS)));
DBQueryFatal("replace into group_exports set ".