Commit 57f53f06 authored by Leigh B Stoller's avatar Leigh B Stoller

Merge the 5 different protogeni xmlrpc scripts into a single wrapper

that loads one of five different modules, based on the PATH_INFO.  The
modules do nothing but pull in the proper library; all of the xmlrpc
and authorization code is in one place now (the wrapper), instead of
being duplicated. Much cleaner, much safer.

An update to httpd.conf is required, and done with update script 7.
parent 028b917c
......@@ -1344,13 +1344,8 @@ SSLVerifyDepth 10
SSLRequire ( %{SSL_CLIENT_S_DN_OU} ne "sslxmlrpc" )
</Location>
ScriptAlias /protogeni/xmlrpc/ch @prefix@/protogeni/xmlrpc/protogeni-ch.pl
ScriptAlias /protogeni/xmlrpc/cm @prefix@/protogeni/xmlrpc/protogeni-cm.pl
ScriptAlias /protogeni/xmlrpc/sa @prefix@/protogeni/xmlrpc/protogeni-sa.pl
ScriptAlias /protogeni/xmlrpc/ses @prefix@/protogeni/xmlrpc/protogeni-ses.pl
<IfDefine GENI_AM>
ScriptAlias /protogeni/xmlrpc/am @prefix@/protogeni/xmlrpc/geni-am.pl
</IfDefine>
ScriptAlias /protogeni/xmlrpc @prefix@/protogeni/xmlrpc/protogeni-wrapper.pl
<Directory "@prefix@/www/protogeni">
SSLRequireSSL
Order deny,allow
......
#
# Note that all actions *MUST* be idempotent; if this script is run
# again, it should always do the right thing, not break if something
# was already done. See boss-install for lots of example of how to use
# libinstall to help with this.
#
use strict;
use libinstall;
my $APACHECONF = "/usr/local/etc/apache/httpd.conf";
sub InstallUpdate($$)
{
my ($version, $phase) = @_;
#
# If something should run in the pre-install phase.
#
if ($phase eq "pre") {
}
#
# If something should run in the post-install phase.
#
if ($phase eq "post") {
if ($PGENISUPPORT) {
Phase "httpd.conf", "Updating apache config file", sub {
#
# This might be a rare case.
#
DoneIfIdentical("$TOP_OBJDIR/apache/httpd.conf", $APACHECONF);
#
# Check to see if SSLVerifyClient has been set to optional
#
PhaseSkip("Already updated")
if `grep ' protogeni-wrapper.pl\$' $APACHECONF`;
BackUpFileFatal($APACHECONF);
# For impotent mode.
DiffFiles("$TOP_OBJDIR/apache/httpd.conf", $APACHECONF);
ExecQuietFatal("$GMAKE -C $TOP_OBJDIR/apache install");
};
Phase "httpd", "Restarting apache", sub {
DoneIfDoesntExist("$VARRUN/httpd.pid");
ExecQuietFatal("$RCDIR/apache.sh restart");
};
}
}
return 0;
}
1;
......@@ -20,31 +20,26 @@ SETUID_LIBX_SCRIPTS =
# Force dependencies on the scripts so that they will be rerun through
# configure if the .in file is changed.
#
all: protogeni-cm.pl protogeni-ch.pl protogeni-sa.pl protogeni-ses.pl \
Genixmlrpc.pm GeniResponse.pm geni-am.pl
all: Genixmlrpc.pm GeniResponse.pm \
protogeni-ch.pm protogeni-sa.pm protogeni-cm.pm \
protogeni-ses.pm geni-am.pm \
protogeni-wrapper.pl
include $(TESTBED_SRCDIR)/GNUmakerules
install-libs: $(INSTALL_LIBDIR)/Genixmlrpc.pm \
$(INSTALL_LIBDIR)/GeniResponse.pm \
$(INSTALL_LIBDIR)/protogeni-ch.pm \
$(INSTALL_LIBDIR)/protogeni-sa.pm \
$(INSTALL_LIBDIR)/protogeni-cm.pm \
$(INSTALL_LIBDIR)/protogeni-ses.pm \
$(INSTALL_LIBDIR)/geni-am.pm \
$(INSTALL_DIR)/opsdir/lib/Genixmlrpc.pm \
$(INSTALL_DIR)/opsdir/lib/GeniResponse.pm
install-scripts: $(INSTALL_DIR)/protogeni/xmlrpc/protogeni-sa.pl \
$(INSTALL_DIR)/protogeni/xmlrpc/protogeni-cm.pl \
$(INSTALL_DIR)/protogeni/xmlrpc/protogeni-ch.pl \
$(INSTALL_DIR)/protogeni/xmlrpc/protogeni-ses.pl \
$(INSTALL_DIR)/protogeni/xmlrpc/geni-am.pl
$(SUDO) chown root $(INSTALL_DIR)/protogeni/xmlrpc/protogeni-sa.pl
$(SUDO) chmod u+s $(INSTALL_DIR)/protogeni/xmlrpc/protogeni-sa.pl
$(SUDO) chown root $(INSTALL_DIR)/protogeni/xmlrpc/protogeni-ch.pl
$(SUDO) chmod u+s $(INSTALL_DIR)/protogeni/xmlrpc/protogeni-ch.pl
$(SUDO) chown root $(INSTALL_DIR)/protogeni/xmlrpc/protogeni-cm.pl
$(SUDO) chmod u+s $(INSTALL_DIR)/protogeni/xmlrpc/protogeni-cm.pl
$(SUDO) chown root $(INSTALL_DIR)/protogeni/xmlrpc/protogeni-ses.pl
$(SUDO) chmod u+s $(INSTALL_DIR)/protogeni/xmlrpc/protogeni-ses.pl
$(SUDO) chown root $(INSTALL_DIR)/protogeni/xmlrpc/geni-am.pl
$(SUDO) chmod u+s $(INSTALL_DIR)/protogeni/xmlrpc/geni-am.pl
install-scripts: $(INSTALL_DIR)/protogeni/xmlrpc/protogeni-wrapper.pl
$(SUDO) chown root $(INSTALL_DIR)/protogeni/xmlrpc/protogeni-wrapper.pl
$(SUDO) chmod u+s $(INSTALL_DIR)/protogeni/xmlrpc/protogeni-wrapper.pl
install: install-libs install-scripts
......
#!/usr/bin/perl -w
#
# GENIPUBLIC-COPYRIGHT
# Copyright (c) 2008-2010 University of Utah and the Flux Group.
# All rights reserved.
#
#
# Simple CGI interface to the GENI xmlrpc interface. This script is invoked
# from the web server. The certificate information is in the environment
# set up by apache.
#
use strict;
use English;
use Frontier::Responder;
use Frontier::RPC2;
use Data::Dumper;
use POSIX;
use Crypt::X509;
use Crypt::OpenSSL::X509;
# Yack. apache does not close fds before the exec, and if this dies
# we are left with a giant mess.
BEGIN {
no warnings;
for (my $i = 3; $i < 1024; $i++) {
POSIX:close($i);
}
}
# Do this early so that we talk to the right DB.
use vars qw($GENI_DBNAME);
BEGIN { $GENI_DBNAME = "geni-cm"; }
# Configure variables
my $EMULAB_PEMFILE = "@prefix@/etc/genicm.pem";
my $MAINSITE = @TBMAINSITE@;
my $VERSION = "1.0";
# Testbed libraries.
use lib '@prefix@/lib';
use GeniAM;
use Genixmlrpc;
use GeniResponse;
use libaudit;
# Geniuser.
my $user = "geniuser";
my $group = "GeniSlices";
# Need a command line option.
my $debug = 0;
# Determined by version.
my $responder;
#
# Turn off line buffering on output
#
$| = 1;
#
# Untaint the path
#
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# So we know who/what we are acting as.
#
my $certificate = GeniCertificate->LoadFromFile($EMULAB_PEMFILE);
if (!defined($certificate)) {
die("*** $0:\n".
" Could not get uuid from $EMULAB_PEMFILE\n");
}
$ENV{'MYUUID'} = $certificate->uuid();
# The URN could also come from the certificate, and that might be preferable
# in some ways (if anybody is doing something silly like authenticating
# with somebody else's certificate). But that would require everybody to
# upgrade to URNs in their certificates, so we can't assume it yet.
$ENV{'MYURN'} = "urn:publicid:IDN+@OURDOMAIN@+authority+cm";
#
# Helper function to return a properly formated XML error.
#
sub XMLError($$)
{
my ($code, $string) = @_;
my $decoder = Frontier::RPC2->new();
print "Content-Type: text/xml \n\n";
print $decoder->encode_fault($code, $string);
exit(0);
}
#
# Make sure the client presented a valid certificate that apache says
# is okay.
#
# THIS HAS TO BE HERE! Why? Cause recent security patches disable SSL
# renegotiation, which is needed when a subdir turns on ssl client
# verification (as httpd.conf used to). Now, we set it to "optional",
# which avoids the renegotiation problem, but we have to make that
# this interface is always invoked by a client supplying a verifiable
# certificate.
#
if (! (exists($ENV{'SSL_CLIENT_VERIFY'}) &&
$ENV{'SSL_CLIENT_VERIFY'} eq "SUCCESS")) {
XMLError(-1, "Invalid or missing certificate");
}
#
# In the prototype, we accept certificate signed by trusted roots
# (CA certs we have locally cached). This script runs as "geniuser"
# so that there is an emulab user context, or many of the scripts we
# invoke will complain and croak.
#
my $unix_uid = getpwnam("$user") or
die("*** $0:\n".
" No such user $user\n");
my $unix_gid = getgrnam("$group") or
die("*** $0:\n".
" No such group $group\n");
# Flip to user and never go back
$GID = $unix_gid;
$EGID = "$unix_gid $unix_gid";
$EUID = $UID = $unix_uid;
$ENV{'USER'} = $user;
$ENV{'LOGNAME'} = $user;
#
# The UUID of the client certificate is in the env var SSL_CLIENT_S_DN_CN.
# If it actually looks like a UUID, then this correponds to an actual user,
# and the supplied credentials/tickets must match. At present, if there is
# no UUID, it is another emulab making a request directly, with no user
# context, and we just let that pass for now.
#
if (exists($ENV{'SSL_CLIENT_S_DN_CN'}) &&
$ENV{'SSL_CLIENT_S_DN_CN'} =~ /^\w+\-\w+\-\w+\-\w+\-\w+$/) {
$ENV{'GENIUSER'} = $ENV{'SSL_CLIENT_S_DN_CN'};
$ENV{'GENIUUID'} = $ENV{'SSL_CLIENT_S_DN_CN'};
}
# Ignore the lack of UUID, they are going away.
#else {
# XMLError(-1, "Invalid certificate; no UUID");
#}
# Furthermore, disable UUID checks in GeniCredential
use GeniCredential;
$GeniCredential::CHECK_UUID = 0;
#
# The CERT data from apache holds the URN of the caller.
#
if (exists($ENV{'SSL_CLIENT_CERT'})) {
my $x509 = eval {
Crypt::OpenSSL::X509->new_from_string($ENV{'SSL_CLIENT_CERT'}); };
if ($@) {
XMLError(-1, "Invalid certificate: $@");
}
my $cert = $x509->as_string(Crypt::OpenSSL::X509::FORMAT_ASN1);
XMLError(-1, "Could not convert certificate to ASN1")
if (!defined($cert) || $cert eq '');
my $decoded = Crypt::X509->new( cert => $cert );
if ($decoded->error) {
XMLError(-1, "Error decoding certificate:" . $decoded->error);
}
foreach my $tmp (@{ $decoded->SubjectAltName }) {
if ($tmp =~ /^uniformResourceIdentifier=(urn:publicid:.*)$/ ||
$tmp =~ /^(urn:publicid:.*)$/) {
$ENV{'GENIURN'} = $1;
}
}
}
XMLError(-1, "Invalid authentication certificate; no URN. Please regenerate.")
if (!exists($ENV{'GENIURN'}));
#
# Reaching into the Frontier code so I can debug this crap.
#
my $request = Frontier::Responder::get_cgi_request();
if (!defined($request)) {
print "Content-Type: text/txt\n\n";
exit(0);
}
if (exists($ENV{'PATH_INFO'}) && $ENV{'PATH_INFO'} ne "") {
my $pathinfo = $ENV{'PATH_INFO'};
$pathinfo =~ s/^\///;
my @parts = split(/\//, $pathinfo);
if (@parts) {
my $v = $parts[0];
if ($v =~ /^[\d\.]+$/) {
$VERSION = "$v";
}
}
}
#
# Create and set our RPC context for any calls we end up making.
#
Genixmlrpc->SetContext(Genixmlrpc->Context($certificate));
if ($VERSION eq "1.0") {
$responder = Frontier::Responder->new( "methods" => {
"GetVersion" => \&GeniAM::GetVersion,
"ListResources" => \&GeniAM::ListResources,
"CreateSliver" => \&GeniAM::CreateSliver,
"DeleteSliver" => \&GeniAM::DeleteSliver,
"SliverStatus" => \&GeniAM::SliverStatus,
"RenewSliver" => \&GeniAM::RenewSliver,
"Shutdown" => \&GeniAM::Shutdown,
},);
}
else {
XMLError(-3, "Invalid API Version");
}
#
# Use libaudit to capture any output from libraries and programs.
# Send that to tbops so they can be fixed.
#
if ($MAINSITE) {
LogStart(0);
AddAuditInfo("to", "protogeni-errors\@flux.utah.edu")
}
else {
LogStart(0, undef, LIBAUDIT_LOGTBOPS());
}
# Add stuff for log message if sent.
AddAuditInfo("message", $request);
# CC errors to Utah for now.
AddAuditInfo("cc", "protogeni-errors\@flux.utah.edu")
if (!$MAINSITE);
my $response = $responder->{'_decode'}->serve($request,
$responder->{'methods'});
# Add stuff for log message if sent.
AddAuditInfo("message", $response . "\n\n" . $request);
#----------------------------------------------------------------------
# Morph the ProtoGENI response (a hash with three keys; a
# GeniResponse) into a GENI AM response (a single value or a fault if
# there is an error).
#
# $response is an XML RPC response, which is a three element hash. The
# value element is the GeniResponse hash.
#----------------------------------------------------------------------
my $decoder = Frontier::RPC2->new();
my $object = $decoder->decode($response);
my $geni_response = $object->{'value'}[0];
if (GeniResponse::IsError($geni_response)) {
# An error result gets mapped to an XML RPC fault
$response = $decoder->encode_fault(GeniResponse::code($geni_response),
GeniResponse::output($geni_response));
} else {
# A successful result means return the value
$response = $decoder->encode_response(GeniResponse::value($geni_response));
}
#
# Terminate the log capture so that we can print the response to STDOUT
# for the web server.
#
LogEnd(0);
print "Content-Type: text/xml \n\n" . $response;
exit(0);
#
# Want to prevent bad exit.
#
END {
my $exitcode = $?;
if ($exitcode) {
LogEnd(0);
my $decoder = Frontier::RPC2->new();
print "Content-Type: text/xml \n\n";
print $decoder->encode_fault(-2, "XMLRPC Server Error");
# Since we converted to a normal error and sent the log message.
$? = 0;
}
}
#!/usr/bin/perl -w
#
# GENIPUBLIC-COPYRIGHT
# Copyright (c) 2008-2010 University of Utah and the Flux Group.
# All rights reserved.
#
use strict;
use English;
# Do this early so that we talk to the right DB.
use vars qw($GENI_DBNAME $GENI_METHODS $EMULAB_PEMFILE);
BEGIN { $GENI_DBNAME = "geni"; }
# Configure variables
my $ETCDIR = "@prefix@/etc";
$EMULAB_PEMFILE = "$ETCDIR/genisa.pem";
# Testbed libraries.
use lib '@prefix@/lib';
use GeniAM;
if (!defined($GENI_VERSION) || $GENI_VERSION eq "1.0") {
$GENI_METHODS = {
"GetVersion" => \&GeniAM::GetVersion,
"ListResources" => \&GeniAM::ListResources,
"CreateSliver" => \&GeniAM::CreateSliver,
"DeleteSliver" => \&GeniAM::DeleteSliver,
"SliverStatus" => \&GeniAM::SliverStatus,
"RenewSliver" => \&GeniAM::RenewSliver,
"Shutdown" => \&GeniAM::Shutdown,
};
}
1;
#!/usr/bin/perl -w
#
# GENIPUBLIC-COPYRIGHT
# Copyright (c) 2008-2010 University of Utah and the Flux Group.
# All rights reserved.
#
#
# Simple CGI interface to the GENI xmlrpc interface. This script is invoked
# from the web server. The certificate information is in the environment
# set up by apache.
#
use strict;
use English;
use Frontier::Responder;
use Data::Dumper;
use POSIX;
use Crypt::X509;
use Crypt::OpenSSL::X509;
# Yack. apache does not close fds before the exec, and if this dies
# we are left with a giant mess.
BEGIN {
no warnings;
for (my $i = 3; $i < 2048; $i++) {
POSIX:close($i);
}
}
# Do this early so that we talk to the right DB.
use vars qw($GENI_DBNAME $GENI_ISCLRHOUSE $GENI_CHPEMFILE);
BEGIN { $GENI_DBNAME = "geni-ch"; $GENI_ISCLRHOUSE = 1; }
# Configure variables
my $ETCDIR = "@prefix@/etc";
my $EMULAB_PEMFILE = ((-s "$ETCDIR/genich-local.pem") ?
"$ETCDIR/genich-local.pem" : "$ETCDIR/genich.pem");
# See GeniCredential; a helpful debugging aid.
$GENI_CHPEMFILE = $EMULAB_PEMFILE;
# Testbed libraries.
use lib '@prefix@/lib';
use GeniCH;
use GeniCertificate;
use GeniResponse;
use libaudit;
# Geniuser.
my $user = "geniuser";
my $group = "GeniSlices";
#
# Turn off line buffering on output
#
$| = 1;
#
# Untaint the path
#
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# So we know who/what we are acting as.
#
my $certificate = GeniCertificate->LoadFromFile($EMULAB_PEMFILE);
if (!defined($certificate)) {
die("*** $0:\n".
" Could not get uuid from $EMULAB_PEMFILE\n");
}
$ENV{'MYUUID'} = $certificate->uuid();
# The URN could also come from the certificate, and that might be preferable
# in some ways (if anybody is doing something silly like authenticating
# with somebody else's certificate). But that would require everybody to
# upgrade to URNs in their certificates, so we can't assume it yet.
$ENV{'MYURN'} = "urn:publicid:IDN+@OURDOMAIN@+authority+ch";
#
# Helper function to return a properly formated XML error.
#
sub XMLError($$)
{
my ($code, $string) = @_;
my $decoder = Frontier::RPC2->new();
print "Content-Type: text/xml \n\n";
print $decoder->encode_fault($code, $string);
exit(0);
}
#
# Make sure the client presented a valid certificate that apache says
# is okay.
#
# THIS HAS TO BE HERE! Why? Cause recent security patches disable SSL
# renegotiation, which is needed when a subdir turns on ssl client
# verification (as httpd.conf used to). Now, we set it to "optional",
# which avoids the renegotiation problem, but we have to make that
# this interface is always invoked by a client supplying a verifiable
# certificate.
#
if (! (exists($ENV{'SSL_CLIENT_VERIFY'}) &&
$ENV{'SSL_CLIENT_VERIFY'} eq "SUCCESS")) {
XMLError(-1, "Invalid or missing certificate");
}
#
# In the prototype, we accept certificate signed by trusted roots
# (CA certs we have locally cached). This script runs as "geniuser"
# so that there is an emulab user context, or many of the scripts we
# invoke will complain and croak.
#
my $unix_uid = getpwnam("$user") or
die("*** $0:\n".
" No such user $user\n");
my $unix_gid = getgrnam("$group") or
die("*** $0:\n".
" No such group $group\n");
# Flip to user and never go back
$GID = $unix_gid;
$EGID = "$unix_gid $unix_gid";
$EUID = $UID = $unix_uid;
$ENV{'USER'} = $user;
$ENV{'LOGNAME'} = $user;
#
# The UUID of the client certificate is in the env var SSL_CLIENT_S_DN_CN.
# If it actually looks like a UUID, then this correponds to an actual user,
# and the supplied credentials/tickets must match. At present, if there is
# no UUID, it is another emulab making a request directly, with no user
# context, and we just let that pass for now.
#
if (exists($ENV{'SSL_CLIENT_S_DN_CN'}) &&
$ENV{'SSL_CLIENT_S_DN_CN'} =~ /^\w+\-\w+\-\w+\-\w+\-\w+$/) {
$ENV{'GENIUSER'} = $ENV{'SSL_CLIENT_S_DN_CN'};
$ENV{'GENIUUID'} = $ENV{'SSL_CLIENT_S_DN_CN'};
}
else {
XMLError(-1, "Invalid certificate; no UUID");
}
#
# The CERT data from apache holds the URN of the caller.
#
if (exists($ENV{'SSL_CLIENT_CERT'})) {
my $x509 = eval {
Crypt::OpenSSL::X509->new_from_string($ENV{'SSL_CLIENT_CERT'}); };
if ($@) {
XMLError(-1, "Invalid certificate: $@");
}
my $cert = $x509->as_string(Crypt::OpenSSL::X509::FORMAT_ASN1);
XMLError(-1, "Could not convert certificate to ASN1")
if (!defined($cert) || $cert eq '');
my $decoded = Crypt::X509->new( cert => $cert );
if ($decoded->error) {
XMLError(-1, "Error decoding certificate:" . $decoded->error);
}
foreach my $tmp (@{ $decoded->SubjectAltName }) {
if ($tmp =~ /^uniformResourceIdentifier=(.*)$/ ||
$tmp =~ /^(urn:.*)$/) {
$ENV{'GENIURN'} = $1;
}
}
}
XMLError(-1, "Invalid authentication certificate; no URN. Please regenerate.")
if (!exists($ENV{'GENIURN'}));
#
# Reaching into the Frontier code so I can debug this crap.
#
my $request = Frontier::Responder::get_cgi_request();
if (!defined($request)) {
print "Content-Type: text/txt\n\n";
exit(0);
}
#
# Use libaudit to capture any output from libraries and programs.