Commit 6aa6275a authored by Leigh B. Stoller's avatar Leigh B. Stoller

A bunch of CRL support.

parent 51563886
......@@ -208,6 +208,18 @@ CREATE TABLE `geni_credentials` (
# private key), say for a sliver.
#
DROP TABLE IF EXISTS `geni_certificates`;
CREATE TABLE `geni_certificates` (
`uuid` varchar(40) NOT NULL default '',
`created` datetime default NULL,
`expires` datetime default NULL,
`revoked` datetime default NULL,
`serial` int(10) unsigned NOT NULL default '0',
`cert` text,
`DN` text,
`privkey` text,
PRIMARY KEY (`uuid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `geni_certificates` (
`uuid` varchar(40) NOT NULL default '',
`created` datetime default NULL,
......@@ -218,6 +230,18 @@ CREATE TABLE `geni_certificates` (
PRIMARY KEY (`uuid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
#
# A clearinghouse table to hold CRLs to be distributed.
#
DROP TABLE IF EXISTS `geni_crls`;
CREATE TABLE `geni_crls` (
`uuid` varchar(40) NOT NULL default '',
`created` datetime default NULL,
`expires` datetime default NULL,
`cert` text,
PRIMARY KEY (`uuid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
#
# A clearinghouse table to hold keys associated with geni users.
#
......
......@@ -103,9 +103,9 @@ sub Stringify($)
{
my ($self) = @_;
my $uuid = $self->uuid();
my $hrn = $self->hrn();
return "[GeniAuthority: $uuid]";
return "[GeniAuthority: $hrn]";
}
#
......
......@@ -710,3 +710,66 @@ sub ListComponents($)
return GeniResponse->Create(GENIRESPONSE_SUCCESS, \@results);
}
#
# Post a new CRL
#
sub PostCRL($)
{
my ($argref) = @_;
my $cred = $argref->{'credential'};
my $cert = $argref->{'cert'};
if (! defined($cred)) {
return GeniResponse->MalformedArgsResponse();
}
if (! defined($cert)) {
return GeniResponse->MalformedArgsResponse();
}
if (! ($cert =~ /^[\012\015\040-\176]*$/)) {
return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
"cert: Invalid characters");
}
my $credential = GeniCredential->CreateFromSigned($cred);
if (!defined($credential)) {
return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
"Could not create GeniCredential object");
}
#
# Make sure the credential was issued to the caller.
#
if ($credential->owner_uuid() ne $ENV{'GENIUUID'}) {
return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
"This is not your credential!");
}
#
# And that the target of the credential is this registry.
#
my $authority = GeniAuthority->Lookup($ENV{'MYUUID'});
if (!defined($authority)) {
print STDERR "Could not find local authority object\n";
return GeniResponse->Create(GENIRESPONSE_ERROR);
}
if ($credential->target_uuid() ne $authority->uuid()) {
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN,
undef, "This is not your registry!");
}
my $caller_authority = GeniAuthority->Lookup($ENV{'GENIUUID'});
if (!defined($caller_authority)) {
print STDERR "Could not find authority object for caller.\n";
return GeniResponse->Create(GENIRESPONSE_ERROR);
}
if (GeniCertificate->StoreCRL($caller_authority, $cert) != 0) {
print STDERR "Could not store CRL for $caller_authority\n";
SENDMAIL($TBOPS, "Failed to Store CRL",
"Fail to store CRL for $caller_authority\n".
"$cert");
return GeniResponse->Create(GENIRESPONSE_ERROR);
}
SENDMAIL($TBOPS, "Stored a new CRL",
"Storeed a new CRL for $caller_authority\n".
"$cert");
return GeniResponse->Create(GENIRESPONSE_SUCCESS);
}
......@@ -1796,6 +1796,8 @@ sub CleanupDeadSlice($)
my $experiment = $slice->GetExperiment();
if (defined($experiment)) {
$experiment->LockDown(0);
my $pid = $experiment->pid();
my $eid = $experiment->eid();
system("$PLABSLICE destroy $pid $eid");
......@@ -1830,7 +1832,10 @@ sub GeniExperiment($)
my $eid = "slice" . TBGetUniqueIndex('next_sliceid', 1);
# Note the -h option; allows experiment with no NS file.
system("$CREATEEXPT -q -i -w -E 'Geni Slice Experiment' ".
system("$CREATEEXPT -q -i -k -w ".
"-S 'Geni Slice Experiment -- DO NOT SWAP OR TERMINATE' ".
"-E 'Geni Slice Experiment -- DO NOT SWAP OR TERMINATE' ".
"-L 'Geni Slice Experiment -- DO NOT SWAP OR TERMINATE' ".
"-h '$uuid' -p GeniSlices -e $eid");
if ($?) {
return undef;
......
......@@ -230,6 +230,9 @@ sub LoadFromString($$)
my $certificate = GeniCertificate->LoadFromFile($filename);
unlink($filename);
return undef
if (!defined($certificate));
$certificate->{'CERT'}->{'certfile'} = undef;
return $certificate;
}
......@@ -390,6 +393,35 @@ sub asText($)
return $text;
}
#
# Load a CRL and store it.
#
sub StoreCRL($$$)
{
my ($class, $authority, $string) = @_;
my ($tempfile, $filename) = tempfile(UNLINK => 1);
print $tempfile $string;
my $uuid = $authority->uuid();
my $expires = `$OPENSSL crl -noout -nextupdate -in $filename`;
chomp($expires);
if (! (defined($expires) && ($expires =~ /^nextupdate/i))) {
print STDERR "Could not get nextupdate from CRL\n";
return -1;
}
$expires =~ s/^nextupdate=//i;
my $safe_cert = DBQuoteSpecial($string);
my $safe_expires = DBQuoteSpecial($expires);
DBQueryWarn("replace into geni_crls set ".
" uuid='$uuid', created=now(), expires=$safe_expires, ".
" cert=$safe_cert")
or return -1;
return 0;
}
############################################################################
#
# Wrapper for local users.
......
......@@ -148,6 +148,8 @@ sub Register($$$$)
{
my ($self, $type, $cert, $info) = @_;
$info = {} if (!defined($info));
my $response =
Genixmlrpc::CallMethod($self->authority(),
$self->context(),
......@@ -241,6 +243,25 @@ sub ListComponents($$$)
return 0;
}
#
# Post the CRL
#
sub PostCRL($$)
{
my ($self, $cert) = @_;
my $response =
Genixmlrpc::CallMethod($self->authority(),
$self->context(),
"PostCRL",
{ "credential" => $self->credential(),
"cert" => $cert });
return -1
if (!defined($response) || $response->code() != GENIRESPONSE_SUCCESS);
return 0;
}
##########################################################################
#
package GeniRegistry::ClearingHouse;
......
......@@ -12,8 +12,9 @@ SUBDIR = protogeni/scripts
include $(OBJDIR)/Makeconf
SBIN_STUFF = cleanupslice
PSBIN_STUFF = register_resources expire_daemon \
createcerts initsite addauthority getcacerts
PSBIN_STUFF = register_resources expire_daemon gencrl postcrl \
createcerts initsite addauthority getcacerts \
gencrlbundle
# These scripts installed setuid, with sudo.
SETUID_BIN_SCRIPTS =
......
......@@ -72,6 +72,7 @@ require GeniTicket;
require GeniSlice;
require GeniCM;
use libtestbed;
use POSIX qw(strftime);
GeniUtil::FlipToGeniUser();
......@@ -183,6 +184,8 @@ sub ExpireSlices()
print STDERR "$slice is idle; releasing.\n";
if (GeniCM::CleanupDeadSlice($slice) != 0) {
print STDERR "Could not release $slice\n";
SENDMAIL($TBOPS, "Could not release slice $slice",
"Could not release slice $slice");
# Leave it locked.
goto skip;
}
......@@ -193,12 +196,23 @@ sub ExpireSlices()
}
}
#
# Setup a signal handler for newsyslog.
#
sub handler()
{
ReOpenLog($LOGFILE);
}
$SIG{HUP} = \&handler
if (!$debug);
while (1) {
print "Running at ".
POSIX::strftime("20%y-%m-%d %H:%M:%S", localtime()) . "\n";
ExpireTickets();
ExpireSlices();
print "Pausing for a while ...\n"
if ($debug);
sleep(30);
}
exit(0);
......
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2008 University of Utah and the Flux Group.
# All rights reserved.
#
use strict;
use English;
use Getopt::Std;
#
# Generate a CRL certificate.
#
sub usage()
{
print "Usage: gencrl [-f]\n";
exit(1);
}
my $optlist = "df";
my $debug = 0;
my $force = 0;
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $TBLOGS = "@TBLOGSEMAIL@";
my $PGENIDOMAIN = "@PROTOGENI_DOMAIN@";
my $PGENISUPPORT = @PROTOGENI_SUPPORT@;
my $TBBASE = "@TBBASE@";
my $SSLDIR = "$TB/lib/ssl";
my $CACONFIG = "$SSLDIR/ca.cnf";
my $EMULAB_CERT = "$TB/etc/emulab.pem";
my $EMULAB_KEY = "$TB/etc/emulab.key";
my $OPENSSL = "/usr/bin/openssl";
my $WORKDIR = "$TB/ssl";
my $CRLPEM = "crl.pem";
my $CRLDAYS = 30;
# Locals
my $crlcreated = 0; # Last update in seconds. 0 effectively forces regeneration.
my $regen = 0; # If anyting has changed or if crl is about to expire.
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin:/usr/site/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
# Protos
sub fatal($);
#
# Turn off line buffering on output
#
$| = 1;
# Load the Testbed support stuff.
use lib "@prefix@/lib";
use libdb;
use libtestbed;
use POSIX qw(strftime);
use Date::Parse;
use Time::Local;
if ($UID != 0) {
fatal("Must be root to run this script\n");
}
#
# Check args.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"f"})) {
$force = 1;
}
if (defined($options{"d"})) {
$debug = 1;
}
#
# CD to the workdir, and then serialize on the lock file since there is
# some shared goop that the ssl tools muck with (serial number, index, etc.).
#
chdir("$WORKDIR") or
fatal("Could not chdir to $WORKDIR: $!");
TBScriptLock("mkusercert") == 0 or
fatal("Could not get the lock!");
#
# If the crl exists and not in force mode, see how old it is. If less
# than 1/2 the distance to expiration, then do not bother to regen if
# there no one has expired since the last time (revoked field in the
# certs table).
#
if (-e $CRLPEM) {
my $lastupdate = `$OPENSSL crl -noout -lastupdate -in $CRLPEM`;
chomp($lastupdate);
if (! (defined($lastupdate) && ($lastupdate =~ /^lastupdate/i))) {
fatal("Could not parse the lastupdate field from CRL file");
}
$lastupdate =~ s/^lastupdate=//i;
$crlcreated = timegm(strptime($lastupdate));
if (!defined($crlcreated)) {
fatal("Could not convert lastupdate field from CRL file to gmtime");
}
$crlcreated = timegm(strptime($lastupdate));
#
# Get expiration time
#
my $expires = `$OPENSSL crl -noout -nextupdate -in $CRLPEM`;
chomp($expires);
if (! (defined($expires) && ($expires =~ /^nextupdate/i))) {
fatal("Could not parse the nextupdate field from CRL file");
}
$expires =~ s/^nextupdate=//i;
$expires = timegm(strptime($expires));
if (!defined($expires)) {
fatal("Could not convert nextupdate field from CRL file to gmtime");
}
my $diff = $expires - $crlcreated;
if ($diff <= 0 ||
$diff < ($CRLDAYS * 3600 * 24) / 2) {
$force = 1;
}
print "$crlcreated, $expires, $diff, $force\n"
if ($debug);
}
#
# Find all revoked certificates. We want to create an index.txt file.
#
my $query_result =
DBQueryWarn("select idx,DN,UNIX_TIMESTAMP(created), ".
" UNIX_TIMESTAMP(revoked) ".
" from user_sslcerts ".
"where encrypted=1 and revoked is not null");
if (!$query_result) {
fatal("Could not get the crl list from the DB");
}
my @list = ();
while (my ($idx,$dn,$created,$revoked) = $query_result->fetchrow_array()) {
chomp($dn);
my $string = "R\t";
$string .= POSIX::strftime("%y%m%d%H%M%SZ", gmtime($created)) . "\t";
$string .= POSIX::strftime("%y%m%d%H%M%SZ", gmtime($revoked)) . "\t";
$string .= sprintf("%08x\t", $idx);
$string .= "unknown\t$dn\n";
push(@list, $string);
if ($debug) {
print STDERR "$created, $revoked\n";
}
# if this was revoked since last CRL, we really do need to regen,
$regen++
if ($revoked >= $crlcreated);
}
if (! ($regen || $force)) {
print STDERR "No reason to regenerate. Exiting ...\n";
# exit value important; tells caller nothing changed.
exit(1);
}
open(CRL, ">crl.txt")
or fatal("Could not create new crl.txt file");
foreach my $string (@list) {
print CRL $string;
}
close(CRL);
# This file needs to exist. Not sure why. Not documented.
if (!-e "crl.txt.attr") {
system("echo 'unique_subject = no' > crl.txt.attr");
}
#
# Generate the CRL certificate.
#
system("$OPENSSL ca -gencrl -name CA_crl -crldays $CRLDAYS -config $CACONFIG ".
" -out $CRLPEM -cert $EMULAB_CERT -keyfile $EMULAB_KEY") == 0
or fatal("Could not sign certificate request");
TBScriptUnlock();
exit(0);
sub fatal($)
{
my ($msg) = @_;
SENDMAIL($TBOPS, "Could not generate CRL", $msg);
TBScriptUnlock();
print STDERR "*** $0:\n".
" $msg\n";
# exit value important.
exit(-1);
}
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2008 University of Utah and the Flux Group.
# All rights reserved.
#
use strict;
use English;
use Getopt::Std;
#
# Generate the CRL bundle and store in the www directly. This is done on
# the clearinghouse only, where all the bundles are kept.
#
sub usage()
{
print "Usage: gencrlbundle\n";
exit(1);
}
my $optlist = "";
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $TBLOGS = "@TBLOGSEMAIL@";
my $PGENIDOMAIN = "@PROTOGENI_DOMAIN@";
my $PGENISUPPORT = @PROTOGENI_SUPPORT@;
my $TBBASE = "@TBBASE@";
my $WWWBUNDLE = "$TB/www/genicrl.bundle";
my $BUNDLE = "$TB/etc/genicrl.bundle";
my $POSTCRL = "$TB/sbin/protogeni/postcrl";
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin:/usr/site/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
# Protos
sub fatal($);
#
# Turn off line buffering on output
#
$| = 1;
if ($UID != 0) {
fatal("Must be root to run this script\n");
}
use vars qw($GENI_DBNAME);
$GENI_DBNAME = "geni-ch";
# Now we can load the libraries after setting the proper DB.
use lib '@prefix@/lib';
use libaudit;
require GeniDB;
import GeniDB;
require GeniCertificate;
require GeniComponent;
require GeniRegistry;
#
# Check args.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
# Record output in case of error.
LogStart(0);
#
# Post our own CRL.
#
system("$POSTCRL") >= 0
or fatal("Could not post our own CRL");
my $query_result =
DBQueryWarn("select cert from geni_crls order by uuid");
open(BUNDLE, ">/tmp/crlbundle.$$")
or fatal("Could not create new CRL bundle file");
while (my ($cert) = $query_result->fetchrow_array()) {
print BUNDLE $cert;
}
close(BUNDLE);
# Don't bother if no change
system("/usr/bin/diff -q $BUNDLE /tmp/crlbundle.$$");
if ($?) {
system("/bin/mv /tmp/crlbundle.$$ $BUNDLE") == 0
or fatal("Could not copy to $BUNDLE!");
system("/usr/local/etc/rc.d/apache.sh restart") == 0
or fatal("Could not restart apache!");
system("/bin/cp $BUNDLE $WWWBUNDLE") == 0
or fatal("Could not copy to $WWWBUNDLE!");
}
# Apache spits out stuff. No errors at this point, nothing to report.
AuditEnd();
exit(0);
sub fatal($)
{
my ($msg) = @_;
die("*** $0:\n".
" $msg\n");
}
......@@ -9,14 +9,15 @@ use English;
use Getopt::Std;
#
# Create the certs for a new protogeni emulab.
# Get and install the CRL
#
sub usage()
{
print "Usage: getcacerts\n";
print "Usage: getcacerts [-l]\n";
exit(1);
}
my $optlist = "d";
my $optlist = "l";
my $nolog = 0;
#
# Configure variables
......@@ -28,6 +29,8 @@ my $PGENIDOMAIN = "@PROTOGENI_DOMAIN@";
my $PGENISUPPORT = @PROTOGENI_SUPPORT@;
my $TBBASE = "@TBBASE@";
my $FETCH = "/usr/bin/fetch";
my $GENCRL = "$TB/sbin/protogeni/gencrl";
my $LOCALCRL = "$TB/ssl/crl.pem";
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin:/usr/site/bin';
......@@ -45,6 +48,7 @@ $| = 1;
use lib "@prefix@/lib";
use libdb;
use libtestbed;
use libaudit;
if ($UID != 0) {
fatal("Must be root to run this script\n");
......@@ -57,10 +61,24 @@ my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"l"})) {
$nolog = 1;
}
my $restartapache = 0;
# Record output in case of error.
LogStart(0)
if (!$nolog);
mkdir("/tmp/genicacerts.$$", 0755) or
fatal("Could not mkdircacerts tmp dir");
#
# Create a new local CRL to concate with whatever get from Utah.
#
system($GENCRL) >= 0
or fatal("Could not generate new CRL!");
#
# Fetch the ca bundle from Utah. These are public keys ...
#
......@@ -68,6 +86,13 @@ system("$FETCH -q -o /tmp/genica.bundle.$$ ".
" http://boss.emulab.net/genica.bundle") == 0
or fatal("Could not fetch genica bundle from Utah");
#
# Fetch the crl bundle from Utah. These are public keys ...
#
system("$FETCH -q -o /tmp/genicrl.bundle.$$ ".
" http://boss.emulab.net/genicrl.bundle") == 0
or fatal("Could not fetch genicrl bundle from Utah");
#
# Split up the certs.
#
......@@ -105,46 +130,76 @@ close(BUNDLE);
# Go no further if the file is exactly the same as last time.
#
system("/usr/bin/diff -q $TB/etc/genica.bundle /tmp/genica.bundle.$$");
if (!$?) {
exit(0);
}
if ($?) {
my $idx = 0;
while (@certs) {
my $cert = pop(@certs);
open(CERT, ">/tmp/genicacerts.$$/$idx.pem")
or fatal("Could not open pem file in /tmp/genicacerts.$$");
print CERT $cert;
close(CERT);
$idx++;
}
my $idx = 0;
while (@certs) {
my $cert = pop(@certs);
if (-e "$TB/etc/genica.bundle.old") {
unlink("$TB/etc/genica.bundle.old") or
fatal("Could not remove old genica bundle");
}
if (-e "$TB/etc/genica.bundle") {
system("/bin/cp $TB/etc/genica.bundle $TB/etc/genica.bundle.old") == 0
or fatal("Could not save off $TB/etc/genica.bundle");
}
if (-e "$TB/etc/genicacerts.old") {
system("/bin/rm -rf $TB/etc/genicacerts.old") == 0
or fatal("Could not remove old genicacerts directory");
}
if (-e "$TB/etc/genicacerts") {
system("/bin/mv $TB/etc/genicacerts $TB/etc/genicacerts.old") == 0
or fatal("Could not save off genicacerts directory");
}
system("/bin/mv /tmp/genica.bundle.$$ $TB/etc/genica.bundle") == 0
or fatal("Could not mv /tmp/genica.bundle.$$ $TB/etc/genica.bundle");
system("/bin/mv /tmp/genicacerts.$$ $TB/etc/genicacerts") == 0
or fatal("Could not mv /tmp/genicacerts.$$ to $TB/etc/genicacerts");
open(CERT, ">/tmp/genicacerts.$$/$idx.pem")
or fatal("Could not open pem file in /tmp/genicacerts.$$");
print CERT $cert;
close(CERT);
$idx++;
$restartapache = 1;
}
if (-e "$TB/etc/genica.bundle.old") {
unlink("$TB/etc/genica.bundle.old") or
fatal("Could not remove old genica bundle");
}
if (-e "$TB/etc/genica.bundle") {
system("/bin/cp $TB/etc/genica.bundle $TB/etc/genica.bundle.old") == 0
or fatal("Could not save off $TB/etc/genica.bundle");
}
if (-e "$TB/etc/genicacerts.old") {
system("/bin/rm -rf $TB/etc/genicacerts.old") == 0
or fatal("Could not remove old genicacerts directory");
#