diff options
author | Russ Allbery <rra@stanford.edu> | 2007-09-01 00:34:23 +0000 |
---|---|---|
committer | Russ Allbery <rra@stanford.edu> | 2007-09-01 00:34:23 +0000 |
commit | 9df0def5c5d518f5415ea5fbae737c16670a8249 (patch) | |
tree | 4c0f41e0f70e17b6b10206824a4b4b249020f6f6 | |
parent | 488e404129536d797583b432eaf1c2ec40df366f (diff) |
Add some additional safeguards to ensure that users cannot destroy,
rename, or remove the last entry from the ADMIN ACL. Add full
documentation for Wallet::Server.
-rw-r--r-- | perl/Wallet/Server.pm | 255 | ||||
-rwxr-xr-x | perl/t/server.t | 12 |
2 files changed, 266 insertions, 1 deletions
diff --git a/perl/Wallet/Server.pm b/perl/Wallet/Server.pm index 8cbc139..420f044 100644 --- a/perl/Wallet/Server.pm +++ b/perl/Wallet/Server.pm @@ -401,6 +401,10 @@ sub acl_rename { $self->error ($@); return undef; } + if ($acl->name eq 'ADMIN') { + $self->error ('cannot rename the ADMIN ACL'); + return undef; + } unless ($acl->rename ($name)) { $self->error ($acl->error); return undef; @@ -421,6 +425,10 @@ sub acl_destroy { $self->error ($@); return undef; } + if ($acl->name eq 'ADMIN') { + $self->error ('cannot destroy the ADMIN ACL'); + return undef; + } unless ($acl->destroy ($self->{user}, $self->{host})) { $self->error ($acl->error); return undef; @@ -461,6 +469,16 @@ sub acl_remove { $self->error ($@); return undef; } + if ($acl->name eq 'ADMIN') { + my @e = $acl->list; + if (@e == 1 and not defined ($e[0])) { + $self->error ($acl->error); + return undef; + } elsif (@e == 1 && $e[0][0] eq $scheme && $e[0][1] eq $identifier) { + $self->error ('cannot remove last ADMIN ACL entry'); + return undef; + } + } my $user = $self->{user}; my $host = $self->{host}; unless ($acl->remove ($scheme, $identifier, $user, $host)) { @@ -472,3 +490,240 @@ sub acl_remove { 1; __END__ + +############################################################################## +# Documentation +############################################################################## + +=head1 NAME + +Wallet::Server - Wallet system server implementation + +=head1 SYNOPSIS + + use Wallet::Server; + my $server = Wallet::Server->new ($user, $host); + $server->create ('keytab', 'host/example.com@EXAMPLE.COM'); + +=head1 DESCRIPTION + +Wallet::Server is the top-level class that implements the wallet server. +The wallet is a system for storing, generating, and retrieving secure +information such as Kerberos keytabs. The server maintains metadata about +the objects, checks access against ACLs, and dispatches requests for objects +to backend implementations for that object type. + +Wallet::Server is normally instantiated and used by B<wallet-backend>, a +thin wrapper around this object that determines the authenticated remote +user and gets user input and then calls the appropriate method of this +object. + +To use this object, several configuration variables must be set (at least +the database configuration). For information on those variables and how to +set them, see Wallet::Config(3). + +=head1 CLASS METHODS + +=over 4 + +=item initialize(PRINCIPAL) + +Initializes the database as configured in Wallet::Config and loads the +wallet database schema. Then, creates an ACL with the name ADMIN and adds +an ACL entry of scheme C<krb5> and instance PRINCIPAL to that ACL. This +bootstraps the authorization system and lets that Kerberos identity make +further changes to the ADMIN ACL and the rest of the wallet database. +Returns a new Wallet::Server object, although that object should only be +used to do other administrative functions. Before performing normal +operations, that object should be destroyed and the database reopened with +new(). initialize() uses C<localhost> as the hostname and PRINCIPAL as the +user when logging the history of the ADMIN ACL creation and for any +subsequent actions on the object it returns. + +On any error, this method throws an exception. + +=item new(PRINCIPAL, HOSTNAME) + +Creates a new wallet server object for actions from the user PRINCIPAL +connecting from HOSTNAME. PRINCIPAL and HOSTNAME will be used for logging +history information for all subsequent operations. new() opens the +database, using the database configuration as set by Wallet::Config and +ensures that the C<ADMIN> ACL exists. That ACL will be used to authorize +privileged operations. + +On any error, this method throws an exception. + +=back + +=head1 INSTANCE METHODS + +For all methods that can fail, the caller should call error() after a +failure to get the error message. + +=over 4 + +=item acl(TYPE, NAME, ACL [, ID]) + +Gets or sets the ACL type ACL to ID for the object identified by TYPE and +NAME. ACL should be one of C<get>, C<store>, C<show>, C<destroy>, or +C<flags>. If ID is not given, returns the current setting of that ACL as a +numeric ACL ID or undef if that ACL isn't set or on failure. To distinguish +between an ACL that isn't set and a failure to retrieve the ACL, the caller +should call error() after an undef return. If error() also returns undef, +that ACL wasn't set; otherwise, error() will return the error message. + +If ID is given, sets the specified ACL to ID, which can be either the name +of an ACL or a numeric ACL ID. To set an ACL, the current user must be +authorized by the ADMIN ACL. Returns true for success and false for +failure. + +ACL settings are checked before the owner and override the owner setting. + +=item acl_add(ID, SCHEME, IDENTIFIER) + +Adds an ACL entry with scheme SCHEME and identifier IDENTIFIER to the ACL +identified by ID. ID may be either the ACL name or the numeric ACL ID. +SCHEME must be a valid ACL scheme for which the wallet system has an ACL +verifier implementation. To add an entry to an ACL, the current user must +be authorized by the ADMIN ACL. Returns true for success and false for +failure. + +=item acl_create(NAME) + +Create a new ACL with the specified NAME, which must not be all-numeric. +The newly created ACL will be empty. To create an ACL, the current user +must be authorized by the ADMIN ACL. Returns true on success and false on +failure. + +=item acl_destroy(ID) + +Destroys the ACL identified by ID, which may be either the ACL name or its +numeric ID. This call will fail if the ACL is still referenced by any +object. The ADMIN ACL may not be destroyed. To destroy an ACL, the current +user must be authorized by the ADMIN ACL. Returns true on success and false +on failure. + +=item acl_remove(ID, SCHEME, IDENTIFIER) + +Removes from the ACL identified by ID the entry matching SCHEME and +IDENTIFIER. ID may be either the name of the ACL or its numeric ID. The +last entry in the ADMIN ACL cannot be removed. To remove an entry from an +ACL, the current user must be authorized by the ADMIN ACL. Returns true on +success and false on failure. + +=item acl_rename(OLD, NEW) + +Renames the ACL identified by OLD to NEW. This changes the human-readable +name, not the underlying numeric ID, so the ACL's associations with objects +will be unchanged. The ADMIN ACL may not be renamed. OLD may be either the +current name or the numeric ID. NEW must not be all-numeric. To rename an +ACL, the current user must be authorized by the ADMIN ACL. Returns true on +success and false on failure. + +=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 +on success and false on failure. + +=item destroy(TYPE, NAME) + +Destroys the object identified by TYPE and NAME. This destroys any data +that the wallet had saved about the object, may remove the underlying object +from other external systems, and destroys the wallet database entry for the +object. To destroy an object, the current user must be authorized by the +ADMIN ACL or the destroy ACL on the object; the owner ACL is not sufficient. +Returns true on success and false on failure. + +=item dbh() + +Returns the database handle of a Wallet::Server object. This is used mostly +for testing; normally, clients should perform all actions through the +Wallet::Server object to ensure that authorization and history logging is +done properly. + +=item error() + +Returns the error of the last failing operation or undef if no operations +have failed. Callers should call this function to get the error message +after an undef return from any other instance method. + +=item expires(TYPE, NAME [, EXPIRES]) + +Gets or sets the expiration for the object identified by TYPE and NAME. If +EXPIRES is not given, returns the current expiration or undef if no +expiration is set or on an error. To distinguish between an expiration that +isn't set and a failure to retrieve the expiration, the caller should call +error() after an undef return. If error() also returns undef, that ACL +wasn't set; otherwise, error() will return the error message. + +If EXPIRES is given, sets the expiration to EXPIRES, which should be in +seconds since epoch. To set an expiration, the current user must be +authorized by the ADMIN ACL. Returns true for success and false for +failure. + +=item get(TYPE, NAME) + +Returns the data associated with the object identified by TYPE and NAME. +Depending on the object TYPE, this may generate new data and invalidate any +existing data or it may return data previously stored or generated. Note +that this data may be binary and may contain nul characters. To get an +object, the current user must either be authorized by the owner ACL or +authorized by the get ACL; however, if the get ACL is set, the owner ACL +will not be checked. Being a member of the ADMIN ACL does not provide any +special privileges to get objects. + +Returns undef on failure. The caller should be careful to distinguish +between undef and the empty string, which is valid object data. + +=item owner(TYPE, NAME [, OWNER]) + +Gets or sets the owner for the object identified by TYPE and NAME. If OWNER +is not given, returns the current owner as a numeric ACL ID or undef if no +owner is set or on an error. To distinguish between an owner that isn't set +and a failure to retrieve the owner, the caller should call error() after an +undef return. If error() also returns undef, that ACL wasn't set; +otherwise, error() will return the error message. + +If OWNER is given, sets the owner to OWNER, which may be either the name of +an ACL or a numeric ACL ID. To set an owner, the current user must be +authorized by the ADMIN ACL. Returns true for success and false for +failure. + +The owner of an object is permitted to get, store, and show that object, but +cannot destroy or set flags on that object without being listed on those +ACLs as well. + +=item show(TYPE, NAME) + +Returns (as a string) a human-readable representation of the metadata stored +for the object identified by TYPE and NAME, or undef on error. To show an +object, the current user must be a member of the ADMIN ACL, authorized by +the show ACL, or authorized by the owner ACL; however, if the show ACL is +set, the owner ACL will not be checked. + +=item store(TYPE, NAME, DATA) + +Stores DATA for the object identified with TYPE and NAME for later retrieval +with get. Note that DATA may be binary and may contain nul characters. To +store an object, the current user must either be authorized by the owner ACL +or authorized by the store ACL; however, if the store ACL is set, the owner +ACL is not checked. Being a member of the ADMIN ACL does not provide any +special privileges to store objects. Returns true on success and false on +failure. + +=back + +=head1 SEE ALSO + +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 <rra@stanford.edu> + +=cut diff --git a/perl/t/server.t b/perl/t/server.t index bca035e..5265c2d 100755 --- a/perl/t/server.t +++ b/perl/t/server.t @@ -3,7 +3,7 @@ # # t/server.t -- Tests for the wallet server API. -use Test::More tests => 201; +use Test::More tests => 207; use Wallet::Config; use Wallet::Server; @@ -96,6 +96,16 @@ is ($server->error, "cannot remove krb5:$user1 from 5: entry not found in ACL", ' and returns the right error'); +# Make sure we can't cripple the ADMIN ACL. +is ($server->acl_destroy ('ADMIN'), undef, 'Cannot destroy the ADMIN ACL'); +is ($server->error, 'cannot destroy the ADMIN ACL', ' with the right error'); +is ($server->acl_rename ('ADMIN', 'foo'), undef, ' or rename it'); +is ($server->error, 'cannot rename the ADMIN ACL', ' with the right error'); +is ($server->acl_remove ('ADMIN', 'krb5', $admin), undef, + ' or remove its last entry'); +is ($server->error, 'cannot remove last ADMIN ACL entry', + ' with the right error'); + # Now, create a few objects to use for testing and test the object API while # we're at it. is ($server->create ('base', 'service/admin'), 1, |