summaryrefslogtreecommitdiff
path: root/perl
diff options
context:
space:
mode:
Diffstat (limited to 'perl')
-rw-r--r--perl/Wallet/Config.pm59
-rw-r--r--perl/Wallet/Server.pm87
-rwxr-xr-xperl/t/server.t74
3 files changed, 211 insertions, 9 deletions
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) };