summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuss Allbery <eagle@eyrie.org>2016-01-03 19:29:20 -0800
committerRuss Allbery <eagle@eyrie.org>2016-01-03 19:29:20 -0800
commit23a6b180f975c24c8ee4190467c74b78fde0d084 (patch)
treea729417aa495ad72256d567b04cf8a0601bfa95f
parent99c718eff041657704a50589486bde2f9e4391f7 (diff)
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
-rw-r--r--NEWS6
-rw-r--r--TODO4
-rw-r--r--docs/design-acl6
-rw-r--r--perl/lib/Wallet/ACL/External.pm197
-rw-r--r--perl/lib/Wallet/Config.pm35
-rwxr-xr-xperl/t/data/acl-command43
-rwxr-xr-xperl/t/verifier/external.t32
7 files changed, 321 insertions, 2 deletions
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 <identifier> 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 <identifier> 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 <eagle@eyrie.org>
+# Copyright 2016 Russ Allbery <eagle@eyrie.org>
+#
+# 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 = <EXTERNAL>;
+ 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<Wallet::Config>
+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<remctld(8)/ENVIRONMENT>.
+
+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<external mdbset shell> 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<Wallet::Config>
+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<http://www.eyrie.org/~eagle/software/wallet/>.
+
+=head1 AUTHOR
+
+Russ Allbery <eagle@eyrie.org>
+
+=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 <eagle@eyrie.org>
-# Copyright 2007, 2008, 2010, 2013, 2014
+# Copyright 2016 Russ Allbery <eagle@eyrie.org>
+# 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<external> 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<remctld(8)/ENVIRONMENT>.
+
+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 <eagle@eyrie.org>
+# Copyright 2016 Russ Allbery <eagle@eyrie.org>
+#
+# 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 <eagle@eyrie.org>
+# Copyright 2016 Russ Allbery <eagle@eyrie.org>
+#
+# 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');