Commit 6591e9fd authored by Leigh Stoller's avatar Leigh Stoller

Add sitecheckin client and server, which will tell Utah (Mother Ship)

about Emulab sites. Nothing private, just the equivalent of calling
testbed-version so that we know what sites exist and what software
they are running.

This is opt-out; sites that do not want to tell Utah about themselves
can set NOSITECHECKIN in their defs file.

In Utah, there is a new option in the Administration drop down menu to
print out the list from the DB.
parent 12b609d0
......@@ -856,6 +856,7 @@ FIREWALL_OPS
FIREWALL_BOSS_LOCALRULETMPL
FIREWALL_OPS_LOCALRULETMPL
SPEWFROMOPS
NOSITECHECKIN
TBOPSEMAIL
TBOPSEMAIL_NOSLASH
TBROBOCOPSEMAIL
......@@ -5211,6 +5212,7 @@ FIREWALL_OPS=0
FIREWALL_BOSS_LOCALRULETMPL=""
FIREWALL_OPS_LOCALRULETMPL=""
SPEWFROMOPS=0
NOSITECHECKIN=0
#
# XXX You really don't want to change these!
......
......@@ -271,6 +271,7 @@ AC_SUBST(FIREWALL_OPS)
AC_SUBST(FIREWALL_BOSS_LOCALRULETMPL)
AC_SUBST(FIREWALL_OPS_LOCALRULETMPL)
AC_SUBST(SPEWFROMOPS)
AC_SUBST(NOSITECHECKIN)
#
# Offer both versions of the email addresses that have the @ escaped
......@@ -394,6 +395,7 @@ FIREWALL_OPS=0
FIREWALL_BOSS_LOCALRULETMPL=""
FIREWALL_OPS_LOCALRULETMPL=""
SPEWFROMOPS=0
NOSITECHECKIN=0
#
# XXX You really don't want to change these!
......
......@@ -23,7 +23,9 @@ sub Install($$$)
"45 \t1\t*\t*\t*\troot\t$PREFIX/sbin/backup",
"*/5\t*\t*\t*\t*\troot\t$PREFIX/sbin/node_status",
"*/5\t*\t*\t*\t*\troot\t$PREFIX/sbin/prereserve_check",
"*/5\t*\t*\t*\t*\troot\t$PREFIX/sbin/idlemail");
"*/5\t*\t*\t*\t*\troot\t$PREFIX/sbin/idlemail",
"\@weekly\t\t\t\t\troot\t$PREFIX/sbin/sitecheckin_client"
);
};
};
return 0;
......
#
# Add prereserve_check to /etc/crontab
#
use strict;
use libinstall;
use installvars;
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") {
Phase "crontab", "Updating $CRONTAB", sub {
DoneIfEdited($CRONTAB);
BackUpFileFatal($CRONTAB);
AppendToFileFatal($CRONTAB,
"\@weekly\t\t\t\t\troot\t$TBROOT/sbin/sitecheckin_client");
};
}
return 0;
}
1;
# Local Variables:
# mode:perl
# End:
......@@ -702,6 +702,28 @@ CREATE TABLE `emulab_pubs_month_map` (
UNIQUE KEY `display_order` (`display_order`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `emulab_sites`
--
DROP TABLE IF EXISTS `emulab_sites`;
CREATE TABLE `emulab_sites` (
`urn` varchar(128) NOT NULL default '',
`commonname` varchar(64) NOT NULL,
`url` tinytext,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
`buildinfo` datetime NOT NULL,
`commithash` varchar(64) NOT NULL,
`dbrev` tinytext NOT NULL,
`install` tinytext NOT NULL,
`os_version` tinytext NOT NULL,
`perl_version` tinytext NOT NULL,
`tbops` tinytext,
PRIMARY KEY (`urn`),
UNIQUE KEY `commonname` (`commonname`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `errors`
--
......
......@@ -1226,6 +1226,18 @@ REPLACE INTO table_regex VALUES ('virt_blockstore_attributes','vname','text','re
REPLACE INTO table_regex VALUES ('virt_blockstore_attributes','attrkey','text','regex','^[-\\w]+$',1,64,NULL);
REPLACE INTO table_regex VALUES ('virt_blockstore_attributes','attrvalue','text','regex','^[-\\w\\.\\+,\\s\\/:]+$',0,255,NULL);
REPLACE INTO table_regex VALUES ('emulab_sites','certificate','text','regex','^[\\012\\015\\040-\\176]*$',128,4096,NULL);
REPLACE INTO table_regex VALUES ('emulab_sites','url','text','redirect','default:tinytext',0,0,NULL);
REPLACE INTO table_regex VALUES ('emulab_sites','urn','text','regex','^[-_\\w\\.\\/:+]*$',10,255,NULL);
REPLACE INTO table_regex VALUES ('emulab_sites','commonname','text','redirect','default:tinytext',0,0,NULL);
REPLACE INTO table_regex VALUES ('emulab_sites','buildinfo','text','regex','^[-\\w\\/]*$',8,32,NULL);
REPLACE INTO table_regex VALUES ('emulab_sites','commithash','text','regex','^[\\w]*$',32,64,NULL);
REPLACE INTO table_regex VALUES ('emulab_sites','dbrev','float','redirect','default:float',0,0,NULL);
REPLACE INTO table_regex VALUES ('emulab_sites','install','float','redirect','default:float',0,0,NULL);
REPLACE INTO table_regex VALUES ('emulab_sites','os_version','text','redirect','default:tinytext',0,0,NULL);
REPLACE INTO table_regex VALUES ('emulab_sites','perl_version','text','redirect','default:tinytext',0,0,NULL);
REPLACE INTO table_regex VALUES ('emulab_sites','tbops','text','redirect','users:usr_email',0,0,NULL);
REPLACE INTO table_regex VALUES ('default','fulltext','text','regex','^[\\040-\\073\\075\\077-\\176\\012\\015\\011]*$',0,20000,NULL);
REPLACE INTO table_regex VALUES ('default','html_fulltext','text','regex','^[\\040-\\176\\012\\015\\011]*$',0,20000,NULL);
......
#
# Add emulab_sites table.
#
use strict;
use libdb;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
if (!DBTableExists("emulab_sites")) {
DBQueryFatal("CREATE TABLE `emulab_sites` ( ".
" `urn` varchar(128) NOT NULL default '', ".
" `commonname` varchar(64) NOT NULL, ".
" `url` tinytext, ".
" `created` datetime NOT NULL, ".
" `updated` datetime NOT NULL, ".
" `buildinfo` datetime NOT NULL, ".
" `commithash` varchar(64) NOT NULL, ".
" `dbrev` tinytext NOT NULL, ".
" `install` tinytext NOT NULL, ".
" `os_version` tinytext NOT NULL, ".
" `perl_version` tinytext NOT NULL, ".
" `tbops` tinytext, ".
" PRIMARY KEY (`urn`), ".
" UNIQUE KEY `commonname` (`commonname`) ".
") ENGINE=MyISAM DEFAULT CHARSET=latin1");
}
DBQueryFatal("REPLACE INTO table_regex VALUES ".
" ('emulab_sites','certificate','text','regex', ".
" '^[\\\\012\\\\015\\\\040-\\\\176]*\$',128,4096,NULL)");
DBQueryFatal("REPLACE INTO table_regex VALUES ".
" ('emulab_sites','url','text','redirect', ".
" 'default:tinytext',0,0,NULL)");
DBQueryFatal("REPLACE INTO table_regex VALUES ".
" ('emulab_sites','urn','text','regex', ".
" '^[-_\\\\w\\\\.\\\\/:+]*\$',10,255,NULL)");
DBQueryFatal("REPLACE INTO table_regex VALUES ".
" ('emulab_sites','commonname','text','redirect', ".
" 'default:tinytext',0,0,NULL)");
DBQueryFatal("REPLACE INTO table_regex VALUES ".
" ('emulab_sites','buildinfo','text','regex', ".
" '^[-\\\\w\\\\/]*\$',8,32,NULL)");
DBQueryFatal("REPLACE INTO table_regex VALUES ".
" ('emulab_sites','commithash','text','regex', ".
" '^[\\\\w]*\$',32,64,NULL)");
DBQueryFatal("REPLACE INTO table_regex VALUES ".
" ('emulab_sites','dbrev','float','redirect', ".
" 'default:float',0,0,NULL)");
DBQueryFatal("REPLACE INTO table_regex VALUES ".
" ('emulab_sites','install','float','redirect', ".
" 'default:float',0,0,NULL)");
DBQueryFatal("REPLACE INTO table_regex VALUES ".
" ('emulab_sites','os_version','text','redirect', ".
" 'default:tinytext',0,0,NULL)");
DBQueryFatal("REPLACE INTO table_regex VALUES ".
" ('emulab_sites','perl_version','text','redirect', ".
" 'default:tinytext',0,0,NULL)");
DBQueryFatal("REPLACE INTO table_regex VALUES ".
" ('emulab_sites','tbops','text','redirect', ".
" 'users:usr_email',0,0,NULL)");
return 0;
}
1;
# Local Variables:
# mode:perl
# End:
......@@ -49,11 +49,11 @@ SBIN_SCRIPTS = vlandiff vlansync withadminprivs export_tables cvsupd.pl \
addspecialdevice addspecialiface imagehash clone_image \
addvpubaddr imageinfo ctrladdr image_import \
prereserve_check tcppd addexternalnetwork \
update_sitevars delete_image
update_sitevars delete_image sitecheckin sitecheckin_client
WEB_SBIN_SCRIPTS= webnewnode webdeletenode webspewconlog webarchive_list \
webwanodecheckin webspewimage webdumpdescriptor \
webdelete_image
webdelete_image websitecheckin
WEB_BIN_SCRIPTS = webcreate_image websetdest weblinkmon_ctl webspewevents \
webdelay_config
LIBEXEC_SCRIPTS = spewleds webcopy spewsource webcvsweb xlogin webviewvc \
......
#!/usr/bin/perl -w
#
# Copyright (c) 2000-2012 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 strict;
use Getopt::Std;
use XML::Simple;
use Data::Dumper;
use Date::Parse;
use Time::Local;
#
# Add (or update) an emulab site record.
#
sub usage()
{
print("Usage: sitecheckin <xmlfile>\n");
exit(-1);
}
my $optlist = "dn";
my $debug = 0;
my $impotent= 0;
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
#
# Do not run this script as root please.
#
if ($UID == 0) {
die("*** $0:\n".
" Please do not run this as root!\n");
}
#
# Untaint the path
#
$ENV{'PATH'} = "$TB/bin:$TB/sbin:/bin:/usr/bin:/usr/bin:/usr/sbin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# Turn off line buffering on output
#
$| = 1;
#
# Load the Testbed support stuff.
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
use User;
use GeniCertificate;
# Protos
sub fatal($);
#
# 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{"d"})) {
$debug = 1;
}
if (defined($options{"n"})) {
$impotent = 1;
}
usage()
if (@ARGV != 1);
my $xmlfile = shift(@ARGV);
#
# Map invoking user to object.
#
my $this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
#
# Check the filename when invoked from the web interface; must be a
# file in /tmp.
#
if ($xmlfile =~ /^([-\w\.\/]+)$/) {
$xmlfile = $1;
}
else {
fatal("Bad data in pathname: $xmlfile");
}
# Use realpath to resolve any symlinks.
my $translated = `realpath $xmlfile`;
if ($translated =~ /^(\/tmp\/[-\w\.\/]+)$/) {
$xmlfile = $1;
}
else {
fatal("Bad data in translated pathname: $xmlfile");
}
#
# These are the fields that we allow to come in from the XMLfile.
# A couple of others are derived from the certificate.
#
my %required = ("certificate" => "certificate",
"url" => "url",
"buildinfo" => "buildinfo",
"commithash" => "commithash",
"dbrev" => "dbrev",
"install" => "install",
"os_version" => "os_version",
"perl_version" => "perl_version",
"tbops" => "tbops");
#
# Must wrap the parser in eval since it exits on error.
#
my $xmlparse = eval { XMLin($xmlfile,
VarAttr => 'name',
ContentKey => '-content',
SuppressEmpty => undef); };
fatal($@)
if ($@);
print STDERR Dumper($xmlparse)
if ($debug);
#
# Make sure all the required arguments were provided.
#
foreach my $key (keys(%required)) {
fatal("Missing required attribute '$key'")
if (! exists($xmlparse->{'attribute'}->{"$key"}));
}
#
# We build up an array of arguments to pass to User->Create() as we check
# the attributes.
#
my %checkin_args = ();
foreach my $key (keys(%{ $xmlparse->{'attribute'} })) {
my $value = $xmlparse->{'attribute'}->{"$key"}->{'value'};
if (!defined($value)) { # Empty string comes from XML as an undef value.
$xmlparse->{'attribute'}->{"$key"}->{'value'} = $value = "";
}
if ($debug) {
print STDERR "User attribute: '$key' -> '$value'\n";
}
my $dbslot;
# Must be in the allowed lists above, with exceptions handled below
if (exists($required{$key})) {
$dbslot = $required{$key};
next
if (!defined($dbslot));
fatal("Null value for required field $key")
if (!defined($value));
}
else {
fatal("Invalid attribute in XML: '$key' -> '$value'\n");
}
# Now check that the value is legal.
if (! TBcheck_dbslot($value, "emulab_sites", $dbslot,
TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) {
fatal("Illegal data: $key - $value");
}
#
# Do a taint check to avoid warnings, since the values are abviously okay.
#
if ($value =~ /^(.*)$/) {
$value = $1;
}
$checkin_args{$dbslot} = $value;
}
#
# Need to load the certificate. We use the ProtGeni code for this.
#
my $certificate = GeniCertificate->LoadFromString($checkin_args{"certificate"});
if (!defined($certificate)) {
fatal("Could not parse CA certificate");
}
delete($checkin_args{"certificate"});
#
# Now do special checks.
#
my $urn = $certificate->urn();
my $commonname = $certificate->CommonName();
if (!defined($urn) ||
! TBcheck_dbslot($urn, "emulab_sites", "urn",
TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) {
fatal("Illegal URN");
}
if (!defined($commonname) ||
! TBcheck_dbslot($commonname, "emulab_sites", "commonname",
TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) {
fatal("Illegal CN");
}
$checkin_args{"commonname"} = $commonname;
# Convert the buildinfo.
my $buildinfo = str2time($checkin_args{"buildinfo"});
if (!defined($buildinfo)) {
fatal("Could not parse buildinfo");
}
delete($checkin_args{"buildinfo"});
#
# See if this urn already exists. The commonname is not supposed to
# change, so if it does send mail and bail.
#
my $safe_urn = DBQuoteSpecial($urn);
my $safe_cn = DBQuoteSpecial($commonname);
my $query_result =
DBQueryFatal("select urn,commonname from emulab_sites ".
"where urn=$safe_urn or commonname=$safe_cn");
if ($query_result->numrows > 1) {
fatal("Inconsistent site data for $urn,$commonname");
}
my $exists = $query_result->numrows;
my @sets = ();
foreach my $key (keys(%checkin_args)) {
my $val = $checkin_args{$key};
# Treat NULL special.
push (@sets, "${key}=" . ($val eq "NULL" ?
"NULL" : DBQuoteSpecial($val)));
}
my $query = "set " . join(",", @sets) . ", updated=now()";
if ($exists) {
$query = "update emulab_sites $query, buildinfo=FROM_UNIXTIME($buildinfo) ".
"where urn=$safe_urn";
}
else {
$query = "insert into emulab_sites $query, ".
"buildinfo=FROM_UNIXTIME($buildinfo), created=now(), urn=$safe_urn";
}
if ($debug) {
print STDERR "$query\n";
}
if (!$impotent) {
DBQueryFatal($query);
}
if (!$exists || $debug) {
my $vals = "";
foreach my $key (keys(%checkin_args)) {
$vals .= sprintf("%-15s %s\n", "${key}:", $checkin_args{$key});
}
SENDMAIL($TBOPS, "Emulab site checkin",
"Emulab site has checked in.\n\n".
sprintf("%-15s %s\n", "URN:", $urn).
$vals, $TBOPS);
}
exit(0);
sub fatal($) {
my ($mesg) = $_[0];
die("*** $0:\n".
" $mesg\n");
}
#!/usr/bin/perl -w
#
# Copyright (c) 2000-2012 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 strict;
use Getopt::Std;
use Data::Dumper;
use URI::Escape;
use File::Temp qw(tempfile);
#
# Phone home back to the mother ship and tell it about Emulab sites.
# Nothing sensitive is sent, just stuff like what version is running,
# commit hash of the code, URL, etc. You can opt out with a setting
# in your defs file.
#
# NOSITECHECKIN=1
#
sub usage()
{
print("Usage: sitecheckin_client\n");
exit(-1);
}
my $optlist = "dn";
my $debug = 0;
my $impotent= 0;
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $MAINSITE = @TBMAINSITE@;
my $ELABINELAB = @ELABINELAB@;
my $NOCHECKIN = @NOSITECHECKIN@;
my $TBBASE = "@TBBASE@";
my $MOTHERSHIP = "https://www.emulab.net/dev/stoller/sitecheckin.php";
my $EMULAB_CERT = "$TB/etc/emulab.pem";
my $OPENSSL = "/usr/bin/openssl";
my $WGET = "/usr/local/bin/wget";
#
# Do not run this script as root please.
#
if ($EUID) {
die("*** $0:\n".
" Only root can run this script!\n");
}
exit(0)
if ($NOCHECKIN || $ELABINELAB);
#
# Untaint the path
#
$ENV{'PATH'} = "$TB/bin:$TB/sbin:/bin:/usr/bin:/usr/bin:/usr/sbin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# Turn off line buffering on output
#
$| = 1;
#
# Load the Testbed support stuff.
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
use emutil;
# Protos
sub fatal($);
#
# 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{"d"})) {
$debug = 1;
}
if (defined($options{"n"})) {
$impotent = 1;
}
usage()
if (@ARGV);
my $osvers = `uname -s -r`;
chomp($osvers);
#
# These are the fields that we have to put into the xml file we send.
#
my %xmlfields = ("certificate" => undef,
"url" => $TBBASE,
"buildinfo" => undef,
"commithash" => undef,
"dbrev" => undef,
"install" => undef,
"os_version" => $osvers,
"perl_version" => $],
"tbops" => $TBOPS);
my $query_result =
DBQueryFatal("select * from version_info order by name");
while (my ($name, $value) = $query_result->fetchrow_array()) {
$xmlfields{$name} = $value
if (exists($xmlfields{$name}));
}
#
# Use openssl to cut any cruft.
#
my $certificate = emutil::ExecQuiet("$OPENSSL x509 -in $EMULAB_CERT");
if ($?) {
fatal("Could not load CA certificate from $EMULAB_CERT");
}
$xmlfields{"certificate"} = $certificate;
#
# Create the XML file to send.
#
my ($fh, $xmlfilename) = tempfile(UNLINK => !$debug);
fatal("Could not create temporary file")
if (!defined($fh));
my $xmlstuff = "<emulab_site>";
foreach my $key (keys(%xmlfields)) {
my $val = $xmlfields{$key};
$xmlstuff .= "<attribute name=\"$key\">";
$xmlstuff .= "<value>$val</value>";
$xmlstuff .= "</attribute>";
}
$xmlstuff .= "</emulab_site>";
print $fh "xmlstuff=" . uri_escape($xmlstuff) . "\n";
close($fh);
if ($debug) {
system("/bin/cat $xmlfilename");
}
system("$WGET --post-file=$xmlfilename --no-check-certificate ".
" -O /dev/null -nv -q $MOTHERSHIP");
unlink($xmlfilename);
exit(0);
sub fatal($) {
my ($mesg) = $_[0];
die("*** $0:\n".
" $mesg\n");
}
......@@ -744,6 +744,11 @@ function WRITESIDEBAR() {
NavMenuButton("Edit Site Variables",
"$TBBASE/editsitevars.php3");
if ($TBMAINSITE) {
NavMenuButton("Emulab Site List",
"$TBBASE/showsite_list.php");
}
NavMenuButton("Show Shared Node Pool",
"$TBBASE/showpool.php");
......
<?php
#
# Copyright (c) 2000-2012 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/>.
#
# }}}
#
include("defs.php3");
#
# Only known and logged in users can do this.
#
$this_user = CheckLoginOrDie();
$uid = $this_user->uid();
$isadmin = ISADMIN();
#
# Verify page arguments
#
$optargs = OptionalPageArguments("sortby", PAGEARG_STRING);
#
# Standard Testbed Header
#
PAGEHEADER("Emulab Site List");