diff options
-rw-r--r-- | NEWS | 6 | ||||
-rw-r--r-- | TODO | 11 | ||||
-rw-r--r-- | perl/Wallet/Config.pm | 59 | ||||
-rw-r--r-- | perl/Wallet/Server.pm | 87 | ||||
-rwxr-xr-x | perl/t/server.t | 74 |
5 files changed, 221 insertions, 16 deletions
@@ -2,6 +2,12 @@ wallet 0.3 (unreleased) + Add support for running a user-defined function whenever an object is + created by a non-ADMIN user and using the default owner ACL returned + by that function provided that the calling user is authorized by that + ACL. This permits dynamic creation of new objects based on a default + owner ACL programmatically determined from the name of the object. + Add support for displaying the history of objects and ACLs. Add an ACL verifier that checks access against NetDB roles using the @@ -16,13 +16,6 @@ Release 0.3: * Write the PTS ACL verifier. -* Add support for a default creation policy and default ACLs. The policy - function needs to return a list of ACLs, and we'll need an ACL function - to check the user against such a list rather than an ACL that already - exists in the database. When we create such an object, we'll also need - to name the ACL, which may require fallback logic if the name was - already taken. - * Use the class names in the database tables when loading object and ACL verifier implementations. @@ -95,6 +88,10 @@ Release 1.0: * Implement a simple file wallet object. Document a naming convention for those files (group-service, perhaps). +* Revisit the way default ACL creation is handled. Add a new function to + verify an ACL given as an array without requiring that it be in the + database first and write better code for comparing two ACLs. + Future work: * Write a conventions document for ACL naming, object naming, and similar diff --git a/perl/Wallet/Config.pm b/perl/Wallet/Config.pm index 3bd2055..ad8070b 100644 --- a/perl/Wallet/Config.pm +++ b/perl/Wallet/Config.pm @@ -465,6 +465,65 @@ our $NETDB_REMCTL_PORT; =back +=head1 DEFAULT OWNERS + +By default, only users in the ADMIN ACL can create new objects in the +wallet. To allow other users to create new objects, define a Perl function +named default_owner. This function will be called whenever a non-ADMIN user +tries to create a new object and will be passed the type and name of the +object. It should return undef if there is no default owner for that +object. If there is, it should return a list containing the name to use for +the ACL and then zero or more anonymous arrays of two elements each giving +the type and identifier for each ACL entry. + +For example, the following simple function says to use a default owner named +C<default> with one entry of type C<krb5> and identifier C<rra@example.com> +for the object with type C<keytab> and name C<host/example.com>: + + sub default_owner { + my ($type, $name) = @_; + if ($type eq 'keytab' and $name eq 'host/example.com') { + return ('default', [ 'krb5', 'rra@example.com' ]); + } else { + return; + } + } + +Of course, normally this function is used for more complex mappings. Here +is a more complete example. For objects of type keytab corresponding to +various types of per-machine principals, return a default owner that sets as +owner anyone with a NetDB role for that system and the system's host +principal. This permits authorization management using NetDB while also +allowing the system to bootstrap itself once the host principal has been +downloaded and rekey itself using the old host principal. + + sub default_owner { + my ($type, $name) = @_; + my %allowed = map { $_ => 1 } + qw(HTTP cifs host imap ldap nfs pop sieve smtp webauth); + my $realm = 'example.com'; + return unless $type eq 'keytab'; + return unless $name =~ m%/%; + my ($service, $instance) = split ('/', $name, 2); + return unless $allowed{$service}; + my $acl_name = "host/$instance"; + my @acl = ([ 'netdb', $instance ], + [ 'krb5', "host/$instance\@$realm" ]); + return ($acl_name, @acl); + } + +The auto-created ACL used for the owner of the new object will, in the above +example, be named C<host/I<system>> where I<system> is the fully-qualified +name of the system as derived from the keytab being requested. + +If the name of the ACL returned by the default_owner function matches an ACL +that already exists in the wallet database, the existing ACL will be +compared to the default ACL returned by the default_owner function. If the +existing ACL has the same entries as the one returned by default_owner, +creation continues if the user is authorized by that ACL. If they don't +match, creation of the object is rejected, since the presence of an existing +ACL may indicate that something different is being done with this object. + =cut # Now, load the configuration file so that it can override the defaults. diff --git a/perl/Wallet/Server.pm b/perl/Wallet/Server.pm index 41072a8..bb1a90c 100644 --- a/perl/Wallet/Server.pm +++ b/perl/Wallet/Server.pm @@ -133,11 +133,73 @@ sub DESTROY { # Object methods ############################################################################## +# Given an object which doesn't currently exist, check whether a default_owner +# function is defined and, if so, if it returns an ACL for that object. If +# so, create the ACL and check if the current user is authorized by that ACL. +# Returns true if so, false if not, setting the internal error as appropriate. +# +# This leaves those new ACLs in the database, which may not be the best +# behavior, but it's the simplest given the current Wallet::ACL API. This +# should probably be revisited later. +sub create_check { + my ($self, $type, $name) = @_; + my $user = $self->{user}; + my $host = $self->{host}; + my $dbh = $self->{dbh}; + unless (defined (&Wallet::Config::default_owner)) { + $self->error ("$user not authorized to create ${type}:${name}"); + return; + } + my ($aname, @acl) = Wallet::Config::default_owner ($type, $name); + unless (defined $aname) { + $self->error ("$user not authorized to create ${type}:${name}"); + return; + } + my $acl = eval { Wallet::ACL->new ($aname, $dbh) }; + if ($@) { + $acl = eval { Wallet::ACL->create ($aname, $dbh, $user, $host) }; + if ($@) { + $self->error ($@); + return; + } + for my $entry (@acl) { + unless ($acl->add ($entry->[0], $entry->[1], $user, $host)) { + $self->error ($acl->error); + return; + } + } + } else { + my @entries = $acl->list; + if (not @entries and $acl->error) { + $self->error ($acl->error); + return; + } + @entries = sort { $$a[0] cmp $$b[0] && $$a[1] cmp $$b[1] } @entries; + @acl = sort { $$a[0] cmp $$b[0] && $$a[1] cmp $$b[1] } @acl; + my $okay = 1; + if (@entries != @acl) { + $okay = 0; + } else { + for my $i (0 .. $#entries) { + $okay = 0 unless ($entries[$i][0] eq $acl[$i][0]); + $okay = 0 unless ($entries[$i][1] eq $acl[$i][1]); + } + } + unless ($okay) { + $self->error ("ACL $aname exists and doesn't match default"); + return; + } + } + if ($acl->check ($user)) { + return $aname; + } else { + $self->error ("$user not authorized to create ${type}:${name}"); + return; + } +} + # Create a new object and returns that object. On error, returns undef and # sets the internal error. -# -# For the time being, we hard-code an ACL named ADMIN to use to authorize -# object creation. This needs more work later. sub create { my ($self, $type, $name) = @_; unless ($MAPPING{$type}) { @@ -148,15 +210,20 @@ sub create { my $dbh = $self->{dbh}; my $user = $self->{user}; my $host = $self->{host}; + my $acl; unless ($self->{admin}->check ($user)) { - $self->error ("$user not authorized to create ${type}:${name}"); - return undef; + $acl = $self->create_check ($type, $name); + return unless $acl; } my $object = eval { $class->create ($type, $name, $dbh, $user, $host) }; if ($@) { $self->error ($@); - return undef; + return; } else { + if ($acl and not $object->owner ($acl, $user, $host)) { + $self->error ($object->error); + return; + } return 1; } } @@ -780,10 +847,14 @@ if set, or the owner ACL if the store ACL is not set. =item create(TYPE, NAME) Creates a new object of type TYPE and name NAME. TYPE must be a recognized -type for which the wallet system has a backend implementation. To create an -object, the current user must be authorized by the ADMIN ACL. Returns true +type for which the wallet system has a backend implementation. Returns true on success and false on failure. +To create an object, the current user must either be authorized by the ADMIN +ACL or authorized by the default owner as determined by the wallet +configuration. For more information on how to map new objects to default +owners, see Wallet::Config(3). + =item destroy(TYPE, NAME) Destroys the object identified by TYPE and NAME. This destroys any data diff --git a/perl/t/server.t b/perl/t/server.t index d6ae35d..d709492 100755 --- a/perl/t/server.t +++ b/perl/t/server.t @@ -8,7 +8,7 @@ # # See LICENSE for licensing terms. -use Test::More tests => 303; +use Test::More tests => 311; use Wallet::Config; use Wallet::Server; @@ -739,6 +739,78 @@ is ($server->store ('base', 'service/both', 'stuff'), undef, ' or store it'); is ($server->error, 'cannot find base:service/both', ' because it is gone'); +# Test default ACLs on object creation. +# +# Create a default_acl sub that permits $user2 to create service/default with +# a default owner of default (the same as the both ACL), $user1 to create +# service/default-both with a default owner of both (but a different +# definition than the existing ACL), and $user2 to create service/default-2 +# with a default owner of user2 (with the same definition as the existing +# ACL). +package Wallet::Config; +sub default_owner { + my ($type, $name) = @_; + if ($type eq 'base' and $name eq 'service/default') { + return ('default', [ 'krb5', $user1 ], [ 'krb5', $user2 ]); + } elsif ($type eq 'base' and $name eq 'service/default-both') { + return ('both', [ 'krb5', $user1 ]); + } elsif ($type eq 'base' and $name eq 'service/default-2') { + return ('user2', [ 'krb5', $user2 ]); + } else { + return; + } +} +package main; + +# We're still user2, so we should now be able to create service/default. Make +# sure we can and that the ACLs all look good. +is ($server->create ('base', 'service/default'), 1, + 'Creating an object with the default ACL works'); +is ($server->create ('base', 'service/foo'), undef, ' but not any object'); +is ($server->error, "$user2 not authorized to create base:service/foo", + ' with the right error'); +$show = $server->show ('base', 'service/default'); +if (defined $show) { + $show =~ s/(Created on:) \d+$/$1 0/m; + $expected = <<"EOO"; + Type: base + Name: service/default + Owner: default + Created by: $user2 + Created from: $host + Created on: 0 + +Members of ACL default (id: 7) are: + krb5 $user1 + krb5 $user2 +EOO + is ($show, $expected, ' and the created object and ACL are correct'); +} else { + is ($server->error, undef, ' and the created object and ACL are correct'); +} + +# Try the other cases in default_acl. +is ($server->create ('base', 'service/default-both'), undef, + 'Creating an object with an ACL mismatch fails'); +is ($server->error, "ACL both exists and doesn't match default", + ' with the right error'); +is ($server->create ('base', 'service/default-2'), 1, + 'Creating an object with an existing ACL works'); +$show = $server->show ('base', 'service/default-2'); +$show =~ s/(Created on:) \d+$/$1 0/m; +$expected = <<"EOO"; + Type: base + Name: service/default-2 + Owner: user2 + Created by: $user2 + Created from: $host + Created on: 0 + +Members of ACL user2 (id: 3) are: + krb5 $user2 +EOO +is ($show, $expected, ' and the created object and ACL are correct'); + # Now test handling of some configuration errors. undef $Wallet::Config::DB_DRIVER; $server = eval { Wallet::Server->new ($user2, $host) }; |