Commit 5c63cf86 authored by Leigh B. Stoller's avatar Leigh B. Stoller

Slice expiration changes. The crux of these changes:

1. You cannot unregister a slice at the SA before it has expired. This
   will be annoying at times, but the alphanumeric namespace for slice
   ames is probably big enough for us.

2. To renew a slice, the easiest approach is to call the Renew method
   at the SA, get a new credential for the slice, and then pass that
   to renew on the CMs where you have slivers.

The changes address the problem of slice expiration.  Before this
change, when registering a slice at the Slice Authority, there was no
way to give it an expiration time. The SA just assigns a default
(currently one hour). Then when asking for a ticket at a CM, you can
specify a "valid_until" field in the rspec, which becomes the sliver
expiration time at that CM. You can later (before it expires) "renew"
the sliver, extending the time. Both the sliver and the slice will
expire from the CM at that time.

Further complicating things is that credentials also have an
expiration time in them so that credentials are not valid forever. A
slice credential picks up the expiration time that the SA assigned to
the slice (mentioned in the first paragraph).

A problem is that this arrangement allows you to extend the expiration
of a sliver past the expiration of the slice that is recorded at the
SA. This makes it impossible to expire slice records at the SA since
if we did, and there were outstanding slivers, you could get into a
situation where you would have no ability to access those slivers. (an
admin person can always kill off the sliver).

Remember, the SA cannot know for sure if there are any slivers out
there, especially if they can exist past the expiration of the slice.

The solution:

* Provide a Renew call at the SA to update the slice expiration time.
  Also allow for an expiration time in the Register() call.

  The SA will need to abide by these three rules:
  1. Never issue slice credentials which expire later than the
     corresponding slice
  2. Never allow the slice expiration time to be moved earlier
  3. Never deregister slices before they expire [*].

* Change the CM to not set the expiration of a sliver past the
  expiration of the slice credential; the credential expiration is an
  upper bound on the valid_until field of the rspec. Instead, one must
  first extend the slice at the SA, get a new slice credential, and
  use that to extend the sliver at the CM.

* For consistency with the SA, the CM API will changed so that
  RenewSliver() becomes RenewSlice(), and it will require the
  slice credential.
parent dd4fe2e9
......@@ -558,6 +558,24 @@ sub GetSlice($)
return $slice;
}
#
# The expiration time for an aggregate is when the slice expires.
# The DB field is ignored.
#
sub expires($)
{
my ($self) = @_;
return undef
if (! ref($self));
my $slice = $self->GetSlice();
return undef
if (!defined($slice));
return $slice->expires();
}
#
# Get the creator for the aggregate.
#
......
......@@ -181,6 +181,7 @@ sub Create($$$$)
# accessors
sub field($$) { return ((! ref($_[0])) ? -1 : $_[0]->{'AUTHORITY'}->{$_[1]}); }
sub uuid($) { return field($_[0], "uuid"); }
sub expires($) { return field($_[0], "expires"); }
sub uuid_prefix($) { return field($_[0], "uuid_prefix"); }
sub urn($) { return field($_[0], "urn"); }
sub url($) { return field($_[0], "url"); }
......@@ -358,7 +359,6 @@ sub ListAll($$)
return 0;
}
# _Always_ make sure that this 1 is at the end of the file...
1;
......@@ -144,15 +144,14 @@ sub GetCredential($)
print STDERR "Could not find local authority object\n";
return GeniResponse->Create(GENIRESPONSE_ERROR);
}
my $credential = GeniCredential->Create($authority, $caller_authority);
if (!defined($credential)) {
print STDERR "Could not create credential for $caller_authority\n";
return GeniResponse->Create(GENIRESPONSE_ERROR);
}
if ($credential->Sign($GeniCredential::LOCALMA_FLAG)) {
print STDERR "Could not sign credential for $caller_authority\n";
return GeniResponse->Create(GENIRESPONSE_ERROR);
}
my $credential =
GeniCredential->CreateSigned($authority,
$caller_authority,
$GeniCredential::LOCALMA_FLAG);
return GeniResponse->Create(GENIRESPONSE_ERROR)
if (!defined($credential));
return GeniResponse->Create(GENIRESPONSE_SUCCESS,
$credential->asString());
}
......
......@@ -468,6 +468,9 @@ sub GetTicketAuxAux($$$$$$$$)
#
# For now all tickets expire very quickly (minutes), but once the
# ticket is redeemed, it will expire according to the rspec request.
# If nothing specified in the rspec, then it will expire when the
# slice record expires, which was given by the expiration time of the
# slice credential.
#
if (exists($rspec->{'valid_until'})) {
my $expires = $rspec->{'valid_until'};
......@@ -487,15 +490,22 @@ sub GetTicketAuxAux($$$$$$$$)
# Do we need a policy limit?
#
my $diff = $when - time();
if ($diff < (60 * 5) || $diff > (3600 * 24 * 100)) {
if ($diff < (60 * 5) || $diff > (3600 * 24 * 90)) {
return GeniResponse->Create(GENIRESPONSE_BADARGS, undef,
"valid_until out of range");
}
}
else {
# Give it a reasonable default for later when the ticket is redeemed.
$rspec->{'valid_until'} =
POSIX::strftime("20%y-%m-%dT%H:%M:%S", gmtime(time() + (3600*1)));
#
# Must be before the slice expires.
#
my $slice_expires = $slice->expires();
if (defined($slice_expires)) {
$slice_expires = strptime($slice_expires);
if ($expires > $slice_expires) {
return GeniResponse->Create(GENIRESPONSE_BADARGS, undef,
"valid_until is past slice expiration");
}
}
}
#
......@@ -525,6 +535,14 @@ sub GetTicketAuxAux($$$$$$$$)
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN, undef,
"Slice has been shutdown");
}
# Ditto for expired.
if ($slice->IsExpired()) {
$slice->UnLock();
$ticket->UnLock()
if (defined($ticket) && $ticket->stored());
return GeniResponse->Create(GENIRESPONSE_REFUSED, undef,
"Slice has expired");
}
#
# For now, there can be only a single toplevel aggregate per slice.
......@@ -1449,6 +1467,14 @@ sub SliverWorkAux($$$$$$$)
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN, undef,
"Slice has been shutdown");
}
# Ditto expired slices.
if ($slice->IsExpired()) {
$slice->UnLock();
$ticket->UnLock()
if (defined($ticket));
return GeniResponse->Create(GENIRESPONSE_REFUSED, undef,
"Slice has expired");
}
#
# Create the user.
......@@ -1540,6 +1566,24 @@ sub SliverWorkAux($$$$$$$)
$message = "valid_until out of range";
goto bad;
}
#
# Must be before the slice expires.
#
my $slice_expires = $slice->expires();
if (defined($slice_expires)) {
$slice_expires = strptime($slice_expires);
if ($when > $slice_expires) {
$message = "valid_until is past slice expiration";
goto bad;
}
}
#
# Seems odd, eh? This changes the slice expiration in the DB,
# which was originally the time in the slice credential. The slice
# cannot be extended beyond this point, except by going through
# the RenewSliver() call below.
#
if ($slice->SetExpiration($when) != 0) {
$message = "Could not set expiration time";
goto bad;
......@@ -2250,13 +2294,13 @@ sub SliverWorkAux($$$$$$$)
#
# Renew a sliver
#
sub RenewSliver($)
sub RenewSlice($)
{
my ($argref) = @_;
my $credstr = $argref->{'credential'};
my $expires = $argref->{'valid_until'};
my $expires = $argref->{'valid_until'} || $argref->{'expiration'};
if (! (defined($credstr) && defined($expires))) {
if (! (defined($credstr))) {
return GeniResponse->Create(GENIRESPONSE_BADARGS);
}
my $credential = GeniCredential->CreateFromSigned($credstr);
......@@ -2277,23 +2321,16 @@ sub RenewSliver($)
sub RenewSliverAux($$)
{
my ($credential, $expires) = @_;
my $target_uuid = $credential->target_uuid();
my $slice_uuid = $credential->target_uuid();
my $user_uuid = $credential->owner_uuid();
my $message = "Error renewing aggregate";
my $slice_uuid;
my $when;
$credential->HasPrivilege( "pi" ) or
$credential->HasPrivilege( "control" ) or
return GeniResponse->Create( GENIRESPONSE_FORBIDDEN, undef,
"Insufficient privilege" );
my $aggregate = GeniAggregate->Lookup($target_uuid);
if (defined($aggregate)) {
$slice_uuid = $aggregate->slice_uuid();
}
else {
$slice_uuid = $target_uuid;
}
my $slice = GeniSlice->Lookup($slice_uuid);
if (!defined($slice)) {
return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
......@@ -2307,37 +2344,55 @@ sub RenewSliverAux($$)
$message = "Slice has been shutdown";
goto bad;
}
my $experiment = GeniExperiment($slice);
if (!defined($experiment)) {
$message = "No local experiment for slice";
goto bad;
}
#
# Figure out new expiration time; this is the time at which we can
# idleswap the slice out.
# Figure out new expiration time.
#
if (! ($expires =~ /^[-\w:.\/]+/)) {
$message = "Illegal valid_until in rspec";
my $slice_expires = $credential->expires();
if (!defined($slice_expires)) {
$message = "No expiration time in credential";
goto bad;
}
# Convert to a localtime.
my $when = timegm(strptime($expires));
if (!defined($when)) {
$message = "Could not parse valid_until";
# Convert slice expiration to a time.
my $slice_when = str2time($slice_expires);
if (!defined($slice_when)) {
$message = "Could not parse expiration in credential";
goto bad;
}
#
# Do we need a policy limit?
# If credential is providing the time, we are done. Otherwise have
# to sanity check it.
#
my $diff = $when - time();
if (defined($expires)) {
# Convert to a localtime.
$when = timegm(strptime($expires));
if (!defined($when)) {
$message = "Could not parse expiration";
goto bad;
}
#
# Do we need a policy limit?
#
my $diff = $when - time();
# print STDERR "RenewSliver: $expires, $when, $diff\n";
if ($diff < (60 * 5) || $diff > (3600 * 24 * 100)) {
$message = "valid_until out of range";
if ($diff < (60 * 5) || $diff > (3600 * 24 * 90)) {
$message = "expiration out of range";
goto bad;
}
if ($when > $slice_when) {
$message = "Expiration is greater then slice expiration";
goto bad;
}
}
else {
$when = $slice_when;
}
if ($when < time()) {
$message = "Expiration is in the past";
goto bad;
}
if ($slice->SetExpiration($when) != 0) {
$message = "Could not set expiration time";
goto bad;
......@@ -2701,75 +2756,7 @@ sub DeleteSlice($)
#
sub SplitSliver($)
{
my ($argref) = @_;
my $cred = $argref->{'credential'};
my $impotent = $argref->{'impotent'};
$impotent = 0
if (!defined($impotent));
if (!defined($cred)) {
return GeniResponse->Create(GENIRESPONSE_BADARGS);
}
my $credential = GeniCredential->CreateFromSigned($cred);
if (!defined($credential)) {
return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
"Could not create GeniCredential object");
}
my $sliver_uuid = $credential->target_uuid();
my $user_uuid = $credential->owner_uuid();
#
# 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!");
}
$credential->HasPrivilege( "pi" ) or
$credential->HasPrivilege( "instantiate" ) or
$credential->HasPrivilege( "control" ) or
return GeniResponse->Create( GENIRESPONSE_FORBIDDEN, undef,
"Insufficient privilege" );
my $user = CreateUserFromCertificate($credential->owner_cert());
if (!defined($user)) {
return GeniResponse->Create(GENIRESPONSE_ERROR);
}
my $aggregate = GeniAggregate->Lookup($sliver_uuid);
if (!defined($aggregate)) {
return GeniResponse->Create(GENIRESPONSE_BADARGS, undef,
"No such aggregate $sliver_uuid");
}
my $slice = GeniSlice->Lookup($aggregate->slice_uuid());
if (!defined($slice)) {
return GeniResponse->Create(GENIRESPONSE_BADARGS, undef,
"No such slice on this component");
}
if ($slice->Lock() != 0) {
return GeniResponse->BusyResponse();
}
my @sliver_list = ();
if ($aggregate->SliverList(\@sliver_list) != 0) {
$slice->UnLock();
return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
"Could not get slivers for $aggregate");
}
my @credentials = ();
foreach my $sliver (@sliver_list) {
my $credential = $sliver->NewCredential($user);
if (!defined($credential)) {
$slice->UnLock();
return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
"Could not create credential for $sliver");
}
push(@credentials, $credential->asString());
}
$slice->UnLock();
return GeniResponse->Create(GENIRESPONSE_SUCCESS, \@credentials);
return GeniResponse->Create(GENIRESPONSE_UNSUPPORTED);
}
#
......@@ -3568,9 +3555,7 @@ sub CreateSliceFromCertificate($)
#
# The problem with HRNs is that people will tend to reuse them.
# So check to see if we have a slice with the hrn, and if so
# we need to check with the registry to see if its still active.
# Destroy it locally if not active.
# So check to see if we have a slice with the hrn.
#
my $slice = GeniSlice->Lookup($certificate->hrn());
if (defined($slice)) {
......@@ -3591,6 +3576,11 @@ sub CreateSliceFromCertificate($)
return undef
if (!defined($slice));
# The slice expires when the credential expires. The slice must be
# renewed to extend it.
my $expires = $credential->expires();
$slice->SetExpiration($expires);
return $slice;
}
......
......@@ -805,13 +805,13 @@ sub Shutdown($)
}
#
# Renew a sliver
# Renew a slice
#
sub RenewSliver($)
sub RenewSlice($)
{
my ($argref) = @_;
my $slice_urn = $argref->{'slice_urn'};
my $valid_until = $argref->{'valid_until'};
my $valid_until = $argref->{'valid_until'} || $argref->{'expiration'};
my $credentials = $argref->{'credentials'};
if (! (defined($credentials) &&
......@@ -889,6 +889,10 @@ sub GetTicket($)
return GeniResponse->Create(GENIRESPONSE_REFUSED, undef,
"Must release unredeemed ticket first");
}
if ($slice->IsExpired()) {
return GeniResponse->Create(GENIRESPONSE_REFUSED, undef,
"Slice has expired");
}
}
else {
# Slice does not exist yet.
......@@ -943,6 +947,11 @@ sub UpdateTicket($)
return GeniResponse->Create(GENIRESPONSE_SEARCHFAILED(), undef,
"Slice does not exist here");
}
if ($slice->IsExpired()) {
return GeniResponse->Create(GENIRESPONSE_REFUSED, undef,
"Slice has expired");
}
#
# UpdateTicket applies only to slices that are not active. Must
# use UpdateSliver() for an active sliver.
......@@ -1036,6 +1045,11 @@ sub UpdateSliver($)
"Must release unredeemed ticket first");
}
if ($slice->IsExpired()) {
return GeniResponse->Create(GENIRESPONSE_REFUSED, undef,
"Slice has expired");
}
#
# Any user can update the sliver. The ticket is signed to that user.
#
......
......@@ -110,6 +110,8 @@ sub Stringify($)
# accessors
sub field($$) { return ((! ref($_[0])) ? -1 : $_[0]->{'CERT'}->{$_[1]}); }
sub uuid($) { return field($_[0], "uuid"); }
# This will always be undefined, but we need the method.
sub expires($) { return undef; }
sub created($) { return field($_[0], "created"); }
sub cert($) { return field($_[0], "cert"); }
sub DN($) { return field($_[0], "DN"); }
......
......@@ -160,6 +160,7 @@ sub Create($$;$)
# accessors
sub field($$) { return ((! ref($_[0])) ? -1 : $_[0]->{'COMPONENT'}->{$_[1]}); }
sub uuid($) { return field($_[0], "uuid"); }
sub expires($) { return field($_[0], "expires"); }
sub manager_uuid($) { return field($_[0], "manager_uuid"); }
sub hrn($) { return field($_[0], "hrn"); }
sub url($) { return field($_[0], "url"); }
......
......@@ -25,6 +25,9 @@ use XML::Simple;
use XML::LibXML;
use Data::Dumper;
use File::Temp qw(tempfile);
use Date::Parse;
use POSIX qw(strftime);
use Time::Local;
use overload ('""' => 'Stringify');
# Exported variables
......@@ -100,9 +103,9 @@ sub Lookup($$;$)
}
#
# Create an empty credential object.
# Create an unsigned credential object.
#
sub Create($$$$)
sub Create($$$)
{
my ($class, $target, $owner) = @_;
......@@ -111,6 +114,7 @@ sub Create($$$$)
my $self = {};
$self->{'uuid'} = undef;
$self->{'valid_until'} = $target->expires();
$self->{'target_uuid'} = $target->uuid();
$self->{'owner_uuid'} = $owner->uuid();
# Convenience stuff.
......@@ -128,6 +132,7 @@ sub Create($$$$)
sub field($$) { return ($_[0]->{$_[1]}); }
sub idx($) { return field($_[0], "idx"); }
sub uuid($) { return field($_[0], "uuid"); }
sub expires($) { return field($_[0], "valid_until"); }
sub target_uuid($) { return field($_[0], "target_uuid"); }
sub slice_uuid($) { return field($_[0], "target_uuid"); }
sub owner_uuid($) { return field($_[0], "owner_uuid"); }
......@@ -187,6 +192,33 @@ sub AddExtension($$$)
return 0;
}
#
# Convenience function; create a signed credential for the target,
# issued to the provided user.
#
sub CreateSigned($$$;$)
{
my ($class, $target, $owner, $signer) = @_;
return undef
if (! (ref($target) && ref($owner)));
$signer = $target->GetCertificate()
if (!defined($signer));
my $credential = GeniCredential->Create($target, $owner);
if (!defined($credential)) {
print STDERR "Could not create credential for $target, $owner\n";
return undef;
}
if ($credential->Sign($signer) != 0) {
$credential->Delete();
print STDERR "Could not sign credential for $target, $owner\n";
return undef;
}
return $credential;
}
#
# Create a credential object from a signed credential string.
#
......@@ -261,6 +293,26 @@ sub CreateFromSigned($$;$)
return undef;
}
# Expiration
my ($expires_node) = $doc->getElementsByTagName("expires");
if (!defined($expires_node)) {
print STDERR "Credential is missing expires node\n";
return undef;
}
my $expires = $expires_node->to_literal();
if (! ($expires =~ /^[-\w:.\/]+/)) {
print STDERR "Invalid expires date in credential\n";
return undef;
}
# Convert to a localtime.
my $when = timegm(strptime($expires));
if (!defined($when)) {
print STDERR "Could not parse expires: '$expires'\n";
return undef;
}
$expires = POSIX::strftime("20%y-%m-%dT%H:%M:%S", localtime($when));
# Dig out the target certificate.
my ($cert_node) = $doc->getElementsByTagName("target_gid");
return undef
......@@ -302,6 +354,7 @@ sub CreateFromSigned($$;$)
$self->{'capabilities'} = $capabilities;
$self->{'extensions'} = $extensions;
$self->{'uuid'} = $this_uuid;
$self->{'valid_until'} = $expires;
$self->{'target_uuid'} = $target_certificate->uuid();
$self->{'target_cert'} = $target_certificate;
$self->{'owner_uuid'} = $owner_certificate->uuid();
......@@ -400,11 +453,17 @@ sub Sign($$)
$owner_urn = $self->owner_uuid();
}
# Credential expiration: hard-code to 24 hours from now.
my @expt = gmtime( time() + 24 * 60 * 60 );
my $expiry = sprintf( "%04d-%02d-%02dT%02d:%02d:%02d",
$expt[ 5 ] + 1900, $expt[ 4 ] + 1, $expt[ 3 ],
$expt[ 2 ], $expt[ 1 ], $expt[ 0 ] );
# Credential expiration: hard-code to 24 hours, if the underlying
# object does not define an expiration.
my $expires = $self->expires();
if (!defined($expires)) {
$expires = POSIX::strftime("20%y-%m-%dT%H:%M:%S",
gmtime(time() + 24 * 60 * 60));
}
else {
$expires = POSIX::strftime("20%y-%m-%dT%H:%M:%S",
gmtime(str2time($expires)));
}
#
# Create a template xml file to sign.
......@@ -419,7 +478,7 @@ sub Sign($$)
" <target_gid>$target_cert</target_gid>\n".
" <target_urn>$target_urn</target_urn>\n".
" <uuid>$cred_uuid</uuid>\n".
" <expires>$expiry</expires>\n".
" <expires>$expires</expires>\n".
" $cap_xml\n".
"</credential>\n";
......@@ -499,6 +558,7 @@ sub Store($)
my $this_uuid = $self->target_uuid();
my $owner_uuid = $self->owner_uuid();
my $uuid = $self->uuid();
my $expires = $self->expires();
# Now tack on other stuff we need.
push(@insert_data, "created=now()");
......@@ -510,6 +570,11 @@ sub Store($)
my $safe_credential = DBQuoteSpecial($self->asString());
push(@insert_data, "credential_string=$safe_credential");
if (defined($expires)) {
my $safe_expires = DBQuoteSpecial($expires);
push(@insert_data, "expires=$safe_expires");
}
# Insert into DB.
DBQueryWarn("insert into geni_credentials set " . join(",", @insert_data))
or return -1;
......
......@@ -414,17 +414,15 @@ sub GetTicket($$$$)
#
# Generate a slice credential for the user.
#
my $slice_credential = GeniCredential->Create($slice, $geniuser);
my $slice_credential =
GeniCredential->CreateSigned($slice,
$geniuser,
$GeniCredential::LOCALSA_FLAG);
if (!defined($slice_credential)) {
print STDERR
"*** Could not create a slice credential for $slice/$geniuser!\n";
return -1;
}
if ($slice_credential->Sign($GeniCredential::LOCALSA_FLAG) != 0) {
print STDERR
"*** Could not sign slice credential $slice_credential!\n";
return -1;
}
#
# Load sliver credential if we have it.
......@@ -568,17 +566,15 @@ sub RedeemTicket($$)
#
# Generate a slice credential for the user.
#
my $slice_credential = GeniCredential->Create($slice, $geniuser);