All new accounts created on Gitlab now require administrator approval. If you invite any collaborators, please let Flux staff know so they can approve the accounts.

Commit 8d53b3fd authored by Leigh B Stoller's avatar Leigh B Stoller

Implement speaksfor (non-abac) support.

CM V2 (and thus the AM) now accept a type=speaksfor credential along
with regular credentials. When supplied, the speaksfor caller must be
equal to the owner of the speaksfor credential and the target must be
equal to the owner of the regular credential(s). All operations take
place in the context of the spokenfor user.

Added speaksfor slots to geni_slices,geni_aggregates and geni_tickets.
Also to the history table. But these are just the most recent data.
Each transaction is logged as normal, and the metadata now includes
the speaksfor data and the log always includes all of the credentials.

For testing, there is a new script in the scripts directory to
generate a speaksfor credential. Not installed since it is really
a hack. But to create one:

  perl genspeaksfor urn:publicid:IDN+emulab.net+user+leebee \
	urn:publicid:IDN+emulab.net+user+stoller

which generates a speaksfor credential that says stoller is speaking
for leebee.

Given a slice credential issued to leebee, the test scripts can be
invoked as follows (by stoller):

  createsliver.py -S speaksfor.cred -s slice.cred -c leebee.cred

A copy of leebee's self credential is needed simply cause of the test
script's desire to talk to the SA (which does not support speaksfor).
Not otherwise needed.

Oh, not tested on the AM interface yet.
parent 748f2f66
......@@ -337,6 +337,9 @@ sub auto_add_sa($)
my $cred = GeniCredential->CreateFromSigned($cred_str, $verify_sig);
my $signers = $cred->signer_certs();
return
if ($cred->type() eq "speaksfor");
# The credential has been verified, so the signer derives from a
# trusted root.
my $sa_cert = @$signers[0];
......
......@@ -272,6 +272,8 @@ sub created($) { return field($_[0], "created"); }
sub registered($) { return field($_[0], "registered"); }
sub credential_idx($) { return field($_[0], "credential_idx"); }
sub aggregate_idx($) { return field($_[0], "aggregate_idx"); }
sub speaksfor_uuid($) { return field($_[0], "speaksfor_uuid"); }
sub speaksfor_urn($) { return field($_[0], "speaksfor_urn"); }
sub status($) { return field($_[0], "status"); }
sub state($) { return field($_[0], "state"); }
sub ErrorLog($) { return field($_[0], "errorlog"); }
......@@ -631,6 +633,29 @@ sub SetRegistered($$)
return 0;
}
#
# Set the speaksfor stuff.
#
sub SetSpeaksFor($$)
{
my ($self, $speaksfor) = @_;
my $idx = $self->idx();
my $safe_speaksfor_uuid = DBQuoteSpecial($speaksfor->owner_uuid());
my $safe_speaksfor_urn = DBQuoteSpecial($speaksfor->owner_urn());
print "GeniAggregate->SetSpeaksFor($self, $speaksfor)\n";
return -1
if (!DBQueryWarn("update geni_aggregates set ".
" speaksfor_uuid=$safe_speaksfor_uuid ".
"where idx='$idx'"));
$self->{'AGGREGATE'}->{'speaksfor_urn'} = $speaksfor->owner_urn();
$self->{'AGGREGATE'}->{'speaksfor_uuid'} = $speaksfor->owner_uuid();
return 0;
}
#
# Get the slice for the aggregate.
#
......@@ -2116,10 +2141,19 @@ sub Create($$$$$$)
my $linkname = GeniXML::GetVirtualId($linkrspec);
if (!defined($linkname)) {
print STDERR "Could not create tunnel aggregate: Undefined linkname";
print STDERR "Could not create tunnel aggregate: Undefined linkname\n";
print STDERR GeniXML::Serialize($linkrspec, 1);
return undef;
}
# We support gre and egre, only.
my $tunnel_type = GeniXML::TunnelStyle($linkrspec);
if (!defined($tunnel_type)) {
print STDERR "Could not create tunnel aggregate: Bad tunnel type\n";
print STDERR GeniXML::Serialize($linkrspec, 1);
return undef;
}
my $tunnel_style = ($tunnel_type eq "egre-tunnel" ? "egre" : "gre");
my @interfaces = GeniXML::FindNodes("n:interface_ref",
$linkrspec)->get_nodelist();
......@@ -2144,7 +2178,8 @@ sub Create($$$$$$)
#
# Create a tunnel entry in the lans table.
#
my $tunnel = Tunnel->Create($experiment, $aggregate->uuid(), "", "gre");
my $tunnel = Tunnel->Create($experiment,
$aggregate->uuid(), "", $tunnel_style);
if (!defined($tunnel)) {
print STDERR "Could not create tunnel entry in lans table\n";
return undef;
......@@ -2333,7 +2368,7 @@ sub Create($$$$$$)
$member1->SetAttribute("tunnel_ipmask", "255.255.255.0");
$member1->SetAttribute("tunnel_lan", $linkname);
$member1->SetAttribute("tunnel_unit", $tunnel_number + 1);
$member1->SetAttribute("tunnel_style", "gre");
$member1->SetAttribute("tunnel_style", $tunnel_style);
$member1->SetAttribute("tunnel_myid", $virtid1);
$member1->SetAttribute("tunnel_peerid", $virtid2);
$member1->SetAttribute("tunnel_tag", $cksum);
......@@ -2365,7 +2400,7 @@ sub Create($$$$$$)
$member2->SetAttribute("tunnel_ipmask", "255.255.255.0");
$member2->SetAttribute("tunnel_lan", $linkname);
$member2->SetAttribute("tunnel_unit", $tunnel_number + 1);
$member2->SetAttribute("tunnel_style", "gre");
$member2->SetAttribute("tunnel_style", $tunnel_style);
$member2->SetAttribute("tunnel_myid", $virtid2);
$member2->SetAttribute("tunnel_peerid", $virtid1);
$member2->SetAttribute("tunnel_tag", $cksum);
......
......@@ -476,10 +476,10 @@ sub GetTicket($;$)
$rspecstr, $isupdate, $impotent, 0, 1, $ticket);
}
sub GetTicketAux($$$$$$$)
sub GetTicketAux($$$$$$$$)
{
my ($credential, $rspecstr, $isupdate, $impotent, $v2, $level,
$ticket) = @_;
$ticket, $speaksfor) = @_;
defined($credential) &&
($credential->HasPrivilege( "pi" ) or
......@@ -515,16 +515,19 @@ sub GetTicketAux($$$$$$$)
return $slice
if (GeniResponse::IsResponse($slice));
}
$slice->SetSpeaksFor($speaksfor)
if (defined($speaksfor));
main::AddLogfileMetaDataFromSlice($slice);
return GetTicketAuxAux($slice, $user, $rspecstr,
$isupdate, $impotent, $v2, $level, $ticket,
[$credential]);
[$credential], $speaksfor);
}
sub GetTicketAuxAux($$$$$$$$$)
sub GetTicketAuxAux($$$$$$$$$$)
{
my ($slice, $user, $rspecstr,
$isupdate, $impotent, $v2, $level, $ticket, $credentials) = @_;
my ($slice, $user, $rspecstr, $isupdate,
$impotent, $v2, $level, $ticket, $credentials, $speaksfor) = @_;
my $response = undef;
my $restorevirt = 0; # Flag to restore virtual state
my $restorephys = 0; # Flag to restore physical state
......@@ -2542,6 +2545,8 @@ sub GetTicketAuxAux($$$$$$$$$)
goto bad;
}
$newticket->SetSlice($slice);
$newticket->SetSpeaksFor($speaksfor)
if (defined($speaksfor));
if ($newticket->Sign()) {
$response = GeniResponse->Create(GENIRESPONSE_ERROR, undef,
......@@ -2674,12 +2679,13 @@ sub SliverWork($$)
"This ticket was already redeemed!");
}
return SliverWorkAux($credential, $ticket,
$keys, $isupdate, $impotent, 0, 0);
$keys, $isupdate, $impotent, 0, 0, undef);
}
sub SliverWorkAux($$$$$$$)
sub SliverWorkAux($$$$$$$$)
{
my ($credential, $object, $keys, $isupdate, $impotent, $v2, $level) = @_;
my ($credential, $object,
$keys, $isupdate, $impotent, $v2, $level, $speaksfor) = @_;
my $didfwsetup = 0;
my $shouldrollback = 0;
my $restorephys = 0; # Flag to restore physical state
......@@ -3840,6 +3846,9 @@ sub SliverWorkAux($$$$$$$)
if (GeniUsage->NewManifest($aggregate, $manifest, $rspec)) {
print STDERR "GeniUsage->NewManifest($aggregate) failed\n";
}
$aggregate->SetSpeaksFor($speaksfor)
if (defined($speaksfor));
#
# Each new aggregate gets a history record.
#
......
This diff is collapsed.
......@@ -145,6 +145,7 @@ sub issuer($) { return field($_[0], "issuer"); }
sub privkey($) { return field($_[0], "privkey"); }
sub revoked($) { return field($_[0], "revoked"); }
sub certfile($) { return field($_[0], "certfile"); }
sub passphrase($) { return undef; }
sub uri($) { return field($_[0], "uri"); }
sub urn($) { return field($_[0], "urn"); }
sub rootcert($) { return $_[0]->{'ROOTCERT'}; }
......@@ -687,7 +688,16 @@ sub WriteToFile($;$)
print $tempfile "-----END CERTIFICATE-----\n";
if ($withkey && $self->privkey()) {
print $tempfile "-----BEGIN RSA PRIVATE KEY-----\n";
print $tempfile $self->privkey();
if ($self->can('DecryptKey')) {
my $key = $self->DecryptKey();
return undef
if (!defined($key));
print $tempfile $key;
}
else {
print $tempfile $self->privkey();
}
print $tempfile "-----END RSA PRIVATE KEY-----\n";
}
return $filename;
......@@ -1156,12 +1166,14 @@ sub created($) { return field($_[0], "created"); }
sub cert($) { return field($_[0], "cert"); }
sub privkey($) { return field($_[0], "privkey"); }
sub revoked($) { return field($_[0], "revoked"); }
sub passphrase($) { return field($_[0], "password"); }
sub uri($) { return undef; }
sub urn($) { return field($_[0], "urn"); }
sub URL($) { return undef; }
sub URN($) { return field($_[0], "urn"); }
sub certfile($) { return undef; }
sub GetCertificate($) { return $_[0]; }
sub WriteToFile($$) { return GeniCertificate::WriteToFile($_[0], $_[1]); }
#
# Need to add DN to the emulab table.
#
......@@ -1184,5 +1196,88 @@ sub DN($)
return $dn;
}
#
# Decrypt a key. Only used on local Emulab users.
#
sub DecryptKey($)
{
my ($self) = @_;
my $passphrase = $self->passphrase();
$passphrase =~ s/\'/\'\\\'\'/g;
my $command = "$OPENSSL rsa -passin 'pass:${passphrase}'";
require Socket;
import Socket qw(:DEFAULT);
require IO::Handle; # thousands of lines just for autoflush :-(
if (! socketpair(CHILD, PARENT, AF_UNIX(), SOCK_STREAM(), PF_UNSPEC())) {
print STDERR "*** PipeTo: Could not create socketpair\n";
return undef;
}
CHILD->autoflush(1);
PARENT->autoflush(1);
my $childpid = fork();
if (! $childpid) {
close CHILD;
#
# Dup our descriptors to the parent, and exec the program.
# The parent then talks to it read/write.
#
open(STDIN, "<&PARENT") || die "Cannot redirect stdin";
open(STDOUT, ">&PARENT") || die "Cannot redirect stdout";
open(STDERR, ">&PARENT") || die "Cannot redirect stderr";
exec($command);
die("*** $0:\n".
" exec '$command' failed: $!\n");
}
close PARENT;
#
# Write the key to the child.
#
print CHILD "-----BEGIN RSA PRIVATE KEY-----\n";
print CHILD $self->privkey();
print CHILD "-----END RSA PRIVATE KEY-----\n";
# Tell the process we are done writing. ie: Send it an EOF.
shutdown(CHILD,1);
my @certlines = ();
while (<CHILD>) {
push(@certlines, $_);
}
close(CHILD);
waitpid($childpid, 0);
if ($? || !@certlines) {
print STDERR "*** Failed to decrypt key\n";
foreach my $line (@certlines) {
print STDERR $line;
}
return undef;
}
my $privkey;
my $string;
foreach my $line (@certlines) {
if ($line =~ /^-----BEGIN RSA/) {
$string = "";
next;
}
if ($line =~ /^-----END RSA/) {
$privkey = $string;
$string = undef;
next;
}
$string .= $line
if (defined($string));
}
if (! defined($privkey)) {
print STDERR "Could not parse private key\n";
return undef;
}
return $privkey;
}
# _Always_ make sure that this 1 is at the end of the file...
1;
......@@ -150,6 +150,7 @@ sub Create($$$)
my $uuid = GeniUtil::NewUUID();
my $self = {};
$self->{'type'} = "privilege";
$self->{'uuid'} = $uuid;
$self->{'valid_until'} = $target->expires();
$self->{'target_uuid'} = $target->uuid();
......@@ -169,6 +170,7 @@ sub Create($$$)
# accessors
sub field($$) { return ($_[0]->{$_[1]}); }
sub idx($) { return field($_[0], "idx"); }
sub type($) { return field($_[0], "type"); }
sub uuid($) { return field($_[0], "uuid"); }
sub expires($) { return field($_[0], "valid_until"); }
sub target_uuid($) { return field($_[0], "target_uuid"); }
......@@ -231,6 +233,17 @@ sub SetExpiration($$)
return 0;
}
#
# Set the type; only useful before signing.
#
sub SetType($$)
{
my ($self, $type) = @_;
$self->{'type'} = $type;
return 0;
}
#
# Compare the certs inside a credential to make sure that the
# certs for the target/owner have not changed. Say, if the user
......@@ -412,6 +425,7 @@ sub CreateFromSigned($$;$)
if ($?) {
unlink($filename);
$msg = $output;
chomp($msg);
goto bad;
}
unlink($filename);
......@@ -451,6 +465,14 @@ sub CreateFromSigned($$;$)
$this_uuid = undef
if (defined($this_uuid) && $this_uuid eq "");
# Type of the credential.
my $type_node = &$find( $credential_el, "type" );
goto bad
if (!defined($type_node));
my $credtype = $type_node->to_literal();
goto bad
if (!defined($credtype) || $credtype eq "");
#
# No longer require this uuid; only PG credentials have it.
# If we try to store it, throw an error. See below.
......@@ -603,6 +625,7 @@ sub CreateFromSigned($$;$)
my $self = {};
$self->{'capabilities'} = undef;
$self->{'extensions'} = $extensions;
$self->{'type'} = $credtype;
$self->{'uuid'} = $this_uuid;
$self->{'valid_until'} = $expires;
$self->{'target_uuid'} = $target_certificate->uuid();
......@@ -782,10 +805,11 @@ sub Sign($$)
$self->{'parent_cred'}->{'credentialdoc'}->toString() .
"</parent>";
}
my $type = $self->type();
my $template =
"<credential xml:id=\"ref$id\">\n".
" <type>privilege</type>\n".
" <type>$type</type>\n".
" <serial>$idx</serial>\n".
" <owner_gid>$owner_cert</owner_gid>\n".
" <owner_urn>$owner_urn</owner_urn>\n".
......@@ -974,14 +998,18 @@ sub HasPrivilege($$)
return defined( $self->{ 'capabilities' }->{ $p } );
}
sub CheckCredential($;$)
sub CheckCredential($;$$)
{
my ($credstring, $authority) = @_;
my $credential = GeniCredential->CreateFromSigned($credstring);
if (!defined($credential)) {
return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
my ($credential, $target, $nocheck) = @_;
$nocheck = 0
if (!defined($nocheck));
if (!ref($credential)) {
$credential = GeniCredential->CreateFromSigned($credential);
if (!defined($credential)) {
return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
$GeniCredential::CreateFromSignedError);
}
}
#
# Well formed credentials must now have URNs.
......@@ -992,13 +1020,13 @@ sub CheckCredential($;$)
defined($credential->target_urn()) &&
GeniHRN::IsValid($credential->owner_urn()) &&
GeniHRN::IsValid($credential->target_urn())));
#
# Make sure the credential was issued to the caller.
#
if ($credential->owner_urn() ne $ENV{'GENIURN'}) {
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN, undef,
"This is not your credential");
if (!$nocheck && $credential->owner_urn() ne $ENV{'GENIURN'}) {
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN, undef,
"This is not your credential");
}
#
......@@ -1078,9 +1106,9 @@ sub CheckCredential($;$)
# If an authority is provided, the target must match the authority.
#
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN, undef,
"This credential is for another authority!")
if (defined($authority) &&
$credential->target_urn() ne $authority->urn());
"This credential is for another target!")
if (defined($target) &&
$credential->target_urn() ne $target->urn());
return $credential;
}
......
......@@ -309,6 +309,8 @@ sub needsfirewall($) { return field($_[0], "needsfirewall"); }
sub registered($) { return field($_[0], "registered"); }
sub isplaceholder($) { return field($_[0], "isplaceholder"); }
sub monitor_pid($) { return field($_[0], "monitor_pid"); }
sub speaksfor_urn($) { return field($_[0], "speaksfor_urn"); }
sub speaksfor_uuid($) { return field($_[0], "speaksfor_uuid"); }
sub cert($) { return $_[0]->{'CERT'}->cert(); }
sub GetCertificate($) { return $_[0]->{'CERT'}; }
sub LOCKED($) { return $_[0]->{'LOCKED'}; }
......@@ -898,6 +900,30 @@ sub IsExpired($)
return (time() >= $slice_expires);
}
#
# Set the speaksfor stuff.
#
sub SetSpeaksFor($$)
{
my ($self, $speaksfor) = @_;
my $uuid = $self->uuid();
my $safe_speaksfor_uuid = DBQuoteSpecial($speaksfor->owner_uuid());
my $safe_speaksfor_urn = DBQuoteSpecial($speaksfor->owner_urn());
print "GeniSlice->SetSpeaksFor($self, $speaksfor)\n";
return -1
if (!DBQueryWarn("update geni_slices set " .
" speaksfor_uuid=$safe_speaksfor_uuid , ".
" speaksfor_urn=$safe_speaksfor_urn ".
"where uuid='$uuid'"));
$self->{'SLICE'}->{'speaksfor_urn'} = $speaksfor->owner_urn();
$self->{'SLICE'}->{'speaksfor_uuid'} = $speaksfor->owner_uuid();
return 0;
}
#
# Set the shutdown field.
#
......
#!/usr/bin/perl -wT
#
# Copyright (c) 2008-2012 University of Utah and the Flux Group.
# Copyright (c) 2008-2013 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
......@@ -187,6 +187,8 @@ sub Create($$$$)
$self->{'slice'} = undef;
$self->{'stored'} = 0; # Stored to the DB.
$self->{'LOCKED'} = 0;
$self->{'speaksfor_uuid'}= undef;
$self->{'speaksfor_urn'} = undef;
#
# For now, all tickets expire very quickly ...
......@@ -228,6 +230,8 @@ sub redeem_before($) { return field($_[0], "redeem_before"); }
sub expires($) { return field($_[0], "expires"); }
sub redeemed($ ) { return field($_[0], "redeemed"); }
sub stored($) { return field($_[0], "stored"); }
sub speaksfor_uuid($) { return field($_[0], "speaksfor_uuid"); }
sub speaksfor_urn($) { return field($_[0], "speaksfor_urn"); }
sub slice($) { return $_[0]->{'slice'}; }
sub slice_uuid($) { return $_[0]->slice()->uuid(); }
sub slice_hrn($) { return $_[0]->slice()->hrn(); }
......@@ -438,6 +442,8 @@ sub CreateFromSignedTicket($$;$)
$self->{'stored'} = 0;
$self->{'slice'} = undef;
$self->{'LOCKED'} = 0;
$self->{'speaksfor_uuid'}= undef;
$self->{'speaksfor_urn'} = undef;
#
# We save copies of the tickets we hand out, but delete them
......@@ -451,6 +457,8 @@ sub CreateFromSignedTicket($$;$)
$self->{'redeem_before'} = $row->{'redeem_before'};
$self->{'idx'} = $seqno;
$self->{'stored'} = 1;
$self->{'speaksfor_uuid'} = $row->{'speaksfor_uuid'};
#
# Older tickets might not have the slice set. The CM will
# set it later though.
......@@ -582,6 +590,20 @@ sub SetSlice($$)
return 0;
}
#
# Set the speaksfor stuff.
#
sub SetSpeaksFor($$)
{
my ($self, $speaksfor) = @_;
print "GeniTicket->SetSpeaksFor($self, $speaksfor)\n";
$self->{'speaksfor_uuid'} = $speaksfor->owner_uuid();
$self->{'speaksfor_urn'} = $speaksfor->owner_urn();
return 0;
}
#
# Return the outstanding ticket for a slice.
#
......@@ -670,6 +692,10 @@ sub Store($;$)
push(@insert_data, "owner_uuid='$owner_uuid'");
push(@insert_data, "slice_uuid='$slice_uuid'");
push(@insert_data, "redeem_before='$expires'");
if (defined($self->{'speaksfor_uuid'})) {
my $speaksfor_uuid = $self->{'speaksfor_uuid'};
push(@insert_data, "speaksfor_uuid='$speaksfor_uuid'");
}
my $safe_ticket = DBQuoteSpecial($self->ticket_string());
push(@insert_data, "ticket_string=$safe_ticket");
......
#!/usr/bin/perl -wT
#
# Copyright (c) 2009, 2010, 2012 University of Utah and the Flux Group.
# Copyright (c) 2009-2013 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
......@@ -154,6 +154,8 @@ sub NewAggregate($$$$)
my $owner_uuid = $owner->uuid();
my $owner_hrn = $owner->hrn();
my $owner_urn = $owner->urn();
my $speaksfor_uuid = $aggregate->speaksfor_uuid();
my $speaksfor_urn = $aggregate->speaksfor_urn();
# Now tack on other stuff we need.
push(@insert_data, "idx='$aggregate_idx'");
......@@ -170,6 +172,10 @@ sub NewAggregate($$$$)
push(@insert_data, "created=now()");
push(@insert_data, "exptidx=$exptidx")
if (defined($exptidx));
push(@insert_data, "speaksfor_uuid=" . DBQuoteSpecial($speaksfor_uuid))
if (defined($speaksfor_uuid));
push(@insert_data, "speaksfor_urn=" . DBQuoteSpecial($speaksfor_urn))
if (defined($speaksfor_urn));
# Insert into DB.
if (!DBQueryWarn("insert into aggregate_history set " .
......@@ -231,6 +237,8 @@ sub NewTicket($$)
my $owner_uuid = $ticket->owner_uuid();
my $owner_hrn = $ticket->owner_hrn();
my $owner_urn = $ticket->owner_urn();
my $speaksfor_uuid = $ticket->speaksfor_uuid();
my $speaksfor_urn = $ticket->speaksfor_urn();
# Now tack on other stuff we need.
push(@insert_data, "idx='$ticket_idx'");
......@@ -242,6 +250,10 @@ sub NewTicket($$)
push(@insert_data, "owner_hrn=" . DBQuoteSpecial($owner_hrn));
push(@insert_data, "owner_urn=" . DBQuoteSpecial($owner_urn));
push(@insert_data, "created=now()");
push(@insert_data, "speaksfor_uuid=" . DBQuoteSpecial($speaksfor_uuid))
if (defined($speaksfor_uuid));
push(@insert_data, "speaksfor_urn=" . DBQuoteSpecial($speaksfor_urn))
if (defined($speaksfor_urn));
if (defined($ticket->rspec())) {
my $safe_rspec = DBQuoteSpecial($ticket->rspecXML());
......
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