diff options
author | Russ Allbery <eagle@eyrie.org> | 2018-06-03 16:58:02 -0700 |
---|---|---|
committer | Russ Allbery <eagle@eyrie.org> | 2018-06-03 16:58:02 -0700 |
commit | edf31eba414d9a105791c076fb1444a78d210dff (patch) | |
tree | 2bac18fa3b71593e616061a0fbcbfdd6ab26a255 /server/wallet-backend.in | |
parent | 4b3f858ef567c0d12511e7fea2a56f08f2729635 (diff) | |
parent | 68c4b05c268cd6e358cc41c8feb44bc2c7fcb898 (diff) |
New upstream version 1.4
Diffstat (limited to 'server/wallet-backend.in')
-rw-r--r-- | server/wallet-backend.in | 702 |
1 files changed, 702 insertions, 0 deletions
diff --git a/server/wallet-backend.in b/server/wallet-backend.in new file mode 100644 index 0000000..8e38460 --- /dev/null +++ b/server/wallet-backend.in @@ -0,0 +1,702 @@ +#!@PERL@ +# -*- perl -*- +# +# Wallet server for storing and retrieving secure data. + +use 5.008; +use strict; +use warnings; + +use Getopt::Long qw(GetOptions); +use Sys::Syslog qw(openlog syslog); +use Wallet::Server; + +# Set to zero to suppress syslog logging, which is used for testing and for +# the -q option. Set to a reference to a string to append messages to that +# string instead. +our $SYSLOG; +$SYSLOG = 1 unless defined $SYSLOG; + +############################################################################## +# Logging +############################################################################## + +# Initialize logging. +sub log_init { + if (ref $SYSLOG) { + $$SYSLOG = ''; + } elsif ($SYSLOG) { + openlog ('wallet-backend', 'pid', 'auth'); + } +} + +# Get an identity string for the user suitable for including in log messages. +sub identity { + my $identity = ''; + if ($ENV{REMOTE_USER}) { + $identity = $ENV{REMOTE_USER}; + my $host = $ENV{REMOTE_HOST} || $ENV{REMOTE_ADDR}; + $identity .= " ($host)" if $host; + } + return $identity; +} + +# Log an error message to both syslog and to stderr and exit with a non-zero +# status. +sub error { + my $message = join ('', @_); + if ($SYSLOG) { + my $identity = identity; + my $log; + if ($identity) { + $log = "error for $identity: $message"; + } else { + $log = "error: $message"; + } + $log =~ s/[^\x20-\x7e]/_/g; + if (ref $SYSLOG) { + $$SYSLOG .= "$log\n"; + } else { + syslog ('err', "%s", $log); + } + } + die "$message\n"; +} + +# Log a wallet failure message for a given command to both syslog and to +# stderr and exit with a non-zero status. Takes the message and the command +# that was being run. +sub failure { + my ($message, @command) = @_; + if ($SYSLOG) { + my $log = "command @command from " . identity . " failed: $message"; + $log =~ s/[^\x20-\x7e]/_/g; + if (ref $SYSLOG) { + $$SYSLOG .= "$log\n"; + } else { + syslog ('err', "%s", $log); + } + } + die "$message\n"; +} + +# Log a wallet success message for a given command. +sub success { + my (@command) = @_; + if ($SYSLOG) { + my $log = "command @command from " . identity . " succeeded"; + $log =~ s/[^\x20-\x7e]/_/g; + if (ref $SYSLOG) { + $$SYSLOG .= "$log\n"; + } else { + syslog ('info', "%s", $log); + } + } +} + +############################################################################## +# Parameter checking +############################################################################## + +# Check all arguments against a very restricted set of allowed characters and +# to ensure the right number of arguments are taken. The arguments are the +# number of arguments expected (minimum and maximum), a reference to an array +# of which argument numbers shouldn't be checked, and then the arguments. +# +# This function is probably temporary and will be replaced with something that +# knows more about the syntax of each command and can check more things. +sub check_args { + my ($min, $max, $exclude, @args) = @_; + if (@args < $min) { + error "insufficient arguments"; + } elsif (@args > $max and $max != -1) { + error "too many arguments"; + } + my %exclude = map { $_ => 1 } @$exclude; + for (my $i = 1; $i <= @args; $i++) { + next if $exclude{$i}; + unless ($args[$i - 1] =~ m,^[\w_/\@.-]*\z,) { + error "invalid characters in argument: $args[$i - 1]"; + } + } +} + +############################################################################## +# Implementation +############################################################################## + +# Parse and execute a command. We wrap this in a subroutine call for easier +# testing. +sub command { + log_init; + my $user = $ENV{REMOTE_USER} or error "REMOTE_USER not set"; + my $host = $ENV{REMOTE_HOST} || $ENV{REMOTE_ADDR} + or error "neither REMOTE_HOST nor REMOTE_ADDR set"; + + # Instantiate the server object. + my $server = Wallet::Server->new ($user, $host); + + # Parse command-line options and dispatch to the appropriate calls. + my ($command, @args) = @_; + if ($command eq 'acl') { + my $action = shift @args; + if ($action eq 'add') { + check_args (3, 3, [3], @args); + $server->acl_add (@args) or failure ($server->error, @_); + } elsif ($action eq 'check') { + check_args (1, 1, [], @args); + my $status = $server->acl_check (@args); + if (!defined ($status)) { + failure ($server->error, @_); + } else { + print $status ? "yes\n" : "no\n"; + } + } elsif ($action eq 'create') { + check_args (1, 1, [], @args); + $server->acl_create (@args) or failure ($server->error, @_); + } elsif ($action eq 'destroy') { + check_args (1, 1, [], @args); + $server->acl_destroy (@args) or failure ($server->error, @_); + } elsif ($action eq 'history') { + check_args (1, 1, [], @args); + my $output = $server->acl_history (@args); + if (defined $output) { + print $output; + } else { + failure ($server->error, @_); + } + } elsif ($action eq 'remove') { + check_args (3, 3, [3], @args); + $server->acl_remove (@args) or failure ($server->error, @_); + } elsif ($action eq 'rename') { + check_args (2, 2, [], @args); + $server->acl_rename (@args) or failure ($server->error, @_); + } elsif ($action eq 'replace') { + check_args (2, 2, [], @args); + $server->acl_replace (@args) or failure ($server->error, @_); + } elsif ($action eq 'show') { + check_args (1, 1, [], @args); + my $output = $server->acl_show (@args); + if (defined $output) { + print $output; + } else { + failure ($server->error, @_); + } + } else { + error "unknown command acl $action"; + } + } elsif ($command eq 'autocreate') { + check_args (2, 2, [], @args); + $server->autocreate (@args) or failure ($server->error, @_); + } elsif ($command eq 'check') { + check_args (2, 2, [], @args); + my $status = $server->check (@args); + if (!defined ($status)) { + failure ($server->error, @_); + } else { + print $status ? "yes\n" : "no\n"; + } + } elsif ($command eq 'comment') { + check_args (2, 3, [3], @args); + if (@args > 2) { + $server->comment (@args) or failure ($server->error, @_); + } else { + my $output = $server->comment (@args); + if (defined $output) { + print $output, "\n"; + } elsif (not $server->error) { + print "No comment set\n"; + } else { + failure ($server->error, @_); + } + } + } elsif ($command eq 'create') { + check_args (2, 2, [], @args); + $server->create (@args) or failure ($server->error, @_); + } elsif ($command eq 'destroy') { + check_args (2, 2, [], @args); + $server->destroy (@args) or failure ($server->error, @_); + } elsif ($command eq 'expires') { + check_args (2, 3, [], @args); + if (@args > 2) { + $server->expires (@args) or failure ($server->error, @_); + } else { + my $output = $server->expires (@args); + if (defined $output) { + print $output, "\n"; + } elsif (not $server->error) { + print "No expiration set\n"; + } else { + failure ($server->error, @_); + } + } + } elsif ($command eq 'flag') { + my $action = shift @args; + check_args (3, 3, [], @args); + if ($action eq 'clear') { + $server->flag_clear (@args) or failure ($server->error, @_); + } elsif ($action eq 'set') { + $server->flag_set (@args) or failure ($server->error, @_); + } else { + error "unknown command flag $action"; + } + } elsif ($command eq 'get') { + check_args (2, 2, [], @args); + my $output = $server->get (@args); + if (defined $output) { + print $output; + } else { + failure ($server->error, @_); + } + } elsif ($command eq 'getacl') { + check_args (3, 3, [], @args); + my $output = $server->acl (@args); + if (defined $output) { + print $output, "\n"; + } elsif (not $server->error) { + print "No ACL set\n"; + } else { + failure ($server->error, @_); + } + } elsif ($command eq 'getattr') { + check_args (3, 3, [], @args); + my @result = $server->attr (@args); + if (not @result and $server->error) { + failure ($server->error, @_); + } elsif (@result) { + print join ("\n", @result, ''); + } + } elsif ($command eq 'history') { + check_args (2, 2, [], @args); + my $output = $server->history (@args); + if (defined $output) { + print $output; + } else { + failure ($server->error, @_); + } + } elsif ($command eq 'owner') { + check_args (2, 3, [], @args); + if (@args > 2) { + $server->owner (@args) or failure ($server->error, @_); + } else { + my $output = $server->owner (@args); + if (defined $output) { + print $output, "\n"; + } elsif (not $server->error) { + print "No owner set\n"; + } else { + failure ($server->error, @_); + } + } + } elsif ($command eq 'rename') { + check_args (3, 3, [], @args); + $server->rename (@args) or failure ($server->error, @_); + } elsif ($command eq 'setacl') { + check_args (4, 4, [], @args); + $server->acl (@args) or failure ($server->error, @_); + } elsif ($command eq 'setattr') { + check_args (4, -1, [], @args); + $server->attr (@args) or failure ($server->error, @_); + } elsif ($command eq 'show') { + check_args (2, 2, [], @args); + my $output = $server->show (@args); + if (defined $output) { + print $output; + } else { + failure ($server->error, @_); + } + } elsif ($command eq 'store') { + check_args (2, 3, [3], @args); + if (@args == 2) { + local $/; + $args[2] = <STDIN>; + } + splice (@_, 3); + $server->store (@args) or failure ($server->error, @_); + } elsif ($command eq 'update') { + check_args (2, 2, [], @args); + my $output = $server->update (@args); + if (defined $output) { + print $output; + } else { + failure ($server->error, @_); + } + } else { + error "unknown command $command"; + } + success (@_); +} + +# Parse command-line options. +my ($quiet); +Getopt::Long::config ('require_order'); +GetOptions ('q|quiet' => \$quiet) or exit 1; +$SYSLOG = 0 if $quiet; + +# Run the command. +command (@ARGV); + +__END__ + +############################################################################## +# Documentation +############################################################################## + +# The commands section of this document is duplicated from the documentation +# for wallet and should be kept in sync. + +=for stopwords +wallet-backend backend backend-specific remctld ACL acl timestamp getacl +setacl metadata keytab keytabs enctypes enctype ktadd KDC Allbery autocreate +MERCHANTABILITY NONINFRINGEMENT sublicense SPDX-License-Identifier MIT + +=head1 NAME + +wallet-backend - Wallet server for storing and retrieving secure data + +=head1 SYNOPSIS + +B<wallet-backend> [B<-q>] I<command> [I<args> ...] + +=head1 DESCRIPTION + +B<wallet-backend> implements the interface between B<remctld> and the +wallet system. It is written to run under B<remctld> and expects the +authenticated identity of the remote user in the REMOTE_USER environment +variable. It uses REMOTE_HOST or REMOTE_ADDR if REMOTE_HOST isn't set for +additional trace information. It accepts the command from B<remctld> on +the command line, creates a Wallet::Server object, and calls the +appropriate methods. + +This program is a fairly thin wrapper around Wallet::Server that +translates command strings into method calls and returns the results. It +does check all arguments except for the <data> argument to the store +command and rejects any argument not matching C<^[\w_/.-]+\z>; in other +words, only alphanumerics, underscore (C<_>), slash (C</>), period (C<.>), +and hyphen (C<->) are permitted in arguments. This provides some +additional security over and above the checking already done by the rest +of the wallet code. + +=head1 OPTIONS + +=over 4 + +=item B<--quiet>, B<-q> + +If this option is given, B<wallet-backend> will not log its actions to +syslog. + +=back + +=head1 COMMANDS + +Most commands are only available to wallet administrators (users on the +C<ADMIN> ACL). The exceptions are C<acl check>, C<check>, C<get>, +C<store>, C<show>, C<destroy>, C<flag clear>, C<flag set>, C<getattr>, +C<setattr>, and C<history>. C<acl check> and C<check> can be run by +anyone. All of the rest of those commands have their own ACLs except +C<getattr> and C<history>, which use the C<show> ACL, C<setattr>, which +uses the C<store> ACL, and C<comment>, which uses the owner or C<show> ACL +depending on whether one is setting or retrieving the comment. If the +appropriate ACL is set, it alone is checked to see if the user has access. +Otherwise, C<destroy>, C<get>, C<store>, C<show>, C<getattr>, C<setattr>, +C<history>, and C<comment> access is permitted if the user is authorized +by the owner ACL of the object. + +Administrators can run any command on any object or ACL except for C<get> +and C<store>. For C<get> and C<store>, they must still be authorized by +either the appropriate specific ACL or the owner ACL. + +If the locked flag is set on an object, no commands can be run on that +object that change data except the C<flags> commands, nor can the C<get> +command be used on that object. C<show>, C<history>, C<getacl>, +C<getattr>, and C<owner>, C<comment>, or C<expires> without an argument +can still be used on that object. + +For more information on attributes, see L<ATTRIBUTES>. + +=over 4 + +=item acl add <id> <scheme> <identifier> + +Add an entry with <scheme> and <identifier> to the ACL <id>. <id> may be +either the name of an ACL or its numeric identifier. + +=item acl check <id> + +Check whether an ACL with the ID <id> already exists. If it does, prints +C<yes>; if not, prints C<no>. + +=item acl create <name> + +Create a new, empty ACL with name <name>. When setting an ACL on an +object with a set of entries that don't match an existing ACL, first +create a new ACL with C<acl create>, add the appropriate entries to it +with C<acl add>, and then set the ACL on an object with the C<owner> or +C<setacl> commands. + +=item acl destroy <id> + +Destroy the ACL <id>. This ACL must no longer be referenced by any object +or the ACL destruction will fail. The special ACL named C<ADMIN> cannot +be destroyed. + +=item acl history <id> + +Display the history of the ACL <id>. Each change to the ACL (not +including changes to the name of the ACL) will be represented by two +lines. The first line will have a timestamp of the change followed by a +description of the change, and the second line will give the user who made +the change and the host from which the change was made. + +=item acl remove <id> <scheme> <identifier> + +Remove the entry with <scheme> and <identifier> from the ACL <id>. <id> +may be either the name of an ACL or its numeric identifier. The last +entry in the special ACL C<ADMIN> cannot be removed to protect against +accidental lockout, but administrators can remove themselves from the +C<ADMIN> ACL and can leave only a non-functioning entry on the ACL. Use +caution when removing entries from the C<ADMIN> ACL. + +=item acl rename <id> <name> + +Renames the ACL identified by <id> to <name>. This changes the +human-readable name, not the underlying numeric ID, so the ACL's +associations with objects will be unchanged. The C<ADMIN> ACL may not be +renamed. <id> may be either the current name or the numeric ID. <name> +must not be all-numeric. To rename an ACL, the current user must be +authorized by the C<ADMIN> ACL. + +=item acl replace <id> <new-id> + +Find any objects owned by <id>, and then change their ownership to +<new_id> instead. <new-id> should already exist, and may already have +some objects owned by it. <id> is not deleted afterwards, though in +most cases that is probably your next step. The C<ADMIN> ACL may not be +replaced from. <id> and <new-id> may be either the current name or the +numeric ID. To replace an ACL, the current user must be authorized by +the C<ADMIN> ACL. + +=item acl show <id> + +Display the name, numeric ID, and entries of the ACL <id>. + +=item autocreate <type> <name> + +Create a new object of type <type> with name <name>. The user must be +listed in the default ACL for an object with that type and name, and the +object will be created with that default ACL set as the object owner. + +=item check <type> <name> + +Check whether an object of type <type> and name <name> already exists. If +it does, prints C<yes>; if not, prints C<no>. + +=item comment <type> <name> [<comment>] + +If <comment> is not given, displays the current comment for the object +identified by <type> and <name>, or C<No comment set> if none is set. + +If <comment> is given, sets the comment on the object identified by +<type> and <name> to <comment>. If <comment> is the empty string, clears +the comment. + +=item create <type> <name> + +Create a new object of type <type> with name <name>. With some backends, +this will trigger creation of an entry in an external system as well. +The new object will have no ACLs and no owner set, so usually the +administrator will want to then set an owner with C<owner> so that the +object will be usable. + +=item destroy <type> <name> + +Destroy the object identified by <type> and <name>. With some backends, +this will trigger destruction of an object in an external system as well. + +=item expires <type> <name> [<date> [<time>]] + +If <date> is not given, displays the current expiration of the object +identified by <type> and <name>, or C<No expiration set> if none is set. +The expiration will be displayed in seconds since epoch. + +If <date> is given, sets the expiration on the object identified by <type> +and <name> to <date> and (if given) <time>. <date> and <time> must be in +some format that can be parsed by the Perl Date::Parse module. Most +common formats are supported; if in doubt, use C<YYYY-MM-DD HH:MM:SS>. If +<date> is the empty string, clears the expiration of the object. + +Currently, the expiration of an object is not used. + +=item flag clear <type> <name> <flag> + +Clears the flag <flag> on the object identified by <type> and <name>. + +=item flag set <type> <name> <flag> + +Sets the flag <flag> on the object identified by <type> and <name>. +Recognized flags are C<locked>, which prevents all further actions on that +object until the flag is cleared, and C<unchanging>, which tells the +object backend to not generate new data on get but instead return the same +data as previously returned. The C<unchanging> flag is not meaningful for +objects that do not generate new data on the fly. + +=item get <type> <name> + +Prints to standard output the data associated with the object identified +by <type> and <name>. This may trigger generation of new data and +invalidate old data for that object depending on the object type. + +=item getacl <type> <name> <acl> + +Prints the ACL <acl>, which must be one of C<get>, C<store>, C<show>, +C<destroy>, or C<flags>, for the object identified by <type> and <name>. +Prints C<No ACL set> if that ACL isn't set on that object. Remember that +if the C<get>, C<store>, or C<show> ACLs aren't set, authorization falls +back to checking the owner ACL. See the C<owner> command for displaying +or setting it. + +=item getattr <type> <name> <attr> + +Prints the object attribute <attr> for the object identified by <type> and +<name>. Attributes are used to store backend-specific information for a +particular object type, and <attr> must be an attribute type known to the +underlying object implementation. The attribute values, if any, are +printed one per line. If the attribute is not set on this object, nothing +is printed. + +=item history <type> <name> + +Displays the history for the object identified by <type> and <name>. This +human-readable output will have two lines for each action that changes the +object, plus for any get action. The first line has the timestamp of the +action and the action, and the second line gives the user who performed +the action and the host from which they performed it. + +=item owner <type> <name> [<owner>] + +If <owner> is not given, displays the current owner ACL of the object +identified by <type> and <name>, or C<No owner set> if none is set. The +result will be the name of an ACL. + +If <owner> is given, sets the owner of the object identified by <type> and +<name> to <owner>. If <owner> is the empty string, clears the owner of +the object. + +=item rename <type> <name> <new-name> + +Renames an existing object. This currently only supports file objects, +where it renames the object itself, then the name and location of the +object in the file store. + +=item setacl <type> <name> <acl> <id> + +Sets the ACL <acl>, which must be one of C<get>, C<store>, C<show>, +C<destroy>, or C<flags>, to <id> on the object identified by <type> and +<name>. If <id> is the empty string, clears that ACL on the object. + +=item setattr <type> <name> <attr> <value> [<value> ...] + +Sets the object attribute <attr> for the object identified by <type> and +<name>. Attributes are used to store backend-specific information for a +particular object type, and <attr> must be an attribute type known to the +underlying object implementation. To clear the attribute for this object, +pass in a <value> of the empty string (C<''>). + +=item show <type> <name> + +Displays the current object metadata for the object identified by <type> +and <name>. This human-readable output will show the object type and +name, the owner, any specific ACLs set on the object, the expiration if +any, and the user, remote host, and time when the object was created, last +stored, and last downloaded. + +=item store <type> <name> [<data>] + +Stores <data> for the object identified by <type> and <name> for later +retrieval with C<get>. Not all object types support this. If <data> is +not given as an argument, it will be read from standard input. + +=item update <type> <name> + +Prints to standard output the data associated with the object identified +by <type> and <name>. If the object is one that can have changing +information, such as a keytab or password, then we generate new data for +that object regardless of whether there is current data or the unchanging +flag is set. + +=back + +=head1 ATTRIBUTES + +Object attributes store additional properties and configuration +information for objects stored in the wallet. They are displayed as part +of the object data with C<show>, retrieved with C<getattr>, and set with +C<setattr>. + +=head2 Keytab Attributes + +Keytab objects support the following attributes: + +=over 4 + +=item enctypes + +Restricts the generated keytab to a specific set of encryption types. The +values of this attribute must be enctype strings recognized by Kerberos +(strings like C<aes256-cts-hmac-sha1-96> or C<des-cbc-crc>). Note that +the salt should not be included; since the salt is irrelevant for keytab +keys, it will always be set to C<normal> by the wallet. + +If this attribute is set, the specified enctype list will be passed to +ktadd when get() is called for that keytab. If it is not set, the default +set in the KDC will be used. + +This attribute is ignored if the C<unchanging> flag is set on a keytab. +Keytabs retrieved with C<unchanging> set will contain all keys present in +the KDC for that Kerberos principal and therefore may contain different +enctypes than those requested by this attribute. + +=back + +=head1 AUTHOR + +Russ Allbery <eagle@eyrie.org> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2007-2008, 2010-2013 The Board of Trustees of the Leland Stanford +Junior University + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +SPDX-License-Identifier: MIT + +=head1 SEE ALSO + +Wallet::Server(3), remctld(8) + +This program is part of the wallet system. The current version is +available from L<https://www.eyrie.org/~eagle/software/wallet/>. + +=cut + +# Local Variables: +# copyright-at-end-flag: t +# End: |