Commit 6591e9fd authored by Leigh B Stoller's avatar Leigh B 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.
#