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 60274694 authored by Leigh B Stoller's avatar Leigh B Stoller

ABAC Speaksfor credential support.

The CM can now receive either an ABAC or a non-ABAC speaksfor
credential in the list of credentials. Thanks to Gary for getting
libabac built on boss so that I could use it! The AM probably needs a
little bit more work since it has a few V3 places where it does not
invoke CMV2 directly, but that should be easy to fix; all of the AMV2
functions will work tough.

Caveat; I don't bother to look at the speaksfor option; if we get a
speaksfor credential, I figure it was cause the user wants to use it!

I added a hacky script called genspeaksfor to create a proper speaks
for credential that allows me to speak for another user. For example:

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

which generates an ABAC speaks for credential that allows me to spead
for leebee. To use the PG test scripts with this credential:

	createsliver.py* -S speaksfor.cred -s slice.cred

Where slice.cred is a plain slice credential issued to leebee and then
given to me via an out of band mechanism (:-).
parent 11f66ce4
......@@ -2154,7 +2154,8 @@ sub CheckCredentials($)
$GeniCredential::CreateFromSignedError);
goto bad;
}
if ($credential->type() eq "speaksfor") {
if ($credential->type() eq "speaksfor" ||
$credential->type() eq "abac") {
$speaksfor = $credential;
}
else {
......@@ -2186,10 +2187,52 @@ sub CheckCredentials($)
}
}
else {
$speaksfor = GeniCredential::CheckCredential($speaksfor);
if (GeniResponse::IsError($speaksfor)) {
$error = $speaksfor;
goto bad;
if ($speaksfor->type() eq "abac") {
#
# At the moment, the easiest thing to do is make the
# speaksfor credential look sorta like a normal
# credential.
#
# The signer of the credential is the one being
# spoken for. This is the target of the speaksfor.
# The speaker is derived from the TLS context, and
# is the owner of the credential.
#
my $speaker_certificate =
GeniCertificate->LoadFromString($ENV{'SSL_CLIENT_CERT'});
if (!defined($speaker_certificate)) {
print STDERR "Could not load speaker certificate:\n";
print STDERR $ENV{'SSL_CLIENT_CERT'} . "\n";
$error = GeniResponse->Create(GENIRESPONSE_FORBIDDEN,
undef,
"Could not load speaker certificate");
goto bad;
}
$speaksfor->SetOwnerCert($speaker_certificate);
#
# Grab the signer. Should only be one.
#
my @signer_certs = @{ $speaksfor->signer_certs() };
my $speakee_certificate =
GeniCertificate->LoadFromString($signer_certs[0]);
if (!defined($speakee_certificate)) {
print STDERR "Could not load user certificate:\n";
print STDERR $signer_certs[0] . "\n";
$error = GeniResponse->Create(GENIRESPONSE_FORBIDDEN,
undef,
"Could not load user certificate");
goto bad;
}
$speaksfor->SetTargetCert($speakee_certificate);
}
else {
$speaksfor = GeniCredential::CheckCredential($speaksfor);
if (GeniResponse::IsError($speaksfor)) {
$error = $speaksfor;
goto bad;
}
}
main::AddLogfileMetaDataFromSpeaksFor($speaksfor);
......
......@@ -445,26 +445,6 @@ sub CreateFromSigned($$;$)
my $credential_el = &$find( $root, "credential" );
goto bad unless defined( $credential_el );
# Dig out the entire credential structure to save it.
my ($credential) = $doc->getElementsByTagName("credential");
# Ditto the signatures.
my ($signatures) = $doc->getElementsByTagName("signatures");
my @signatures = $signatures->getElementsByTagName("Signature");
# Dig out the extensions
# now extensions is an xml element.
my ($extensions) = GeniXML::FindNodes('//n:extensions',
$root)->get_nodelist;
# UUID of the credential.
my $uuid_node = &$find( $credential_el, "uuid" );
goto bad
if (!defined($uuid_node));
my $this_uuid = $uuid_node->to_literal();
$this_uuid = undef
if (defined($this_uuid) && $this_uuid eq "");
# Type of the credential.
my $type_node = &$find( $credential_el, "type" );
goto bad
......@@ -473,16 +453,6 @@ sub CreateFromSigned($$;$)
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.
#
if (defined($this_uuid) &&
! ($this_uuid =~ /^\w+\-\w+\-\w+\-\w+\-\w+$/)) {
$msg = "Invalid this_uuid in credential";
goto bad;
}
# Expiration
my $expires_node = &$find( $credential_el, "expires" );
if (!defined($expires_node)) {
......@@ -503,6 +473,48 @@ sub CreateFromSigned($$;$)
}
$expires = POSIX::strftime("20%y-%m-%dT%H:%M:%S", localtime($when));
# Dig out the signatures.
my ($signatures) = $doc->getElementsByTagName("signatures");
my @signatures = $signatures->getElementsByTagName("Signature");
# We deal with ABAC on a totally separate path. Results in a bit
# of code duplication, but so be it.
if ($credtype eq "abac") {
my $ref = {
'type' => $credtype,
'string' => $string,
'valid_until' => $expires,
'signatures' => \@signatures,
};
return GeniCredential::ABAC->CreateFromSigned($ref, $root);
}
# Dig out the entire credential structure to save it.
my ($credential) = $doc->getElementsByTagName("credential");
# Dig out the extensions
# now extensions is an xml element.
my ($extensions) = GeniXML::FindNodes('//n:extensions',
$root)->get_nodelist;
# UUID of the credential.
my $uuid_node = &$find( $credential_el, "uuid" );
goto bad
if (!defined($uuid_node));
my $this_uuid = $uuid_node->to_literal();
$this_uuid = undef
if (defined($this_uuid) && $this_uuid eq "");
#
# No longer require this uuid; only PG credentials have it.
# If we try to store it, throw an error. See below.
#
if (defined($this_uuid) &&
! ($this_uuid =~ /^\w+\-\w+\-\w+\-\w+\-\w+$/)) {
$msg = "Invalid this_uuid in credential";
goto bad;
}
# Dig out the target certificate.
my $cert_node = &$find( $credential_el, "target_gid" );
goto bad
......@@ -1113,5 +1125,169 @@ sub CheckCredential($;$$)
return $credential;
}
########################################################################
# ABAC version of a credential. This a total hack job, will need to
# be flushed and redone later.
#
package GeniCredential::ABAC;
use Exporter;
use SelfLoader ();
use vars qw(@ISA @EXPORT $AUTOLOAD);
@ISA = qw(Exporter SelfLoader);
@EXPORT = qw ( );
use English;
use GeniDB;
use GeniHRN;
use GeniCertificate;
use GeniResponse;
use Carp;
use Data::Dumper;
use overload ('""' => 'Stringify');
#
# Stringify for output.
#
sub Stringify($)
{
my ($self) = @_;
return "[GeniCredential::ABAC ]";
}
AUTOLOAD {
my $self = $_[0];
my $type = ref($self) or croak "$self is not an object";
my $name = $AUTOLOAD;
$name =~ s/.*://; # strip fully-qualified portion
confess("No such method '$name' in class $type");
return undef;
}
sub field($$) { return ($_[0]->{$_[1]}); }
sub asString($) { return field($_[0], "string"); }
sub type($) { return field($_[0], "type"); }
sub owner_cert($) { return field($_[0], "owner_cert"); }
sub target_cert($) { return field($_[0], "target_cert"); }
sub owner_urn($) { return field($_[0], "owner_cert")->urn(); }
sub target_urn($) { return field($_[0], "target_cert")->urn(); }
sub owner_uuid($) { return field($_[0], "owner_cert")->uuid(); }
sub target_uuid($) { return field($_[0], "target_cert")->uuid(); }
sub signer_certs($) { return field($_[0], "signer_certs"); }
# Break circular reference someplace to avoid exit errors.
sub DESTROY {
my $self = shift;
$self = undef;
}
sub CreateFromSigned($$$)
{
my ($class, $self, $root) = @_;
my $msg = undef;
# Find an element (which must exist exactly once) within a node.
my $find = sub
{
my( $node, $name ) = @_;
my @cnodes = grep( $_->nodeName eq $name, $node->childNodes );
return undef unless scalar( @cnodes ) == 1;
return $cnodes[ 0 ];
};
my $credential_el = &$find( $root, "credential" );
goto bad unless defined( $credential_el );
my @signatures = @{ $self->{'signatures'} };
#
# There should be just a single signature at this point.
#
if (@signatures > 1) {
$msg = "Too many signatures in ABAC credential";
goto bad;
}
#
# Dig out the signer certs.
#
my $signer_certs = [];
my $cert_nodes = $signatures[0]->getElementsByTagName("X509Certificate");
foreach my $node (@$cert_nodes) {
my $signer_cert = $node->to_literal();
push(@$signer_certs, $signer_cert);
}
if (! @{ $signer_certs }) {
$msg = "Cannot find signer certs in credential";
goto bad;
}
$self->{'signer_certs'} = $signer_certs;
$self->{'idx'} = undef; # Only set when stored to DB.
bless($self, $class);
return $self;
bad:
if (!defined($msg)) {
$msg = "Internal error creating ABAC credential object";
}
print STDERR "$msg\n";
return undef;
}
#
# Set the owner/target cert so that this looks something like a
# normal credential.
#
sub SetTargetCert($$)
{
my ($self, $cert) = @_;
$self->{'target_cert'} = $cert;
return 0;
}
sub SetOwnerCert($$)
{
my ($self, $cert) = @_;
$self->{'owner_cert'} = $cert;
return 0;
}
sub CheckCredential($)
{
my ($self) = @_;
require ABAC;
my $context = ABAC::Context->new();
$context->load_attribute_chunk($self->asString());
if (0) {
print STDERR "Could not load $self into abac context\n";
return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
"Could not load $self into abac context");
}
my $user = ABAC::ID->new_ID_chunk("-----BEGIN CERTIFICATE-----\n".
$self->target_cert()->cert() .
"-----END CERTIFICATE-----\n");
my $tool = ABAC::ID->new_ID_chunk("-----BEGIN CERTIFICATE-----\n".
$self->owner_cert()->cert() .
"-----END CERTIFICATE-----\n");
my ($success, $credentials) =
$context->query($user->keyid() . ".speaks_for_" . $user->keyid(),
$tool->keyid());
return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
"Could not prove abac credential")
if (!$success);
return $self;
}
# _Always_ make sure that this 1 is at the end of the file...
1;
......@@ -30,6 +30,7 @@
use strict;
use English;
use Getopt::Std;
use Data::Dumper;
#
# Generate a speaksfor credential, useful only for testing.
......@@ -37,11 +38,11 @@ use Getopt::Std;
#
sub usage()
{
print STDERR "Usage: $0 <user-urn> <speaker-urn>";
print STDERR "[permission,delegate ...]\n";
print STDERR "Usage: $0 [-a] <user-urn> <speaker-urn>";
exit(1);
}
my $optlist = "";
my $optlist = "a";
my $doabac = 0;
# Configure ...
my $TB = "@prefix@";
......@@ -75,6 +76,9 @@ my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"a"})) {
$doabac = 1;
}
usage()
if (@ARGV < 2);
my $user_urn = shift();
......@@ -98,14 +102,54 @@ if (!defined($speaker)) {
fatal("No such speaker in the DB");
}
my $credential = GeniCredential->Create($geniuser, $speaker);
fatal("Could not create credential")
if (!defined($credential));
$credential->SetType("speaksfor");
fatal("Could not sign speaksfor credential")
if ($credential->Sign($speaker->GetCertificate()));
if ($doabac) {
require ABAC;
import ABAC;
my $userfile = $geniuser->GetCertificate()->WriteToFile(1);
fatal("Could not write user cert/key to file!")
if (!defined($userfile));
my $speakerfile = $speaker->GetCertificate()->WriteToFile(1);
fatal("Could not write speaker cert/key to file!")
if (!defined($speakerfile));
my $abacuser = ABAC::ID->new($userfile);
fatal("Could not create user ABAC:ID")
if (!defined($abacuser));
$abacuser->load_privkey($userfile);
print $credential->{'string'};
my $abactool = ABAC::ID->new($speakerfile);
fatal("Could not create speaker ABAC:ID")
if (!defined($abactool));
my $abacattr = ABAC::Attribute->new($abacuser,
"speaks_for_" . $abacuser->keyid(),
365 * 24 * 60 * 60);
fatal("Could not create ABAC::Attribute")
if (!defined($abacattr));
$abacattr->principal($abactool->keyid());
$abacattr->bake();
my $xml = $abacattr->cert_chunk();
if (0) {
my $cred = GeniCredential->CreateFromSigned($xml);
$cred->SetTargetCert($geniuser->GetCertificate());
$cred->SetOwnerCert($speaker->GetCertificate());
$cred->CheckCredential();
}
else {
print $xml;
}
}
else {
my $credential = GeniCredential->Create($geniuser, $speaker);
fatal("Could not create credential")
if (!defined($credential));
$credential->SetType("speaksfor");
fatal("Could not sign speaksfor credential")
if ($credential->Sign($speaker->GetCertificate()));
print $credential->{'string'};
}
exit(0);
......@@ -44,7 +44,7 @@ datatypes xs = "http://www.w3.org/2001/XMLSchema-datatypes"
anyelementbody = (attribute * {text} | text | element * {anyelementbody} )*
# This is where we get the definition of RSpec from
include "../rspec/protogeni-rspec-common.rnc"
#include "../rspec/protogeni-rspec-common.rnc"
## Representation of a single privileges.
PrivilegeSpec = element privilege {
......@@ -84,6 +84,11 @@ TicketSpec = element ticket {
anyelementbody
}
## ABAC stuff. Not defined.
ABACSpec = element abac {
anyelementbody
}
## A list of signatures.
signatures = element signatures {
element sig:Signature { anyelementbody }+
......@@ -94,7 +99,7 @@ credentials = element credential {
## The ID for signature referencing.
attribute xml:id {xs:ID},
## The type of this credential. Currently a Privilege set or a Ticket.
element type { "privilege" | "ticket" | "capability" | "speaksfor" },
element type { "privilege" | "ticket" | "capability" | "speaksfor" | "abac"},
## A serial number.
element serial { xsd:string },
## GID of the owner of this credential.
......@@ -110,7 +115,7 @@ credentials = element credential {
## Expires on
element expires { xsd:dateTime },
## Privileges or a ticket
(PrivilegesSpec | TicketSpec | CapabilitiesSpec),
(PrivilegesSpec | TicketSpec | CapabilitiesSpec | ABACSpec),
## Optional Extensions
element extensions { anyelementbody }*,
## Parent that delegated to us
......@@ -123,3 +128,5 @@ SignedCredential = element signed-credential {
}
start = SignedCredential
......@@ -43,7 +43,6 @@
default namespace = "http://www.protogeni.net/resources/credential/0.1"
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" xmlns:sig="http://www.w3.org/2000/09/xmldsig#">
<xs:include schemaLocation="protogeni-rspec-common.xsd"/>
<xs:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="sig.xsd"/>
<xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd"/>
<xs:group name="anyelementbody">
......@@ -54,7 +53,10 @@
<xs:attributeGroup name="anyelementbody">
<xs:anyAttribute processContents="skip"/>
</xs:attributeGroup>
<!-- This is where we get the definition of RSpec from -->
<!--
This is where we get the definition of RSpec from
include "../rspec/protogeni-rspec-common.rnc"
-->
<xs:element name="privilege">
<xs:complexType>
<xs:sequence>
......@@ -122,6 +124,12 @@
<xs:documentation>The ticket must be "cashed in" by this date </xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="abac">
<xs:complexType mixed="true">
<xs:group ref="anyelementbody"/>
<xs:attributeGroup ref="anyelementbody"/>
</xs:complexType>
</xs:element>
<xs:element name="signatures">
<xs:complexType>
<xs:sequence>
......@@ -155,6 +163,7 @@
<xs:element ref="privileges"/>
<xs:element ref="ticket"/>
<xs:element ref="capabilities"/>
<xs:element ref="abac"/>
</xs:choice>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="extensions"/>
<xs:element minOccurs="0" ref="parent"/>
......@@ -172,6 +181,7 @@
<xs:enumeration value="ticket"/>
<xs:enumeration value="capability"/>
<xs:enumeration value="speaksfor"/>
<xs:enumeration value="abac"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
......
#!/usr/bin/perl -w
#
# Copyright (c) 2008-2012 University of Utah and the Flux Group.
# Copyright (c) 2008-2013 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
......@@ -337,10 +337,14 @@ if( opendir( DIR, "$GENICERTS/local" ) ) {
# covers the credential and all the parents up to the top.
#
foreach my $sigid (keys(%credentials)) {
my $nosigref = 0;
again:
my $cmd = "$XMLSEC1 --verify --store-signatures ";
$cmd .= "--print-debug "
if ($debug);
$cmd .= "--node-id Sig_$sigid $certarg $xmlfile";
$cmd .= "--node-id ". ($nosigref ? $sigid : "Sig_${sigid}");
$cmd .= "$certarg $xmlfile";
print STDERR "$cmd\n"
if ($debug);
......@@ -370,9 +374,18 @@ foreach my $sigid (keys(%credentials)) {
$issuer = undef;
}
}
close(SEC) or
fatal($! ? "Error closing $XMLSEC1 pipe: $!"
: "Exit status $? from $XMLSEC1");
if (!close(SEC)) {
if (! $|) {
fatal("Error closing $XMLSEC1 pipe: $!");
}
if ($debug) {
print STDERR "Exit status $? from $XMLSEC1\n";
}
}
if ($bad && !$nosigref) {
$nosigref = 1;
goto again;
}
fatal("$sigid in $xmlfile did not verify!")
if ($bad);
}
......
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