Commit 410b56e0 authored by Leigh B Stoller's avatar Leigh B Stoller

Add shellinabox support for Quick VM ssh in the browser.

parent d8831438
......@@ -35,7 +35,8 @@ ISCLEARINGHOUSE = @PROTOGENI_ISCLEARINGHOUSE@
include $(OBJDIR)/Makeconf
SBIN_STUFF = cleanupslice gencabundle cleanupticket
SBIN_STUFF = cleanupslice gencabundle cleanupticket aptssh-setup
PSBIN_STUFF = register_resources expire_daemon gencrl postcrl \
addauthority getcacerts \
gencrlbundle shutdownslice remauthority listusage \
......@@ -53,7 +54,7 @@ endif
# These scripts installed setuid, with sudo.
SETUID_BIN_SCRIPTS =
SETUID_SBIN_SCRIPTS = cleanupslice gencabundle cleanupticket
SETUID_SBIN_SCRIPTS = cleanupslice gencabundle cleanupticket aptssh-setup
SETUID_LIBX_SCRIPTS =
#
......@@ -61,7 +62,8 @@ SETUID_LIBX_SCRIPTS =
# configure if the .in file is changed.
#
all: $(SBIN_STUFF) $(PSBIN_STUFF) \
initsite resolve resolvenode resolve-ch getversion genspeaksfor
initsite resolve resolvenode resolve-ch getversion genspeaksfor \
shellinabox.pl
include $(TESTBED_SRCDIR)/GNUmakerules
......@@ -72,6 +74,9 @@ install: $(addprefix $(INSTALL_SBINDIR)/, $(SBIN_STUFF)) \
$(INSTALL_LIBEXECDIR)/webmaptoslice
-rm -f $(INSTALL_SBINDIR)/protogeni/cleanupticket
apt-install: $(addprefix $(INSTALL_DIR)/opsdir/cgi-bin/, shellinabox.pl) \
$(addprefix $(INSTALL_SBINDIR)/, aptssh-setup)
control-install:
clean:
......@@ -82,5 +87,11 @@ $(INSTALL_SBINDIR)/protogeni/%: %
-mkdir -p $(INSTALL_SBINDIR)/protogeni
$(INSTALL) $< $@
$(INSTALL_DIR)/opsdir/cgi-bin/shellinabox.pl: shellinabox.pl
echo "Installing (link to wrapper) $<"
mkdir -p $(INSTALL_DIR)/opsdir/cgi-bin
-rm -f $@
ln -s $(INSTALL_LIBEXECDIR)/runsuid $@
echo "Installing (real script) $<"
-mkdir -p $(INSTALL_DIR)/opsdir/suidbin
$(SUDO) $(INSTALL_PROGRAM) $< $(INSTALL_DIR)/opsdir/suidbin/$<
#!/usr/bin/perl -wT
#
# Copyright (c) 2000-2013 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
# This file is part of the Emulab network testbed software.
#
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
#
# }}}
#
use English;
use Getopt::Std;
#
# Setup GateOne stuff for in browser SSH client.
#
sub usage()
{
print "Usage: gateone-setup [-d] ...\n";
print "Options:\n";
print " -d Turn on debugging\n";
print " -r Force a regenerate of initial key for user\n";
exit(-1);
}
my $optlist = "dr";
my $debug = 0;
my $regen = 0;
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $OURDOMAIN = "@OURDOMAIN@";
my $CONTROL = "@USERNODE@";
my $PROTOGENI = @PROTOGENI_SUPPORT@;
my $KEYGEN = "/usr/bin/ssh-keygen";
my $APTDIR = "/var/apt/users";
my $USERSAPTDIR = "$TB/usersvar/apt/users";
my $SSH = "$TB/bin/sshtb";
my $SAVEUID = $UID;
# Locals
my $user;
#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use GeniDB;
use GeniUser;
# Connect to the SA DB.
GeniDB::DBConnect(GeniDB::GENISA_DBNAME());
#
# Function prototypes
#
sub fatal($);
#
# Turn off line buffering on output
#
$| = 1;
#
# Untaint the path
#
$ENV{'PATH'} = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# We don't want to run this script unless its the real version.
#
if ($EUID != 0) {
die("*** $0:\n".
" Must be setuid! Maybe its a development version?\n");
}
#
# Please do not run it as root. Hard to track what has happened.
#
if ($UID == 0) {
die("*** $0:\n".
" Please do not run this as root!\n");
}
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
%options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"r"})) {
$regen = 1;
}
usage()
if (@ARGV != 1);
my $geniuser = GeniUser->Lookup($ARGV[0], 0);
if (!defined($geniuser)) {
fatal("No such Geni user!");
}
my $uid = $geniuser->uid();
my $dir = "$APTDIR/$uid";
my $udir = "$USERSAPTDIR/$uid";
$UID = 0;
if (! -e "$udir") {
system("$SSH -host $CONTROL '/bin/mkdir -p -m 0750 $dir'")
== 0 or fatal("Could not mkdir $dir: $!");
}
system("$SSH -host $CONTROL ".
" 'rm -f $dir/id_rsa; $KEYGEN -q -t rsa -P \"\" ".
" -C \"${uid}" . "\@" . ${OURDOMAIN} . "\" ".
" -f $dir/id_rsa'")
== 0 or fatal("Failure in ssh-keygen!");
system("$SSH -host $CONTROL '/usr/sbin/chown -R nobody:nobody $dir'")
== 0 or fatal("Could not mkdir $dir: $!");
system("$SSH -host $CONTROL '/bin/chmod -R 700 $dir'")
== 0 or fatal("Could not mkdir $dir: $!");
system("/usr/bin/fsync $udir");
system("/usr/bin/fsync $udir/id_rsa.pub");
sleep(5);
$UID = $SAVEUID;
# Grab a copy for the DB.
my $pubkey = `cat $udir/id_rsa.pub`;
if ($?) {
fatal("Could not read new key from file");
}
chomp($pubkey);
# Only one.
$geniuser->DeleteInternalKeys();
$geniuser->AddInternalKey($pubkey) == 0
or fatal("Could not add new pub key to the database!");
exit(0);
sub fatal($) {
my($mesg) = $_[0];
die("*** $0:\n".
" $mesg\n");
}
......@@ -69,7 +69,7 @@ my $PGENIDOMAIN = "@PROTOGENI_DOMAIN@";
my $SACERT = "$TB/etc/genisa.pem";
my $CMCERT = "$TB/etc/genicm.pem";
my $SSHKEYGEN = "/usr/bin/ssh-keygen";
my $GATEONESETUP = "$TB/sbin/gateone-setup";
my $SSHSETUP = "$TB/sbin/aptssh-setup";
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin:/usr/site/bin';
......@@ -354,13 +354,11 @@ if (!defined($geniuser)) {
$geniuser->SetAuthToken($auth_token);
#
# Setup GateOne browser ssh.
# Setup ssh browser ssh.
#
if (0) {
system("$GATEONESETUP -g " . $geniuser->uuid());
system("$SSHSETUP " . $geniuser->uuid());
fatal("Could not create ssh key pair")
if ($?);
}
}
my $user_uuid = $geniuser->uuid();
# So we know this user has dome something lately.
......@@ -653,7 +651,9 @@ sub Terminate($)
[$slice_credential->asString(),
$speaksfor_credential->asString()]});
if (!defined($response) || $response->code() != GENIRESPONSE_SUCCESS) {
if (!defined($response) ||
($response->code() != GENIRESPONSE_SUCCESS &&
$response->code() != GENIRESPONSE_SEARCHFAILED)) {
fatal("DeleteSlice failed: ".
(defined($response) ? $response->output() : "") . "\n");
}
......
#!/usr/bin/perl -w
#
# Copyright (c) 2008-2013 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
# GENI Public License
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and/or hardware specification (the "Work") to
# deal in the Work without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Work, and to permit persons to whom the Work
# is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Work.
#
# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS
# IN THE WORK.
#
# }}}
#
#
# Simple CGI interface to shellinabox ...
#
use strict;
use English;
use Data::Dumper;
use CGI;
use JSON;
use Digest::HMAC_SHA1 qw(hmac_sha1 hmac_sha1_hex);
use Sys::Syslog;
use IO::Handle;
# 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);
}
}
# Configure variables
my $TB = "@prefix@";
my $MAINSITE = @TBMAINSITE@;
my $TBOPS = "@TBOPSEMAIL@";
my $TBLOGS = "@TBLOGSEMAIL@";
my $TBBASE = "@TBBASE@";
my $TBLOGFACIL = "@TBLOGFACIL@";
my $CERTFILE = "/usr/local/etc/apache22/ssl.crt/users.emulab.net.crt";
my $KEYFILE = "/usr/local/etc/apache22/ssl.key/users.emulab.net.key";
my $APTDIR = "/var/apt/users";
# Testbed libraries.
use lib '@prefix@/lib';
#
# 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'};
# Locals
my $debug = 0;
# Watch for apache restart; must disconnect and continue.
my $disconnected = 0;
#
# Only apache ...
#
if (getpwuid($UID) ne "nobody") {
printf STDERR "You are not allowed to run this script!\n";
exit(1);
}
sub info($)
{
my ($msg) = @_;
if ($debug) {
print STDERR "$msg\n";
}
else {
syslog("info", $msg);
}
}
sub fatal($)
{
my ($msg) = @_;
info($msg);
exit(1);
}
if ($debug) {
open(STDERR, "> /tmp/foo.log");
}
else {
# Set up syslog
openlog("shellinabox", "pid", $TBLOGFACIL);
}
# The query holds the authentication object.
my $query = new CGI();
my $authstuff = $query->param('auth');
if (!defined($authstuff)) {
fatal("No auth object provided");
}
#
# We need the shared key to recreate the SHA1 signature.
#
open(KEY, "/usr/testbed/etc/sshauth.key") or
fatal("Could not open sshauth.key");
my $sshauthkey = <KEY>;
chomp($sshauthkey);
#
# Dig out the authentication object. It is a json object.
#
my $auth = eval { decode_json($authstuff); };
if ($@) {
fatal("Could not decode auth object");
}
if ($debug) {
print STDERR Dumper($auth);
}
else {
syslog("info", $auth->{'uid'} . "," . $auth->{'nodeid'});
}
#
# Recreate the signature and compare.
#
my $sigtocheck =
$auth->{'uid'} . $auth->{'stuff'} . $auth->{'nodeid'} . $auth->{'timestamp'};
my $signature = hmac_sha1_hex($sigtocheck, $sshauthkey);
if ($signature ne $auth->{'signature'}) {
fatal("Bad signature: $signature");
}
my $uid = $auth->{'uid'};
my $nodeid = $auth->{'nodeid'};
my $port = "";
# Silly taint check stuff.
if ($uid =~ /^([-\w]*)$/) {
$uid = $1;
}
# Watch for port number in nodeid.
if ($nodeid =~ /^([-\.\w]*)$/) {
$nodeid = $1;
}
elsif ($nodeid =~ /^([-\.\w]*):(\d*)$/) {
$nodeid = $1;
$port = "-p $2";
}
my $who = "${uid}\@${nodeid}";
my $where = "HOME";
my $sshopts = "";
# shellinabox wants the gid to be the default for the user.
my (undef,undef,$gid) = getpwnam($uid);
# No gid, see if a phony user.
if (!defined($gid)) {
if (-e "$APTDIR/$uid") {
$sshopts = "-i $APTDIR/$uid/id_rsa ";
$sshopts .= "-q -o BatchMode=yes -o StrictHostKeyChecking=no ";
$sshopts .= "-o UserKnownHostsFile=${APTDIR}/$uid/known_hosts";
$where = "$APTDIR/$uid";
# Switch to nobody for below.
$uid = "nobody";
$gid = "nobody";
}
else {
fatal("$uid is not in the passwd file or $APTDIR");
}
}
# Silly taint check stuff.
if ($gid =~ /^([-\w]*)$/) {
$gid = $1;
}
# This is so shellinabox will not complain.
$UID = $EUID;
# Shove this header out so that we can do cross site xmlrpc.
print "Access-Control-Allow-Origin: $TBBASE\n";
my $cmd = "shellinaboxd " . ($debug ? "-d" : "-v") . " " .
"--certfile=${CERTFILE} --keyfile=${KEYFILE} ".
"--cgi -c $TB/etc -s '/:$uid:$gid:$where:/usr/bin/ssh $port $sshopts $who'";
info($cmd);
#
# The point of this is to capture the initial STDERR of shellinaboxd
# and send it out to syslog or file.
#
pipe(READER, WRITER);
WRITER->autoflush(1);
my $pid = fork();
if ($pid) {
close WRITER;
close STDOUT;
close STDIN;
while (<READER>) {
info($_);
}
info("Before waitpid");
waitpid($pid, 0);
info("After waitpid: $?");
exit($? >> 0);
}
else {
# Need the parent to run first so it can close STDOUT. Better
# ways to do this of course.
select(undef, undef, undef, 0.2);
close READER;
close STDERR;
open(STDERR, ">&WRITER") || fatal("can't dup to stderr");
close WRITER;
}
exec($cmd);
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