From 86533bf43d071048d654691dc18a3004b6142081 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Mon, 8 Jun 2015 11:15:37 -0700 Subject: Added nested acl verifier This verifier will allow embedding one ACL in another for more flexible ACL handling. As part of thise we've also added the ability for each verifier to do a syntax check to see if a given name is valid for that verifier. For the moment this returns true for everything but Nested. Nested will check to make sure the given name is an existing group. Change-Id: Iacdf146d46ed882d57b7534058d34db6e6ec1de4 --- perl/t/verifier/nested.t | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100755 perl/t/verifier/nested.t (limited to 'perl/t/verifier') diff --git a/perl/t/verifier/nested.t b/perl/t/verifier/nested.t new file mode 100755 index 0000000..ec7ce40 --- /dev/null +++ b/perl/t/verifier/nested.t @@ -0,0 +1,84 @@ +#!/usr/bin/perl +# +# Tests for the wallet ACL nested verifier. +# +# Written by Jon Robertson +# Copyright 2015 +# The Board of Trustees of the Leland Stanford Junior University +# +# See LICENSE for licensing terms. + +use strict; +use warnings; + +use Test::More tests => 22; + +use Wallet::ACL::Base; +use Wallet::ACL::Nested; +use Wallet::Admin; +use Wallet::Config; + +use lib 't/lib'; +use Util; + +# Some global defaults to use. +my $admin = 'admin@EXAMPLE.COM'; +my $user1 = 'alice@EXAMPLE.COM'; +my $user2 = 'bob@EXAMPLE.COM'; +my $user3 = 'jack@EXAMPLE.COM'; +my $host = 'localhost'; +my @trace = ($admin, $host, time); + +# Use Wallet::Admin to set up the database. +db_setup; +my $setup = eval { Wallet::Admin->new }; +is ($@, '', 'Database connection succeeded'); +is ($setup->reinitialize ($setup), 1, 'Database initialization succeeded'); +my $schema = $setup->schema; + +# Create a few ACLs for later testing. +my $acl = eval { Wallet::ACL->create ('test', $schema, @trace) }; +ok (defined ($acl), 'ACL creation'); +my $acl_nesting = eval { Wallet::ACL->create ('nesting', $schema, @trace) }; +ok (defined ($acl), ' and another'); +my $acl_deep = eval { Wallet::ACL->create ('deepnesting', $schema, @trace) }; +ok (defined ($acl), ' and another'); + +# Create an verifier to make sure that works +my $verifier = Wallet::ACL::Nested->new ('test', $schema); +ok (defined $verifier, 'Wallet::ACL::Nested creation'); +ok ($verifier->isa ('Wallet::ACL::Nested'), ' and class verification'); +is ($verifier->syntax_check ('notcreated'), 0, + ' and it rejects a nested name that is not already an ACL'); +is ($verifier->syntax_check ('test'), 1, + ' and accepts one that already exists'); + +# Add a few entries to one ACL and then see if they validate. +ok ($acl->add ('krb5', $user1, @trace), 'Added test scheme'); +ok ($acl->add ('krb5', $user2, @trace), ' and another'); +ok ($acl_nesting->add ('nested', 'test', @trace), ' and then nested it'); +ok ($acl_nesting->add ('krb5', $user3, @trace), + ' and added a non-nesting user'); +is ($acl_nesting->check ($user1), 1, ' so check of nested succeeds'); +is ($acl_nesting->check ($user3), 1, ' so check of non-nested succeeds'); +is (scalar ($acl_nesting->list), 2, + ' and the acl has the right number of items'); + +# Add a recursive nesting to make sure it doesn't send us into loop. +ok ($acl_deep->add ('nested', 'test', @trace), + 'Adding deep nesting for one nest succeeds'); +ok ($acl_deep->add ('nested', 'nesting', @trace), ' and another'); +ok ($acl_deep->add ('krb5', $user3, @trace), + ' and added a non-nesting user'); +is ($acl_deep->check ($user1), 1, ' so check of nested succeeds'); +is ($acl_deep->check ($user3), 1, ' so check of non-nested succeeds'); + +# Test getting an error in adding an invalid group to an ACL object itself. +isnt ($acl->add ('nested', 'doesnotexist', @trace), 1, + 'Adding bad nested acl fails'); + +# Clean up. +$setup->destroy; +END { + unlink 'wallet-db'; +} -- cgit v1.2.3 From e353e236cf6828647820b2d83529cc4a4f08cef2 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Tue, 25 Aug 2015 14:29:16 -0700 Subject: ldap-attr.t: Updated tests to use jonrober rather than rra Change-Id: I842a7335a4b50c9c20b921ae2efc63aab571635e --- perl/t/verifier/ldap-attr.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'perl/t/verifier') diff --git a/perl/t/verifier/ldap-attr.t b/perl/t/verifier/ldap-attr.t index 3c132e2..3caaf8b 100755 --- a/perl/t/verifier/ldap-attr.t +++ b/perl/t/verifier/ldap-attr.t @@ -31,7 +31,7 @@ require_ok ('Wallet::ACL::LDAP::Attribute'); my $host = 'ldap.stanford.edu'; my $base = 'cn=people,dc=stanford,dc=edu'; my $filter = 'uid'; -my $user = 'rra@stanford.edu'; +my $user = 'jonrober@stanford.edu'; my $attr = 'suPrivilegeGroup'; my $value = 'stanford:stanford'; @@ -68,7 +68,7 @@ SKIP: { is ($verifier->check ($user, "BOGUS=$value"), undef, "Checking BOGUS=$value fails with error"); is ($verifier->error, - 'cannot check LDAP attribute BOGUS for rra: Undefined attribute type', + 'cannot check LDAP attribute BOGUS for jonrober: Undefined attribute type', '...with correct error'); is ($verifier->check ('user-does-not-exist', "$attr=$value"), 0, "Checking for nonexistent user fails"); -- cgit v1.2.3 From 6b0cad572edef05d119abc8fc843c8c5d33665b8 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Thu, 27 Aug 2015 10:34:22 -0700 Subject: Added Wallet::ACL::LDAP::Attribute::Root Added a version of the LDAP attribute ACL. Like the root version for NetDB, this requires that the principal end in /root, and then strips off /root before doing matching against the given LDAP attribute. Change-Id: I23119ef9c9ce3e0556f5d71a509815f2efc1bbe6 --- perl/lib/Wallet/ACL/LDAP/Attribute/Root.pm | 128 +++++++++++++++++++++++++++++ perl/lib/Wallet/Admin.pm | 13 +-- perl/lib/Wallet/Schema.pm | 4 + perl/lib/Wallet/Schema/Result/AclScheme.pm | 4 + perl/t/general/report.t | 11 +-- perl/t/verifier/ldap-attr.t | 37 +++++++-- 6 files changed, 179 insertions(+), 18 deletions(-) create mode 100644 perl/lib/Wallet/ACL/LDAP/Attribute/Root.pm (limited to 'perl/t/verifier') diff --git a/perl/lib/Wallet/ACL/LDAP/Attribute/Root.pm b/perl/lib/Wallet/ACL/LDAP/Attribute/Root.pm new file mode 100644 index 0000000..eb30931 --- /dev/null +++ b/perl/lib/Wallet/ACL/LDAP/Attribute/Root.pm @@ -0,0 +1,128 @@ +# Wallet::ACL::LDAP::Attribute::Root -- Wallet LDAP ACL verifier (root instances). +# +# Written by Jon Robertson +# From Wallet::ACL::NetDB::Root by Russ Allbery +# Copyright 2015 +# The Board of Trustees of the Leland Stanford Junior University +# +# See LICENSE for licensing terms. + +############################################################################## +# Modules and declarations +############################################################################## + +package Wallet::ACL::LDAP::Attribute::Root; +require 5.006; + +use strict; +use warnings; +use vars qw(@ISA $VERSION); + +use Wallet::ACL::LDAP::Attribute; +use Wallet::Config; + +@ISA = qw(Wallet::ACL::LDAP::Attribute); + +# This version should be increased on any code change to this module. Always +# use two digits for the minor version with a leading zero if necessary so +# that it will sort properly. +$VERSION = '0.01'; + +############################################################################## +# Interface +############################################################################## + +# Override the check method of Wallet::ACL::LDAP::Attribute to require that +# the principal be a root instance and to strip /root out of the principal +# name before checking roles. +sub check { + my ($self, $principal, $acl) = @_; + undef $self->{error}; + unless ($principal) { + $self->error ('no principal specified'); + return; + } + unless ($principal =~ s%^([^/\@]+)/root(\@|\z)%$1$2%) { + return 0; + } + return $self->SUPER::check ($principal, $acl); +} + +############################################################################## +# Documentation +############################################################################## + +=for stopwords +ACL Allbery LDAP verifier + +=head1 NAME + +Wallet::ACL::LDAP::Attribute::Root - Wallet ACL verifier for LDAP attributes (root instances) + +=head1 SYNOPSIS + + my $verifier = Wallet::ACL::LDAP::Attribute::Root->new; + my $status = $verifier->check ($principal, "$attr=$value"); + if (not defined $status) { + die "Something failed: ", $verifier->error, "\n"; + } elsif ($status) { + print "Access granted\n"; + } else { + print "Access denied\n"; + } + +=head1 DESCRIPTION + +Wallet::ACL::LDAP::Attribute::Root works identically to +Wallet::ACL::LDAP::Attribute except that it requires the principal to +be a root instance (in other words, to be in the form +/root@) and strips the C portion from the +principal before checking against the LDAP attribute and value. As +with the base LDAP Attribute ACL verifier, the value of such a +C ACL is an attribute followed by an equal sign and a +value, and the ACL grants access to a given principal if and only if +the LDAP entry for that principal (with C stripped) has that +attribute set to that value. + +To use this object, the same configuration parameters must be set as for +Wallet::ACL::LDAP::Attribute. See Wallet::Config(3) for details on +those configuration parameters and information about how to set wallet +configuration. + +=head1 METHODS + +=over 4 + +=item check(PRINCIPAL, ACL) + +Returns true if PRINCIPAL is granted access according to ACL, false if +not, and undef on an error (see L<"DIAGNOSTICS"> below). ACL must be an +attribute name and a value, separated by an equal sign (with no +whitespace). PRINCIPAL will be granted access if it has an instance of +C and if (with C stripped off) its LDAP entry contains +that attribute with that value + +=back + +=head1 DIAGNOSTICS + +Same as for Wallet::ACL::LDAP::Attribute. + +=head1 CAVEATS + +The instance to strip is not currently configurable. + +=head1 SEE ALSO + +Net::Remctl(3), Wallet::ACL(3), Wallet::ACL::Base(3), +Wallet::ACL::LDAP::Attribute(3), Wallet::Config(3), wallet-backend(8) + +This module is part of the wallet system. The current version is +available from L. + +=head1 AUTHORS + +Jon Robertson +Russ Allbery + +=cut diff --git a/perl/lib/Wallet/Admin.pm b/perl/lib/Wallet/Admin.pm index f6f1f90..b4246ba 100644 --- a/perl/lib/Wallet/Admin.pm +++ b/perl/lib/Wallet/Admin.pm @@ -115,12 +115,13 @@ sub default_data { # acl_schemes default rows. my ($r1) = $self->{schema}->resultset('AclScheme')->populate ([ [ qw/as_name as_class/ ], - [ 'krb5', 'Wallet::ACL::Krb5' ], - [ 'krb5-regex', 'Wallet::ACL::Krb5::Regex' ], - [ 'ldap-attr', 'Wallet::ACL::LDAP::Attribute' ], - [ 'nested', 'Wallet::ACL::Nested' ], - [ 'netdb', 'Wallet::ACL::NetDB' ], - [ 'netdb-root', 'Wallet::ACL::NetDB::Root' ], + [ 'krb5', 'Wallet::ACL::Krb5' ], + [ 'krb5-regex', 'Wallet::ACL::Krb5::Regex' ], + [ 'ldap-attr', 'Wallet::ACL::LDAP::Attribute' ], + [ 'ldap-attr-root', 'Wallet::ACL::LDAP::Attribute::Root' ], + [ 'nested', 'Wallet::ACL::Nested' ], + [ 'netdb', 'Wallet::ACL::NetDB' ], + [ 'netdb-root', 'Wallet::ACL::NetDB::Root' ], ]); warn "default AclScheme not installed" unless defined $r1; diff --git a/perl/lib/Wallet/Schema.pm b/perl/lib/Wallet/Schema.pm index 5b850c0..386801a 100644 --- a/perl/lib/Wallet/Schema.pm +++ b/perl/lib/Wallet/Schema.pm @@ -113,6 +113,10 @@ Holds the supported ACL schemes and their corresponding Perl classes: values ('krb5-regex', 'Wallet::ACL::Krb5::Regex'); insert into acl_schemes (as_name, as_class) values ('ldap-attr', 'Wallet::ACL::LDAP::Attribute'); + insert into acl_schemes (as_name, as_class) + values ('ldap-attr-root', 'Wallet::ACL::LDAP::Attribute::Root'); + insert into acl_schemes (as_name, as_class) + values ('nested', 'Wallet::ACL::Nested'); insert into acl_schemes (as_name, as_class) values ('netdb', 'Wallet::ACL::NetDB'); insert into acl_schemes (as_name, as_class) diff --git a/perl/lib/Wallet/Schema/Result/AclScheme.pm b/perl/lib/Wallet/Schema/Result/AclScheme.pm index 91a58b2..be4ec09 100644 --- a/perl/lib/Wallet/Schema/Result/AclScheme.pm +++ b/perl/lib/Wallet/Schema/Result/AclScheme.pm @@ -35,6 +35,10 @@ By default it contains the following entries: values ('krb5-regex', 'Wallet::ACL::Krb5::Regex'); insert into acl_schemes (as_name, as_class) values ('ldap-attr', 'Wallet::ACL::LDAP::Attribute'); + insert into acl_schemes (as_name, as_class) + values ('ldap-attr-root', 'Wallet::ACL::LDAP::Attribute::Root'); + insert into acl_schemes (as_name, as_class) + values ('nested', 'Wallet::ACL::Nested'); insert into acl_schemes (as_name, as_class) values ('netdb', 'Wallet::ACL::NetDB'); insert into acl_schemes (as_name, as_class) diff --git a/perl/t/general/report.t b/perl/t/general/report.t index a841acd..e47cdc6 100755 --- a/perl/t/general/report.t +++ b/perl/t/general/report.t @@ -11,7 +11,7 @@ use strict; use warnings; -use Test::More tests => 222; +use Test::More tests => 223; use Wallet::Admin; use Wallet::Report; @@ -57,14 +57,15 @@ is ($types[9][0], 'wa-keyring', ' and the tenth member is correct'); # And that we have all schemes that we expect. my @schemes = $report->acl_schemes; -is (scalar (@schemes), 7, 'There are seven acl schemes created'); +is (scalar (@schemes), 8, 'There are seven acl schemes created'); is ($schemes[0][0], 'base', ' and the first member is correct'); is ($schemes[1][0], 'krb5', ' and the second member is correct'); is ($schemes[2][0], 'krb5-regex', ' and the third member is correct'); is ($schemes[3][0], 'ldap-attr', ' and the fourth member is correct'); -is ($schemes[4][0], 'nested', ' and the fifth member is correct'); -is ($schemes[5][0], 'netdb', ' and the sixth member is correct'); -is ($schemes[6][0], 'netdb-root', ' and the seventh member is correct'); +is ($schemes[4][0], 'ldap-attr-root', ' and the fifth member is correct'); +is ($schemes[5][0], 'nested', ' and the sixth member is correct'); +is ($schemes[6][0], 'netdb', ' and the seventh member is correct'); +is ($schemes[7][0], 'netdb-root', ' and the eighth member is correct'); # Create an object. my $server = eval { Wallet::Server->new ('admin@EXAMPLE.COM', 'localhost') }; diff --git a/perl/t/verifier/ldap-attr.t b/perl/t/verifier/ldap-attr.t index 3caaf8b..cff3b63 100755 --- a/perl/t/verifier/ldap-attr.t +++ b/perl/t/verifier/ldap-attr.t @@ -24,16 +24,18 @@ plan skip_all => 'LDAP verifier tests only run for maintainer' unless $ENV{RRA_MAINTAINER_TESTS}; # Declare a plan. -plan tests => 10; +plan tests => 22; require_ok ('Wallet::ACL::LDAP::Attribute'); +require_ok ('Wallet::ACL::LDAP::Attribute::Root'); -my $host = 'ldap.stanford.edu'; -my $base = 'cn=people,dc=stanford,dc=edu'; -my $filter = 'uid'; -my $user = 'jonrober@stanford.edu'; -my $attr = 'suPrivilegeGroup'; -my $value = 'stanford:stanford'; +my $host = 'ldap.stanford.edu'; +my $base = 'cn=people,dc=stanford,dc=edu'; +my $filter = 'uid'; +my $user = 'jonrober@stanford.edu'; +my $rootuser = 'jonrober/root@stanford.edu'; +my $attr = 'suPrivilegeGroup'; +my $value = 'stanford:stanford'; # Remove the realm from principal names. package Wallet::Config; @@ -73,4 +75,25 @@ SKIP: { is ($verifier->check ('user-does-not-exist', "$attr=$value"), 0, "Checking for nonexistent user fails"); is ($verifier->error, undef, '...with no error'); + + # Then also test the root version. + $verifier = eval { Wallet::ACL::LDAP::Attribute::Root->new }; + isa_ok ($verifier, 'Wallet::ACL::LDAP::Attribute::Root'); + is ($verifier->check ($user, "$attr=$value"), 0, + "Checking as a non /root user fails"); + is ($verifier->error, undef, '...with no error'); + is ($verifier->check ($rootuser, "$attr=$value"), 1, + "Checking $attr=$value succeeds"); + is ($verifier->error, undef, '...with no error'); + is ($verifier->check ($rootuser, "$attr=BOGUS"), 0, + "Checking $attr=BOGUS fails"); + is ($verifier->error, undef, '...with no error'); + is ($verifier->check ($rootuser, "BOGUS=$value"), undef, + "Checking BOGUS=$value fails with error"); + is ($verifier->error, + 'cannot check LDAP attribute BOGUS for jonrober: Undefined attribute type', + '...with correct error'); + is ($verifier->check ('user-does-not-exist', "$attr=$value"), 0, + "Checking for nonexistent user fails"); + is ($verifier->error, undef, '...with no error'); } -- cgit v1.2.3 From 23a6b180f975c24c8ee4190467c74b78fde0d084 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 19:29:20 -0800 Subject: Add Wallet::ACL::External ACL type A new ACL type, external (Wallet::ACL::External), is now supported. This ACL runs an external command to check if access is allowed, and passes the principal and the ACL identifier to that command. To enable this ACL type for an existing wallet database, use wallet-admin to register the new verifier. Change-Id: I21b72b4373eefc92985aca1505e2d1a1ec699602 --- NEWS | 6 ++ TODO | 4 + docs/design-acl | 6 ++ perl/lib/Wallet/ACL/External.pm | 197 ++++++++++++++++++++++++++++++++++++++++ perl/lib/Wallet/Config.pm | 35 ++++++- perl/t/data/acl-command | 43 +++++++++ perl/t/verifier/external.t | 32 +++++++ 7 files changed, 321 insertions(+), 2 deletions(-) create mode 100644 perl/lib/Wallet/ACL/External.pm create mode 100755 perl/t/data/acl-command create mode 100755 perl/t/verifier/external.t (limited to 'perl/t/verifier') diff --git a/NEWS b/NEWS index 48ab131..3185b5b 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,12 @@ wallet 1.3 (unreleased) existing wallet database, use wallet-admin to register the new verifier. + A new ACL type, external (Wallet::ACL::External), is now supported. + This ACL runs an external command to check if access is allowed, and + passes the principal and the ACL identifier to that command. To + enable this ACL type for an existing wallet database, use wallet-admin + to register the new verifier. + A new variation on the ldap-attr ACL type, ldap-attr-root (Wallet::ACL::LDAP::Attribute::Root), is now supported. This is similar to netdb-root (compared to netdb): the authenticated principal diff --git a/TODO b/TODO index 24514d8..18b68eb 100644 --- a/TODO +++ b/TODO @@ -121,6 +121,10 @@ ACLs: * Add a comment field to ACLs. + * Support external ACLs under a backend other than remctl. This will + require some way of re-exporting the authenticated user identity + instead of relying on the existence of the remctl variables. + Database: * Fix case-insensitivity bug in unique keys with MySQL for objects. When diff --git a/docs/design-acl b/docs/design-acl index 32ac508..b8bb8b3 100644 --- a/docs/design-acl +++ b/docs/design-acl @@ -50,6 +50,12 @@ Semantics ACL Schemes + external + + The is arguments to an external command. Access is + granted if the external command returns success. The standard remctl + environment variables are exposed to the external command. + krb5 The is a fully-qualified Kerberos principal. Access is diff --git a/perl/lib/Wallet/ACL/External.pm b/perl/lib/Wallet/ACL/External.pm new file mode 100644 index 0000000..da013aa --- /dev/null +++ b/perl/lib/Wallet/ACL/External.pm @@ -0,0 +1,197 @@ +# Wallet::ACL::External -- Wallet external ACL verifier +# +# Written by Russ Allbery +# Copyright 2016 Russ Allbery +# +# See LICENSE for licensing terms. + +############################################################################## +# Modules and declarations +############################################################################## + +package Wallet::ACL::External; +require 5.008; + +use strict; +use warnings; +use vars qw(@ISA $VERSION); + +use Wallet::ACL::Base; +use Wallet::Config; + +@ISA = qw(Wallet::ACL::Base); + +# This version should be increased on any code change to this module. Always +# use two digits for the minor version with a leading zero if necessary so +# that it will sort properly. +$VERSION = '0.01'; + +############################################################################## +# Interface +############################################################################## + +# Creates a new persistent verifier. This just checks if the configuration +# is in place. +sub new { + my $type = shift; + unless ($Wallet::Config::EXTERNAL_COMMAND) { + die "external ACL support not configured\n"; + } + my $self = {}; + bless ($self, $type); + return $self; +} + +# The most trivial ACL verifier. Returns true if the provided principal +# matches the ACL. +sub check { + my ($self, $principal, $acl) = @_; + unless ($principal) { + $self->error ('no principal specified'); + return; + } + my @args = split (' ', $acl); + unshift @args, $principal; + my $pid = open (EXTERNAL, '-|'); + if (not defined $pid) { + $self->error ("cannot fork: $!"); + return; + } elsif ($pid == 0) { + unless (open (STDERR, '>&STDOUT')) { + warn "wallet: cannot dup stdout: $!\n"; + exit 1; + } + unless (exec ($Wallet::Config::EXTERNAL_COMMAND, @args)) { + warn "wallet: cannot run $Wallet::Config::EXTERNAL_COMMAND: $!\n"; + exit 1; + } + } + local $_; + my @output = ; + close EXTERNAL; + if ($? == 0) { + return 1; + } else { + if (@output) { + $self->error ($output[0]); + return; + } else { + return 0; + } + } +} + +1; +__END__ + +############################################################################## +# Documentation +############################################################################## + +=for stopwords +ACL Allbery verifier + +=head1 NAME + +Wallet::ACL::External - Wallet ACL verifier using an external command + +=head1 SYNOPSIS + + my $verifier = Wallet::ACL::External->new; + my $status = $verifier->check ($principal, $acl); + if (not defined $status) { + die "Something failed: ", $verifier->error, "\n"; + } elsif ($status) { + print "Access granted\n"; + } else { + print "Access denied\n"; + } + +=head1 DESCRIPTION + +Wallet::ACL::External runs an external command to determine whether access is +granted. The command configured via $EXTERNAL_COMMAND in L +will be run. The first argument to the command will be the principal +requesting access. The identifier of the ACL will be split on whitespace and +passed in as the remaining arguments to this command. + +No other arguments are passed to the command, but the command will have access +to all of the remctl environment variables seen by the wallet server (such as +REMOTE_USER). For a full list of environment variables, see +L. + +The external command should exit with a non-zero status but no output to +indicate a normal failure to satisfy the ACL. Any output will be treated as +an error. + +=head1 METHODS + +=over 4 + +=item new() + +Creates a new ACL verifier. For this verifier, this just confirms that +the wallet configuration sets an external command. + +=item check(PRINCIPAL, ACL) + +Returns true if the external command returns success when run with that +PRINCIPAL and ACL. ACL will be split on whitespace and passed as multiple +arguments. So, for example, the ACL C will, when +triggered by a request from rra@EXAMPLE.COM, result in the command: + + $Wallet::Config::EXTERNAL_COMMAND rra@EXAMPLE.COM mdbset shell + +=item error() + +Returns the error if check() returned undef. + +=back + +=head1 DIAGNOSTICS + +The new() method may fail with one of the following exceptions: + +=over 4 + +=item external ACL support not configured + +The required configuration parameters were not set. See L +for the required configuration parameters and how to set them. + +=back + +Verifying an external ACL may fail with the following errors (returned by +the error() method): + +=over 4 + +=item cannot fork: %s + +The attempt to fork in order to execute the external ACL verifier +command failed, probably due to a lack of system resources. + +=item no principal specified + +The PRINCIPAL parameter to check() was undefined or the empty string. + +=back + +In addition, if the external command fails and produces some output, +that will be considered a failure and the first line of its output will +be returned as the error message. The external command should exit +with a non-zero status but no error to indicate a normal failure. + +=head1 SEE ALSO + +remctld(8), Wallet::ACL(3), Wallet::ACL::Base(3), Wallet::Config(3), +wallet-backend(8) + +This module is part of the wallet system. The current version is +available from L. + +=head1 AUTHOR + +Russ Allbery + +=cut diff --git a/perl/lib/Wallet/Config.pm b/perl/lib/Wallet/Config.pm index b3e1931..98b5dc9 100644 --- a/perl/lib/Wallet/Config.pm +++ b/perl/lib/Wallet/Config.pm @@ -1,7 +1,8 @@ # Wallet::Config -- Configuration handling for the wallet server. # # Written by Russ Allbery -# Copyright 2007, 2008, 2010, 2013, 2014 +# Copyright 2016 Russ Allbery +# Copyright 2007, 2008, 2010, 2013, 2014, 2015 # The Board of Trustees of the Leland Stanford Junior University # # See LICENSE for licensing terms. @@ -16,7 +17,7 @@ use vars qw($PATH $VERSION); # This version should be increased on any code change to this module. Always # use two digits for the minor version with a leading zero if necessary so # that it will sort properly. -$VERSION = '0.05'; +$VERSION = '0.06'; # Path to the config file to load. $PATH = $ENV{WALLET_CONFIG} || '/etc/wallet/wallet.conf'; @@ -540,6 +541,36 @@ our $WAKEYRING_PURGE_INTERVAL = 60 * 60 * 24 * 90; =back +=head1 EXTERNAL ACL CONFIGURATION + +This configuration variable is only needed if you intend to use the +C ACL type (the Wallet::ACL::External class). This ACL type +runs an external command to determine if access is granted. + +=over 4 + +=item EXTERNAL_COMMAND + +Path to the command to run to determine whether access is granted. The +first argument to the command will be the principal requesting access. +The identifier of the ACL will be split on whitespace and passed in as the +remaining arguments to this command. + +No other arguments are passed to the command, but the command will have +access to all of the remctl environment variables seen by the wallet +server (such as REMOTE_USER). For a full list of environment variables, +see L. + +The external command should exit with a non-zero status but no output to +indicate a normal failure to satisfy the ACL. Any output will be treated +as an error. + +=cut + +our $EXTERNAL_COMMAND; + +=back + =head1 LDAP ACL CONFIGURATION These configuration variables are only needed if you intend to use the diff --git a/perl/t/data/acl-command b/perl/t/data/acl-command new file mode 100755 index 0000000..e368118 --- /dev/null +++ b/perl/t/data/acl-command @@ -0,0 +1,43 @@ +#!/bin/sh +# +# An external ACL implementation. Checks that the first argument is +# eagle@eyrie.org, the second argument is "test", and then returns success, +# failure, or reports an error based on whether the second argument is +# success, failure, or error. +# +# Written by Russ Allbery +# Copyright 2016 Russ Allbery +# +# See LICENSE for licensing terms. + +set -e + +# Check the initial principal argument. +if [ "$1" != 'eagle@eyrie.org' ]; then + echo 'incorrect principal' >&2 + exit 1 +fi + +# Check that the second argument is test. +if [ "$2" != 'test' ]; then + echo 'incorrect second argument' >&2 + exit 1 +fi + +# Process the third argument. +case $3 in + success) + exit 0 + ;; + failure) + exit 1 + ;; + error) + echo 'some error' >&2 + exit 1 + ;; + *) + echo 'unknown third argument' >&2 + exit 1 + ;; +esac diff --git a/perl/t/verifier/external.t b/perl/t/verifier/external.t new file mode 100755 index 0000000..3e7e776 --- /dev/null +++ b/perl/t/verifier/external.t @@ -0,0 +1,32 @@ +#!/usr/bin/perl +# +# Tests for the external wallet ACL verifier. +# +# Written by Russ Allbery +# Copyright 2016 Russ Allbery +# +# See LICENSE for licensing terms. + +use strict; +use warnings; + +use Test::More tests => 9; + +use Wallet::ACL::External; +use Wallet::Config; + +# Configure the external ACL verifier. +$Wallet::Config::EXTERNAL_COMMAND = 't/data/acl-command'; + +# Check a few verifications. +my $verifier = Wallet::ACL::External->new; +ok (defined $verifier, 'Wallet::ACL::External creation'); +ok ($verifier->isa ('Wallet::ACL::External'), ' and class verification'); +is ($verifier->check ('eagle@eyrie.org', 'test success'), 1, 'Success'); +is ($verifier->check ('eagle@eyrie.org', 'test failure'), 0, 'Failure'); +is ($verifier->error, undef, 'No error set'); +is ($verifier->check ('eagle@eyrie.org', 'test error'), undef, 'Error'); +is ($verifier->error, 'some error', ' and right error'); +is ($verifier->check (undef, 'eagle@eyrie.org'), undef, + 'Undefined principal'); +is ($verifier->error, 'no principal specified', ' and right error'); -- cgit v1.2.3 From d2fde5b8330cab6bd6210ef99a628b1897676897 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sat, 16 Jan 2016 15:34:22 -0800 Subject: Pass object type and name to external ACL verifiers This requires changing the ACL verifier plumbing to pass object type and name all the way through when verifying ACLs. Hopefully I caught everything. --- NEWS | 6 +++--- docs/design-acl | 7 ++++--- perl/lib/Wallet/ACL.pm | 20 +++++++++++--------- perl/lib/Wallet/ACL/Base.pm | 6 ++++-- perl/lib/Wallet/ACL/External.pm | 16 ++++++++-------- perl/lib/Wallet/ACL/Nested.pm | 7 ++++--- perl/lib/Wallet/Config.pm | 8 ++++---- perl/t/data/acl-command | 20 ++++++++++++-------- perl/t/verifier/external.t | 11 +++++++---- 9 files changed, 57 insertions(+), 44 deletions(-) (limited to 'perl/t/verifier') diff --git a/NEWS b/NEWS index eee61bd..aa9cf47 100644 --- a/NEWS +++ b/NEWS @@ -11,9 +11,9 @@ wallet 1.3 (unreleased) A new ACL type, external (Wallet::ACL::External), is now supported. This ACL runs an external command to check if access is allowed, and - passes the principal and the ACL identifier to that command. To - enable this ACL type for an existing wallet database, use wallet-admin - to register the new verifier. + passes the principal, type and name of the object, and the ACL + identifier to that command. To enable this ACL type for an existing + wallet database, use wallet-admin to register the new verifier. A new variation on the ldap-attr ACL type, ldap-attr-root (Wallet::ACL::LDAP::Attribute::Root), is now supported. This is diff --git a/docs/design-acl b/docs/design-acl index b8bb8b3..836c411 100644 --- a/docs/design-acl +++ b/docs/design-acl @@ -31,9 +31,10 @@ Semantics used: Iterate through each ACL entry in the ACL in question. If the ACL entry is malformatted or the scheme is not recognized, skip it. Otherwise, dispatch the question to the check function of the ACL - implementation, passing it the principal identifying the client and - the portion of the ACL entry. This function returns - either authorized or unauthorized. If authorized, end the search; if + implementation, passing it the principal identifying the client, the + portion of the ACL entry, and the type and name of the + object the user is attempting to access. This function returns either + authorized or unauthorized. If authorized, end the search; if unauthorized, continue to the next ACL entry. There is no support in this scheme for negative ACLs. diff --git a/perl/lib/Wallet/ACL.pm b/perl/lib/Wallet/ACL.pm index 862b88f..69e6890 100644 --- a/perl/lib/Wallet/ACL.pm +++ b/perl/lib/Wallet/ACL.pm @@ -480,7 +480,7 @@ sub history { { my %verifier; sub check_line { - my ($self, $principal, $scheme, $identifier) = @_; + my ($self, $principal, $scheme, $identifier, $type, $name) = @_; unless ($verifier{$scheme}) { my $class = $self->scheme_mapping ($scheme); unless ($class) { @@ -493,7 +493,8 @@ sub history { return; } } - my $result = ($verifier{$scheme})->check ($principal, $identifier); + my $result = ($verifier{$scheme})->check ($principal, $identifier, + $type, $name); if (not defined $result) { push (@{ $self->{check_errors} }, ($verifier{$scheme})->error); return; @@ -503,13 +504,13 @@ sub history { } } -# Given a principal, check whether it should be granted access according to -# this ACL. Returns 1 if access was granted, 0 if access was denied, and -# undef on some error. Errors from ACL verifiers do not cause an error -# return, but are instead accumulated in the check_errors variable returned by -# the check_errors() method. +# Given a principal, object type, and object name, check whether that +# principal should be granted access according to this ACL. Returns 1 if +# access was granted, 0 if access was denied, and undef on some error. Errors +# from ACL verifiers do not cause an error return, but are instead accumulated +# in the check_errors variable returned by the check_errors() method. sub check { - my ($self, $principal) = @_; + my ($self, $principal, $type, $name) = @_; unless ($principal) { $self->error ('no principal specified'); return; @@ -520,7 +521,8 @@ sub check { $self->{check_errors} = []; for my $entry (@entries) { my ($scheme, $identifier) = @$entry; - my $result = $self->check_line ($principal, $scheme, $identifier); + my $result = $self->check_line ($principal, $scheme, $identifier, + $type, $name); return 1 if $result; } return 0; diff --git a/perl/lib/Wallet/ACL/Base.pm b/perl/lib/Wallet/ACL/Base.pm index 19ca612..3778c07 100644 --- a/perl/lib/Wallet/ACL/Base.pm +++ b/perl/lib/Wallet/ACL/Base.pm @@ -103,10 +103,12 @@ This method should be overridden by any child classes that want to implement validating the name of an ACL before creation. The default implementation allows any name for an ACL. -=item check(PRINCIPAL, ACL) +=item check(PRINCIPAL, ACL, TYPE, NAME) This method should always be overridden by child classes. The default -implementation just declines all access. +implementation just declines all access. TYPE and NAME are the type and +name of the object being accessed, which may be used by some ACL schemes +or may be ignored. =item error([ERROR ...]) diff --git a/perl/lib/Wallet/ACL/External.pm b/perl/lib/Wallet/ACL/External.pm index 77c2499..f1bd577 100644 --- a/perl/lib/Wallet/ACL/External.pm +++ b/perl/lib/Wallet/ACL/External.pm @@ -46,13 +46,12 @@ sub new { # The most trivial ACL verifier. Returns true if the provided principal # matches the ACL. sub check { - my ($self, $principal, $acl) = @_; + my ($self, $principal, $acl, $type, $name) = @_; unless ($principal) { $self->error ('no principal specified'); return; } - my @args = split (' ', $acl); - unshift @args, $principal; + my @args = ($principal, $type, $name, $acl); my $pid = open (EXTERNAL, '-|'); if (not defined $pid) { $self->error ("cannot fork: $!"); @@ -134,14 +133,15 @@ an error. Creates a new ACL verifier. For this verifier, this just confirms that the wallet configuration sets an external command. -=item check(PRINCIPAL, ACL) +=item check(PRINCIPAL, ACL, TYPE, NAME) Returns true if the external command returns success when run with that -PRINCIPAL and ACL. ACL will be split on whitespace and passed as multiple -arguments. So, for example, the ACL C will, when -triggered by a request from rra@EXAMPLE.COM, result in the command: +PRINCIPAL, object TYPE and NAME, and ACL. So, for example, the ACL C will, when triggered by a request from rra@EXAMPLE.COM for the +object C, result in the command: - $Wallet::Config::EXTERNAL_COMMAND rra@EXAMPLE.COM mdbset shell + $Wallet::Config::EXTERNAL_COMMAND rra@EXAMPLE.COM file password \ + 'mdbset shell' =item error() diff --git a/perl/lib/Wallet/ACL/Nested.pm b/perl/lib/Wallet/ACL/Nested.pm index 07833f8..3b6c827 100644 --- a/perl/lib/Wallet/ACL/Nested.pm +++ b/perl/lib/Wallet/ACL/Nested.pm @@ -59,7 +59,7 @@ sub syntax_check { # that entry. We also want to keep track of things already checked in order # to avoid any loops. sub check { - my ($self, $principal, $group) = @_; + my ($self, $principal, $group, $type, $name) = @_; unless ($principal) { $self->error ('no principal specified'); return; @@ -78,8 +78,9 @@ sub check { # to go through each entry and decide if the given acl has access. my @members = $self->get_membership ($group); for my $entry (@members) { - my ($type, $name) = @{ $entry }; - my $result = $acl->check_line ($principal, $type, $name); + my ($scheme, $identifier) = @{ $entry }; + my $result = $acl->check_line ($principal, $scheme, $identifier, + $type, $name); return 1 if $result; } return 0; diff --git a/perl/lib/Wallet/Config.pm b/perl/lib/Wallet/Config.pm index 98b5dc9..e8bc00c 100644 --- a/perl/lib/Wallet/Config.pm +++ b/perl/lib/Wallet/Config.pm @@ -551,10 +551,10 @@ runs an external command to determine if access is granted. =item EXTERNAL_COMMAND -Path to the command to run to determine whether access is granted. The -first argument to the command will be the principal requesting access. -The identifier of the ACL will be split on whitespace and passed in as the -remaining arguments to this command. +Path to the command to run to determine whether access is granted. The first +argument to the command will be the principal requesting access. The second +and third arguments will be the type and name of the object that principal is +requesting access to. The final argument will be the identifier of the ACL. No other arguments are passed to the command, but the command will have access to all of the remctl environment variables seen by the wallet diff --git a/perl/t/data/acl-command b/perl/t/data/acl-command index e368118..b7c3066 100755 --- a/perl/t/data/acl-command +++ b/perl/t/data/acl-command @@ -18,26 +18,30 @@ if [ "$1" != 'eagle@eyrie.org' ]; then exit 1 fi -# Check that the second argument is test. -if [ "$2" != 'test' ]; then +# Check that the second and third arguments are file test (the test object). +if [ "$2" != 'file' ]; then echo 'incorrect second argument' >&2 exit 1 fi +if [ "$3" != 'test' ]; then + echo 'incorrect third argument' >&2 + exit 1 +fi -# Process the third argument. -case $3 in - success) +# Process the fourth argument. +case $4 in + 'test success') exit 0 ;; - failure) + 'test failure') exit 1 ;; - error) + 'test error') echo 'some error' >&2 exit 1 ;; *) - echo 'unknown third argument' >&2 + echo 'unknown fourth argument' >&2 exit 1 ;; esac diff --git a/perl/t/verifier/external.t b/perl/t/verifier/external.t index 3e7e776..d1438de 100755 --- a/perl/t/verifier/external.t +++ b/perl/t/verifier/external.t @@ -22,11 +22,14 @@ $Wallet::Config::EXTERNAL_COMMAND = 't/data/acl-command'; my $verifier = Wallet::ACL::External->new; ok (defined $verifier, 'Wallet::ACL::External creation'); ok ($verifier->isa ('Wallet::ACL::External'), ' and class verification'); -is ($verifier->check ('eagle@eyrie.org', 'test success'), 1, 'Success'); -is ($verifier->check ('eagle@eyrie.org', 'test failure'), 0, 'Failure'); +is ($verifier->check ('eagle@eyrie.org', 'test success', 'file', 'test'), + 1, 'Success'); +is ($verifier->check ('eagle@eyrie.org', 'test failure', 'file', 'test'), + 0, 'Failure'); is ($verifier->error, undef, 'No error set'); -is ($verifier->check ('eagle@eyrie.org', 'test error'), undef, 'Error'); +is ($verifier->check ('eagle@eyrie.org', 'test error', 'file', 'test'), + undef, 'Error'); is ($verifier->error, 'some error', ' and right error'); -is ($verifier->check (undef, 'eagle@eyrie.org'), undef, +is ($verifier->check (undef, 'eagle@eyrie.org', 'file', 'test'), undef, 'Undefined principal'); is ($verifier->error, 'no principal specified', ' and right error'); -- cgit v1.2.3