From 6b7b9a29d20a65712061648404bbc6f1be5cacee Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Mon, 8 Dec 2014 21:48:35 -0800 Subject: Fix syntax of NAME section of Wallet::Object::Duo::* modules --- perl/lib/Wallet/Object/Duo/LDAPProxy.pm | 2 +- perl/lib/Wallet/Object/Duo/PAM.pm | 2 +- perl/lib/Wallet/Object/Duo/RDP.pm | 2 +- perl/lib/Wallet/Object/Duo/RadiusProxy.pm | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/perl/lib/Wallet/Object/Duo/LDAPProxy.pm b/perl/lib/Wallet/Object/Duo/LDAPProxy.pm index 74ff43c..23894ac 100644 --- a/perl/lib/Wallet/Object/Duo/LDAPProxy.pm +++ b/perl/lib/Wallet/Object/Duo/LDAPProxy.pm @@ -100,7 +100,7 @@ Allbery Duo integration DBH keytab LDAP auth =head1 NAME -Wallet::Object::Duo::LDAPProxy -- Duo auth proxy integration for LDAP +Wallet::Object::Duo::LDAPProxy - Duo auth proxy integration for LDAP =head1 SYNOPSIS diff --git a/perl/lib/Wallet/Object/Duo/PAM.pm b/perl/lib/Wallet/Object/Duo/PAM.pm index 6f90ba1..d9d17f8 100644 --- a/perl/lib/Wallet/Object/Duo/PAM.pm +++ b/perl/lib/Wallet/Object/Duo/PAM.pm @@ -101,7 +101,7 @@ Allbery Duo integration DBH keytab =head1 NAME -Wallet::Object::Duo::PAM -- Duo PAM int. object implementation for wallet +Wallet::Object::Duo::PAM - Duo PAM int. object implementation for wallet =head1 SYNOPSIS diff --git a/perl/lib/Wallet/Object/Duo/RDP.pm b/perl/lib/Wallet/Object/Duo/RDP.pm index 2e975fc..c74661c 100644 --- a/perl/lib/Wallet/Object/Duo/RDP.pm +++ b/perl/lib/Wallet/Object/Duo/RDP.pm @@ -101,7 +101,7 @@ Allbery Duo integration DBH keytab RDP =head1 NAME -Wallet::Object::Duo::RDP -- Duo RDP int. object implementation for wallet +Wallet::Object::Duo::RDP - Duo RDP int. object implementation for wallet =head1 SYNOPSIS diff --git a/perl/lib/Wallet/Object/Duo/RadiusProxy.pm b/perl/lib/Wallet/Object/Duo/RadiusProxy.pm index faa0c2f..a1f6e24 100644 --- a/perl/lib/Wallet/Object/Duo/RadiusProxy.pm +++ b/perl/lib/Wallet/Object/Duo/RadiusProxy.pm @@ -101,7 +101,7 @@ Allbery Duo integration DBH keytab auth =head1 NAME -Wallet::Object::Duo::RadiusProxy -- Duo auth proxy integration for RADIUS +Wallet::Object::Duo::RadiusProxy - Duo auth proxy integration for RADIUS =head1 SYNOPSIS -- cgit v1.2.3 From e566f44d15a075e6fb4e06bdd14d94f10b4ec124 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Thu, 22 Jan 2015 13:54:09 -0800 Subject: Updated Stanford policy to add optional extra to ssh keys Change-Id: Ic575c22c741c29e814749d334e9ed40eb83014e5 --- perl/lib/Wallet/Policy/Stanford.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/perl/lib/Wallet/Policy/Stanford.pm b/perl/lib/Wallet/Policy/Stanford.pm index a392476..a707ea5 100644 --- a/perl/lib/Wallet/Policy/Stanford.pm +++ b/perl/lib/Wallet/Policy/Stanford.pm @@ -66,8 +66,8 @@ our %FILE_TYPE = ( 'password-root' => { host => 1 }, 'password-tivoli' => { host => 1 }, properties => { extra => 1 }, - 'ssh-dsa' => { host => 1 }, - 'ssh-rsa' => { host => 1 }, + 'ssh-dsa' => { host => 1, extra => 1 }, + 'ssh-rsa' => { host => 1, extra => 1 }, 'ssl-key' => { host => 1, extra => 1 }, 'ssl-keypair' => { host => 1, extra => 1 }, 'ssl-keystore' => { extra => 1 }, -- cgit v1.2.3 From aebae838e3aa327e94d796bd99b48c169ffe6683 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Fri, 6 Feb 2015 13:04:35 -0800 Subject: wallet-report: Added a report for unstored objects Report on all file objects that have never had data stored in them. Also clean up the text around the 'objects unused' report which said that it did this plus things that were never gotten, but in reality only reported on the objects that were never gotten. Change-Id: I30c9585ac6f3744fbea2f94b3d6874a64c0109ad --- perl/lib/Wallet/Report.pm | 37 ++++++++++++++++++++++++++++--------- server/wallet-report | 5 ++++- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/perl/lib/Wallet/Report.pm b/perl/lib/Wallet/Report.pm index bf48308..2382d87 100644 --- a/perl/lib/Wallet/Report.pm +++ b/perl/lib/Wallet/Report.pm @@ -175,6 +175,20 @@ sub objects_unused { return (\%search, \%options); } +# Return the SQL statement to find all fiel objects that have been created +# but have never had information stored (via store). +sub objects_unstored { + my ($self) = @_; + my @objects; + + my %search = (ob_stored_on => undef, + ob_type => 'file'); + my %options = (order_by => [ qw/ob_type ob_name/ ], + select => [ qw/ob_type ob_name/ ]); + + return (\%search, \%options); +} + # Returns a list of all objects stored in the wallet database in the form of # type and name pairs. On error and for an empty database, the empty list # will be returned. To distinguish between an empty list and an error, call @@ -190,7 +204,7 @@ sub objects { if (!defined $type || $type eq '') { ($search_ref, $options_ref) = $self->objects_all; } else { - if ($type ne 'unused' && @args != 1) { + if ($type ne 'unused' && $type ne 'unstored' && @args != 1) { $self->error ("object searches require one argument to search"); } elsif ($type eq 'type') { ($search_ref, $options_ref) = $self->objects_type (@args); @@ -202,6 +216,8 @@ sub objects { ($search_ref, $options_ref) = $self->objects_acl (@args); } elsif ($type eq 'unused') { ($search_ref, $options_ref) = $self->objects_unused (@args); + } elsif ($type eq 'unstored') { + ($search_ref, $options_ref) = $self->objects_unstored (@args); } else { $self->error ("do not know search type: $type"); } @@ -633,14 +649,17 @@ Returns a list of all objects matching a search type and string in the database, or all objects in the database if no search information is given. -There are five types of searches currently. C, with a given type, -will return only those entries where the type matches the given type. -C, with a given owner, will only return those objects owned by the -given ACL name or ID. C, with a given flag name, will only return -those items with a flag set to the given value. C operates like -C, but will return only those objects that have the given ACL name -or ID on any of the possible ACL settings, not just owner. C will -return all entries for which a get command has never been issued. +There are several types of searches. C, with a given type, will +return only those entries where the type matches the given type. +C, with a given owner, will only return those objects owned by +the given ACL name or ID. C, with a given flag name, will only +return those items with a flag set to the given value. C operates +like C, but will return only those objects that have the given +ACL name or ID on any of the possible ACL settings, not just owner. +C will return all entries for which a get command has never +been issued. C will return all entries for which a store +command has never been issued (limited to file type since storing isn't +needed for other types). The return value is a list of references to pairs of type and name. For example, if two objects existed in the database, both of type C diff --git a/server/wallet-report b/server/wallet-report index b5a2247..1c8f914 100755 --- a/server/wallet-report +++ b/server/wallet-report @@ -25,7 +25,8 @@ Wallet reporting help: objects flag Objects with that flag set objects owner Objects owned by that owner objects type Objects of that type - objects unused Objects that have never been stored/gotten + objects unused Objects that have never been gotten + objects unstored Objects that have never been stored owners All ACL entries owning matching objects EOH @@ -220,6 +221,8 @@ Displays a summary of all available commands. =item objects unused +=item objects unstored + Returns a list of objects in the database. Objects will be listed in the form: -- cgit v1.2.3 From 0e16def8a9e12f9b2232b29da79cdacb6710b086 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Fri, 6 Feb 2015 23:43:50 -0800 Subject: Added acl replace command to wallet backend New command for replacing the ownership of anything owned by a specific ACL with another ACL. This differs from acl rename in that it's to be used when the destination ACL already exists and potentially already owns some objects. Change-Id: I765bebf499fe0f861abc2ffe1873990590beed36 --- client/wallet.pod | 10 ++++++++ perl/lib/Wallet/ACL.pm | 35 ++++++++++++++++++++++++++ perl/lib/Wallet/Server.pm | 38 ++++++++++++++++++++++++++++ perl/t/general/acl.t | 64 ++++++++++++++++++++++++++++++++++++++++++++--- server/wallet-backend | 22 ++++++++++++++++ 5 files changed, 166 insertions(+), 3 deletions(-) diff --git a/client/wallet.pod b/client/wallet.pod index 4b58bbf..20d1874 100644 --- a/client/wallet.pod +++ b/client/wallet.pod @@ -227,6 +227,16 @@ renamed. may be either the current name or the numeric ID. must not be all-numeric. To rename an ACL, the current user must be authorized by the C ACL. +=item acl replace + +Find any objects owned by , and then change their ownership to + instead. should already exist, and may already have +some objects owned by it. is not deleted afterwards, though in +most cases that is probably your next step. The C ACL may not be +replaced from. and may be either the current name or the +numeric ID. To replace an ACL, the current user must be authorized by +the C ACL. + =item acl show Display the name, numeric ID, and entries of the ACL . diff --git a/perl/lib/Wallet/ACL.pm b/perl/lib/Wallet/ACL.pm index a3b0146..370df8b 100644 --- a/perl/lib/Wallet/ACL.pm +++ b/perl/lib/Wallet/ACL.pm @@ -17,6 +17,7 @@ use strict; use warnings; use vars qw($VERSION); +use Wallet::Object::Base; use DateTime; use DBI; @@ -207,6 +208,32 @@ sub rename { return 1; } +# Moves everything owned by one ACL to instead be owned by another. You'll +# normally want to use rename, but this exists for cases where the replacing +# ACL already exists and has things assigned to it. Returns true on success, +# false on failure. +sub replace { + my ($self, $replace_id, $user, $host, $time) = @_; + $time ||= time; + + my %search = (ob_owner => $self->{id}); + my @objects = $self->{schema}->resultset('Object')->search (\%search); + if (@objects) { + for my $object (@objects) { + my $type = $object->ob_type; + my $name = $object->ob_name; + my $object = eval { + Wallet::Object::Base->new($type, $name, $self->{schema}); + }; + $object->owner ($replace_id, $user, $host, $time); + } + } else { + $self->error ("no objects found for ACL $self->{id}"); + return; + } + return 1; +} + # Destroy the ACL, deleting it out of the database. Returns true on success, # false on failure. # @@ -643,6 +670,14 @@ On failure, the caller should call error() to get the error message. Note that rename() operations are not logged in the ACL history. +=item replace(ID) + +Replace this ACL with another. This goes through each object owned by +the ACL and changes its ownership to the new ACL, leaving this acl owning +nothing (and probably then needing to be deleted). Returns true on +success and false on failure. On failure, the caller should call error() +to get the error message. + =item show() Returns a human-readable description of this ACL, including its diff --git a/perl/lib/Wallet/Server.pm b/perl/lib/Wallet/Server.pm index f6ea342..6af0570 100644 --- a/perl/lib/Wallet/Server.pm +++ b/perl/lib/Wallet/Server.pm @@ -734,6 +734,36 @@ sub acl_rename { return 1; } +# Move all ACLs owned by one ACL to another, or return undef and set the +# internal error. +sub acl_replace { + my ($self, $old_id, $replace_id) = @_; + unless ($self->{admin}->check ($self->{user})) { + $self->acl_error ($old_id, 'replace'); + return; + } + my $acl = eval { Wallet::ACL->new ($old_id, $self->{schema}) }; + if ($@) { + $self->error ($@); + return; + } + if ($acl->name eq 'ADMIN') { + $self->error ('cannot replace the ADMIN ACL'); + return; + } + my $replace_acl = eval { Wallet::ACL->new ($replace_id, $self->{schema}) }; + if ($@) { + $self->error ($@); + return; + } + + unless ($acl->replace ($replace_id, $self->{user}, $self->{host})) { + $self->error ($acl->error); + return; + } + return 1; +} + # Destroy an ACL, deleting it out of the database. Returns true on success. # On failure, returns undef, setting the internal error. sub acl_destroy { @@ -942,6 +972,14 @@ 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 acl_replace(OLD, NEW) + +Moves any object owned by the ACL identified by OLD to be instead owned by +NEW. This goes through all objects owned by OLD and individually changes +the owner, along with history updates. OLD and NEW may be either the name +or the numeric ID. To replace an ACL, the current user must be authorized +by the ADMIN ACL. Returns true on success and false on failure. + =item acl_show(ID) Returns a human-readable description, including membership, of the ACL diff --git a/perl/t/general/acl.t b/perl/t/general/acl.t index 1dd5c53..ff8ddad 100755 --- a/perl/t/general/acl.t +++ b/perl/t/general/acl.t @@ -12,11 +12,11 @@ use strict; use warnings; use POSIX qw(strftime); -use Test::More tests => 101; +use Test::More tests => 109; use Wallet::ACL; use Wallet::Admin; -use Wallet::Server; +use Wallet::Object::Base; use lib 't/lib'; use Util; @@ -46,7 +46,7 @@ $acl = eval { Wallet::ACL->create (3, $schema, @trace) }; ok (!defined ($acl), 'Creating with a numeric name'); is ($@, "ACL name may not be all numbers\n", ' with the right error message'); $acl = eval { Wallet::ACL->create ('test', $schema, @trace) }; -ok (!defined ($acl), 'Creating a duplicate object'); +ok (!defined ($acl), 'Creating a duplicate acl'); like ($@, qr/^cannot create ACL test: /, ' with the right error message'); $acl = eval { Wallet::ACL->new ('test2', $schema) }; ok (!defined ($acl), 'Searching for a non-existent ACL'); @@ -231,6 +231,64 @@ is ($@, '', ' with no exceptions'); is ($acl->name, 'example', ' and the right name'); like ($acl->id, qr{\A[23]\z}, ' and an ID of 2 or 3'); +# Test replace. by creating three acls, then assigning two objects to the +# first, one to the second, and another to the third. Then replace the first +# acl with the second, so that we can verify that multiple objects are moved, +# that an object already belonging to the new acl is okay, and that the +# objects with unrelated ACL are unaffected. +my ($acl_old, $acl_new, $acl_other, $obj_old_one, $obj_old_two, $obj_new, + $obj_unrelated); +eval { + $acl_old = Wallet::ACL->create ('example-old', $schema, @trace); + $acl_new = Wallet::ACL->create ('example-new', $schema, @trace); + $acl_other = Wallet::ACL->create ('example-other', $schema, @trace); +}; +is ($@, '', 'ACLs needed for testing replace are created'); +eval { + $obj_old_one = Wallet::Object::Base->create ('keytab', + 'service/test1@EXAMPLE.COM', + $schema, @trace); + $obj_old_two = Wallet::Object::Base->create ('keytab', + 'service/test2@EXAMPLE.COM', + $schema, @trace); + $obj_new = Wallet::Object::Base->create ('keytab', + 'service/test3@EXAMPLE.COM', + $schema, @trace); + $obj_unrelated = Wallet::Object::Base->create ('keytab', + 'service/test4@EXAMPLE.COM', + $schema, @trace); +}; +is ($@, '', ' and so were needed objects'); +if ($obj_old_one->owner ('example-old', @trace) + && $obj_old_two->owner ('example-old', @trace) + && $obj_new->owner ('example-new', @trace) + && $obj_unrelated->owner ('example-other', @trace)) { + + ok (1, ' and setting initial ownership on the objects succeeds'); +} +is ($acl_old->replace('example-new', @trace), 1, + ' and replace ran successfully'); +eval { + $obj_old_one = Wallet::Object::Base->new ('keytab', + 'service/test1@EXAMPLE.COM', + $schema); + $obj_old_two = Wallet::Object::Base->new ('keytab', + 'service/test2@EXAMPLE.COM', + $schema); + $obj_new = Wallet::Object::Base->new ('keytab', + 'service/test3@EXAMPLE.COM', + $schema); + $obj_unrelated = Wallet::Object::Base->new ('keytab', + 'service/test4@EXAMPLE.COM', + $schema); +}; +is ($obj_old_one->owner, 'example-new', ' and first replace is correct'); +is ($obj_old_two->owner, 'example-new', ' and second replace is correct'); +is ($obj_new->owner, 'example-new', + ' and object already with new acl is correct'); +is ($obj_unrelated->owner, 'example-other', + ' and unrelated object ownership is correct'); + # Clean up. $setup->destroy; END { diff --git a/server/wallet-backend b/server/wallet-backend index 8dfc952..dcf2300 100755 --- a/server/wallet-backend +++ b/server/wallet-backend @@ -173,6 +173,9 @@ sub command { } 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); @@ -449,6 +452,25 @@ accidental lockout, but administrators can remove themselves from the C ACL and can leave only a non-functioning entry on the ACL. Use caution when removing entries from the C ACL. +=item acl rename + +Renames the ACL identified by to . This changes the +human-readable name, not the underlying numeric ID, so the ACL's +associations with objects will be unchanged. The C ACL may not be +renamed. may be either the current name or the numeric ID. +must not be all-numeric. To rename an ACL, the current user must be +authorized by the C ACL. + +=item acl replace + +Find any objects owned by , and then change their ownership to + instead. should already exist, and may already have +some objects owned by it. is not deleted afterwards, though in +most cases that is probably your next step. The C ACL may not be +replaced from. and may be either the current name or the +numeric ID. To replace an ACL, the current user must be authorized by +the C ACL. + =item acl show Display the name, numeric ID, and entries of the ACL . -- cgit v1.2.3 From 55875aa020f31751f295ae6c07547fe2949c5e82 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Sat, 7 Feb 2015 13:59:34 -0800 Subject: Added a new password object type The password type inherits almost everything from the file object, but if you try to get a password object that has never been stored, we generate a random string to put in the object rather than just erroring out. The maximum and minimum length of the string can be set in the wallet config. If a password object was stored earlier and then cleared out, we don't generate another random string. Change-Id: I17a65ca7dac9d4430e8a731f417297890ee612bb --- perl/lib/Wallet/Admin.pm | 1 + perl/lib/Wallet/Config.pm | 43 ++++++++ perl/lib/Wallet/Object/Password.pm | 210 +++++++++++++++++++++++++++++++++++++ perl/t/object/password.t | 118 +++++++++++++++++++++ 4 files changed, 372 insertions(+) create mode 100644 perl/lib/Wallet/Object/Password.pm create mode 100644 perl/t/object/password.t diff --git a/perl/lib/Wallet/Admin.pm b/perl/lib/Wallet/Admin.pm index 8120e9c..a8b8368 100644 --- a/perl/lib/Wallet/Admin.pm +++ b/perl/lib/Wallet/Admin.pm @@ -131,6 +131,7 @@ sub default_data { [ 'duo-radius', 'Wallet::Object::Duo::RadiusProxy' ], [ 'duo-rdp', 'Wallet::Object::Duo::RDP' ], [ 'file', 'Wallet::Object::File' ], + [ 'password', 'Wallet::Object::Password' ], [ 'keytab', 'Wallet::Object::Keytab' ], [ 'wa-keyring', 'Wallet::Object::WAKeyring' ]); ($r1) = $self->{schema}->resultset('Type')->populate (\@record); diff --git a/perl/lib/Wallet/Config.pm b/perl/lib/Wallet/Config.pm index 2eb57f9..76c7ecd 100644 --- a/perl/lib/Wallet/Config.pm +++ b/perl/lib/Wallet/Config.pm @@ -260,6 +260,49 @@ our $FILE_MAX_SIZE; =back +=head1 PASSWORD OBJECT CONFIGURATION + +These configuration variables only need to be set if you intend to use the +C object type (the Wallet::Object::Password class). You will also +need to set the FILE_MAX_SIZE value from the file object configuration, as +that is inherited. + +=over 4 + +=item PWD_FILE_BUCKET + +The directory into which to store password objects. Password objects will +be stored in subdirectories of this directory. See +L for the full details of the naming scheme. This +directory must be writable by the wallet server and the wallet server must +be able to create subdirectories of it. + +PWD_FILE_BUCKET must be set to use file objects. + +=cut + +our $PWD_FILE_BUCKET; + +=item PWD_LENGTH_MIN + +The minimum length for any auto-generated password objects created when get +is run before data is stored. + +=cut + +our $PWD_LENGTH_MIN = 20; + +=item PWD_LENGTH_MAX + +The maximum length for any auto-generated password objects created when get +is run before data is stored. + +=cut + +our $PWD_LENGTH_MAX = 21; + +=back + =head1 KEYTAB OBJECT CONFIGURATION These configuration variables only need to be set if you intend to use the diff --git a/perl/lib/Wallet/Object/Password.pm b/perl/lib/Wallet/Object/Password.pm new file mode 100644 index 0000000..d06c8a6 --- /dev/null +++ b/perl/lib/Wallet/Object/Password.pm @@ -0,0 +1,210 @@ +# Wallet::Object::Password -- Password object implementation for the wallet. +# +# Written by Jon Robertson +# Copyright 2015 +# The Board of Trustees of the Leland Stanford Junior University +# +# See LICENSE for licensing terms. + +############################################################################## +# Modules and declarations +############################################################################## + +package Wallet::Object::Password; +require 5.006; + +use strict; +use warnings; +use vars qw(@ISA $VERSION); + +use Crypt::GeneratePassword qw(chars); +use Digest::MD5 qw(md5_hex); +use Wallet::Config (); +use Wallet::Object::File; + +@ISA = qw(Wallet::Object::File); + +# 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'; + +############################################################################## +# File naming +############################################################################## + +# Returns the path into which that password object will be stored or undef on +# error. On error, sets the internal error. +sub file_path { + my ($self) = @_; + my $name = $self->{name}; + unless ($Wallet::Config::PWD_FILE_BUCKET) { + $self->error ('password support not configured'); + return; + } + unless ($name) { + $self->error ('password objects may not have empty names'); + return; + } + my $hash = substr (md5_hex ($name), 0, 2); + $name =~ s/([^\w-])/sprintf ('%%%02X', ord ($1))/ge; + my $parent = "$Wallet::Config::PWD_FILE_BUCKET/$hash"; + unless (-d $parent || mkdir ($parent, 0700)) { + $self->error ("cannot create password bucket $hash: $!"); + return; + } + return "$Wallet::Config::PWD_FILE_BUCKET/$hash/$name"; +} + +############################################################################## +# Core methods +############################################################################## + +# Return the contents of the file. +sub get { + my ($self, $user, $host, $time) = @_; + $time ||= time; + my $id = $self->{type} . ':' . $self->{name}; + if ($self->flag_check ('locked')) { + $self->error ("cannot get $id: object is locked"); + return; + } + my $path = $self->file_path; + return unless $path; + + # If nothing is yet stored, generate a random password and save it to + # the file. + my $schema = $self->{schema}; + my %search = (ob_type => $self->{type}, + ob_name => $self->{name}); + my $object = $schema->resultset('Object')->find (\%search); + unless ($object->ob_stored_on) { + unless (open (FILE, '>', $path)) { + $self->error ("cannot store initial settings for $id: $!\n"); + return; + } + my $pass = chars ($Wallet::Config::PWD_LENGTH_MIN, + $Wallet::Config::PWD_LENGTH_MAX); + print FILE $pass; + $self->log_action ('store', $user, $host, $time); + unless (close FILE) { + $self->error ("cannot get $id: $!"); + return; + } + } + + unless (open (FILE, '<', $path)) { + $self->error ("cannot get $id: object has not been stored"); + return; + } + local $/; + my $data = ; + unless (close FILE) { + $self->error ("cannot get $id: $!"); + return; + } + $self->log_action ('get', $user, $host, $time); + return $data; +} + +1; +__END__ + +############################################################################## +# Documentation +############################################################################## + +=head1 NAME + +Wallet::Object::Password - Password object implementation for wallet + +=for stopwords +API HOSTNAME DATETIME keytab remctld backend nul Allbery wallet-backend + +=head1 SYNOPSIS + + my @name = qw(file mysql-lsdb) + my @trace = ($user, $host, time); + my $object = Wallet::Object::Password->create (@name, $schema, @trace); + unless ($object->store ("the-password\n")) { + die $object->error, "\n"; + } + my $password = $object->get (@trace); + $object->destroy (@trace); + +=head1 DESCRIPTION + +Wallet::Object::Password is an extension of Wallet::Object::File, +acting as a representation of simple file objects in the wallet. The +difference between the two is that if there is no data stored in a +password object when a user tries to get it for the first time, then a +random string suited for a password will be generated and put into the +object data. + +It implements the wallet object API and provides the necessary +glue to store a file on the wallet server, retrieve it later, and delete +it when the password object is deleted. + +To use this object, the configuration option specifying where on the +wallet server to store password objects must be set. See +L for details on this configuration parameter and +information about how to set wallet configuration. + +=head1 METHODS + +This object mostly inherits from Wallet::Object::File. See the +documentation for that class for all generic methods. Below are only +those methods that are overridden or behave specially for this +implementation. + +=over 4 + +=item get(PRINCIPAL, HOSTNAME [, DATETIME]) + +Retrieves the current contents of the file object or undef on error. +store() must be called before get() will be successful. The caller should +call error() to get the error message if get() returns undef. PRINCIPAL, +HOSTNAME, and DATETIME are stored as history information. PRINCIPAL +should be the user who is downloading the keytab. If DATETIME isn't +given, the current time is used. + +=back + +=head1 FILES + +=over 4 + +=item PWD_FILE_BUCKET// + +Password files are stored on the wallet server under the directory +PWD_FILE_BUCKET as set in the wallet configuration. is the +first two characters of the hex-encoded MD5 hash of the wallet password +object name, used to not put too many files in the same directory. + is the name of the password object with all characters other +than alphanumerics, underscores, and dashes replaced by C<%> and the +hex code of the character. + +=back + +=head1 LIMITATIONS + +The wallet implementation itself can handle arbitrary password object +names. However, due to limitations in the B server usually +used to run B, password object names containing nul +characters (ASCII 0) may not be permitted. The file system used for +storing file objects may impose a length limitation on the +password object name. + +=head1 SEE ALSO + +remctld(8), Wallet::Config(3), Wallet::Object::File(3), +wallet-backend(8) + +This module is part of the wallet system. The current version is +available from L. + +=head1 AUTHOR + +Jon Robertson + +=cut diff --git a/perl/t/object/password.t b/perl/t/object/password.t new file mode 100644 index 0000000..c0f2fbc --- /dev/null +++ b/perl/t/object/password.t @@ -0,0 +1,118 @@ +#!/usr/bin/perl +# +# Tests for the password object implementation. Only includes tests that are +# basic or different from the file object implementation. +# +# 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 POSIX qw(strftime); +use Test::More tests => 31; + +use Wallet::Admin; +use Wallet::Config; +use Wallet::Object::Password; + +use lib 't/lib'; +use Util; + +# Some global defaults to use. +my $user = 'admin@EXAMPLE.COM'; +my $host = 'localhost'; +my @trace = ($user, $host, time); + +# Flush all output immediately. +$| = 1; + +# Use Wallet::Admin to set up the database. +system ('rm -rf test-files') == 0 or die "cannot remove test-files\n"; +db_setup; +my $admin = eval { Wallet::Admin->new }; +is ($@, '', 'Database connection succeeded'); +is ($admin->reinitialize ($user), 1, 'Database initialization succeeded'); +my $schema = $admin->schema; + +# Use this to accumulate the history traces so that we can check history. +my $history = ''; +my $date = strftime ('%Y-%m-%d %H:%M:%S', localtime $trace[2]); + +$Wallet::Config::PWD_FILE_BUCKET = undef; + +# Test error handling in the absence of configuration. +my $object = eval { + Wallet::Object::Password->create ('password', 'test', $schema, @trace) + }; +ok (defined ($object), 'Creating a basic password object succeeds'); +ok ($object->isa ('Wallet::Object::Password'), ' and is the right class'); +is ($object->get (@trace), undef, ' and get fails'); +is ($object->error, 'password support not configured', + ' with the right error'); +is ($object->store (@trace), undef, ' and store fails'); +is ($object->error, 'password support not configured', + ' with the right error'); +is ($object->destroy (@trace), 1, ' but destroy succeeds'); + +# Set up our configuration. +mkdir 'test-files' or die "cannot create test-files: $!\n"; +$Wallet::Config::PWD_FILE_BUCKET = 'test-files'; +$Wallet::Config::PWD_LENGTH_MIN = 10; +$Wallet::Config::PWD_LENGTH_MAX = 10; + +# Okay, now we can test. First, the basic object without store. +$object = eval { + Wallet::Object::Password->create ('password', 'test', $schema, @trace) + }; +ok (defined ($object), 'Creating a basic password object succeeds'); +ok ($object->isa ('Wallet::Object::Password'), ' and is the right class'); +my $pwd = $object->get (@trace); +like ($pwd, qr{^.{$Wallet::Config::PWD_LENGTH_MIN}$}, + ' and get creates a random password string of the right length'); +ok (-d 'test-files/09', ' and the hash bucket was created'); +ok (-f 'test-files/09/test', ' and the file exists'); +is (contents ('test-files/09/test'), $pwd, ' with the right contents'); +my $pwd2 = $object->get (@trace); +is ($pwd, $pwd2, ' and getting again gives the same string'); +is ($object->destroy (@trace), 1, ' and destroying the object succeeds'); + +# Now check to see if the password length is adjusted. +$Wallet::Config::PWD_LENGTH_MIN = 20; +$Wallet::Config::PWD_LENGTH_MAX = 20; +$object = eval { + Wallet::Object::Password->create ('password', 'test', $schema, @trace) + }; +ok (defined ($object), 'Recreating the object succeeds'); +$pwd = $object->get (@trace); +like ($pwd, qr{^.{$Wallet::Config::PWD_LENGTH_MIN}$}, + ' and get creates a random password string of a longer length'); +is ($object->destroy (@trace), 1, ' and destroying the object succeeds'); + +# Now store something and be sure that we get something reasonable. +$object = eval { + Wallet::Object::Password->create ('password', 'test', $schema, @trace) + }; +ok (defined ($object), 'Recreating the object succeeds'); +is ($object->store ("foo\n", @trace), 1, ' and storing data in it succeeds'); +ok (-f 'test-files/09/test', ' and the file exists'); +is (contents ('test-files/09/test'), 'foo', ' with the right contents'); +is ($object->get (@trace), "foo\n", ' and get returns correctly'); +unlink 'test-files/09/test'; +is ($object->get (@trace), undef, + ' and get will not autocreate a password if there used to be data'); +is ($object->error, 'cannot get password:test: object has not been stored', + ' as if it had not been stored'); +is ($object->store ("bar\n\0baz\n", @trace), 1, ' but storing again works'); +ok (-f 'test-files/09/test', ' and the file exists'); +is (contents ('test-files/09/test'), 'bar', ' with the right contents'); +is ($object->get (@trace), "bar\n\0baz\n", ' and get returns correctly'); + +# Clean up. +$admin->destroy; +END { + unlink ('wallet-db'); +} -- cgit v1.2.3 From e7aed7182fe22c0f89754f96dfa6b2c6c2b665b0 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Sat, 7 Feb 2015 16:03:55 -0800 Subject: Added first pass of password objects to Stanford policy Change-Id: I6198f4247f589e94beced128504dd086194b1983 --- perl/lib/Wallet/Policy/Stanford.pm | 91 +++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/perl/lib/Wallet/Policy/Stanford.pm b/perl/lib/Wallet/Policy/Stanford.pm index a707ea5..4a3c445 100644 --- a/perl/lib/Wallet/Policy/Stanford.pm +++ b/perl/lib/Wallet/Policy/Stanford.pm @@ -75,6 +75,16 @@ our %FILE_TYPE = ( 'tivoli-key' => { host => 1 }, ); +# Password object types. Most of these mimic file object types (which should +# be gradually phased out). +our %PASSWORD_TYPE = ( + 'ipmi' => { host => 1 }, + 'root' => { host => 1 }, + 'tivoli' => { host => 1 }, + 'system' => { host => 1, extra => 1, need_extra => 1 }, + 'app' => { extra => 1 }, +); + # Host-based file object types for the legacy file object naming scheme. our @FILE_HOST_LEGACY = qw(htpasswd ssh-rsa ssh-dsa ssl-key tivoli-key); @@ -144,6 +154,17 @@ sub _host_for_file_legacy { return $host; } +# Map a password object name to a hostname. Returns undef if this password +# object name doesn't map to a hostname. +sub _host_for_password { + my ($name) = @_; + + # Parse the name and check whether this is a host-based object. + my ($type, $host) = split('/', $name); + return if !$PASSWORD_TYPE{$type}{host}; + return $host; +} + # Map a file object name to a hostname. Returns undef if this file object # name doesn't map to a hostname. sub _host_for_file { @@ -192,6 +213,7 @@ sub default_owner { my %host_for = ( 'keytab' => \&_host_for_keytab, 'file' => \&_host_for_file, + 'password' => \&_host_for_password, 'duo' => \&_host_for_duo, 'duo-pam' => \&_host_for_duo, 'duo-radius' => \&_host_for_duo, @@ -242,7 +264,7 @@ sub default_owner { # hostnames, limit the acceptable characters for service/* keytabs, and # enforce our naming constraints on */cgi principals. # -# Also use this function to require that IDG staff always do implicit object +# Also use this function to require that ACS staff always do implicit object # creation using a */root instance. sub verify_name { my ($type, $name, $user) = @_; @@ -363,6 +385,8 @@ sub verify_name { return "missing component in $name"; } return; + + } else { # Legacy naming scheme. my %groups = map { $_ => 1 } @GROUPS_LEGACY; @@ -380,6 +404,71 @@ sub verify_name { } } + # Check password object naming conventions. + if ($type eq 'password') { + if ($name =~ m{ / }xms) { + my @name = split('/', $name); + + # Names have between two and four components and all must be + # non-empty. + if (@name > 4) { + return "too many components in $name"; + } + if (@name < 2) { + return "too few components in $name"; + } + if (grep { $_ eq q{} } @name) { + return "empty component in $name"; + } + + # All objects start with the type. First check if this is a + # host-based type. + my $type = shift @name; + if ($PASSWORD_TYPE{$type} && $PASSWORD_TYPE{$type}{host}) { + my ($host, $extra) = @name; + if ($host !~ m{ [.] }xms) { + return "host name $host is not fully qualified"; + } + if (defined($extra) && !$PASSWORD_TYPE{$type}{extra}) { + return "extraneous component at end of $name"; + } + if (!defined($extra) && $PASSWORD_TYPE{$type}{need_extra}) { + return "missing component in $name"; + } + return; + } + + # Otherwise, the name is group-based. There be at least two + # remaining components. + if (@name < 2) { + return "too few components in $name"; + } + my ($group, $service, $extra) = @name; + + # Check the group. + if (!$ACL_FOR_GROUP{$group}) { + return "unknown group $group"; + } + + # Check the type. Be sure it's not host-based. + if (!$PASSWORD_TYPE{$type}) { + return "unknown type $type"; + } + if ($PASSWORD_TYPE{$type}{host}) { + return "bad name for host-based file type $type"; + } + + # Check the extra data. + if (defined($extra) && !$PASSWORD_TYPE{$type}{extra}) { + return "extraneous component at end of $name"; + } + if (!defined($extra) && $PASSWORD_TYPE{$type}{need_extra}) { + return "missing component in $name"; + } + return; + } + } + # Check the naming conventions for all Duo object types. The object # should simply be the host name for now. if ($type =~ m{^duo(-\w+)?$}) { -- cgit v1.2.3 From f14bd8343010ad96104965029e36c5a65d231571 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Sat, 7 Feb 2015 16:09:12 -0800 Subject: Added an object history report to wallet-report Took code from Commerzbank AG and refactored to add to wallet-report. This does a complete dump of all object history for searching on. Change-Id: Id22c51d2938ad90e0c6a19aaa016501a1ba333b3 --- perl/lib/Wallet/Report.pm | 42 ++++++++++++++++++++++++++++++++++++++++++ server/wallet-report | 8 +++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/perl/lib/Wallet/Report.pm b/perl/lib/Wallet/Report.pm index 2382d87..912bc17 100644 --- a/perl/lib/Wallet/Report.pm +++ b/perl/lib/Wallet/Report.pm @@ -242,6 +242,48 @@ sub objects { return @objects; } +# Returns a list of all object_history records stored in the wallet database +# including all of their fields. On error and for an empty database, the +# empty list will be returned. To distinguish between an empty list and an +# error, call error(), which will return undef if there was no error. +# Farms out specific statement to another subroutine for specific search +# types, but each case should return ob_type and ob_name in that order. +sub objects_history { + my ($self, $type, @args) = @_; + undef $self->{error}; + + # All fields in the order we want to see them. + my @fields = ('oh_on', 'oh_by', 'oh_type', 'oh_name', 'oh_action', + 'oh_from'); + + # Get the search and options array refs from specific functions. + my %search = (); + my %options = (order_by => \@fields, + select => \@fields); + + # Perform the search and return on any errors. + my @objects; + my $schema = $self->{schema}; + eval { + my @objects_rs + = $schema->resultset('ObjectHistory')->search (\%search, + \%options); + for my $object_rs (@objects_rs) { + my @rec; + for my $field (@fields) { + push (@rec, $object_rs->get_column($field)); + } + push (@objects, \@rec); + } + }; + if ($@) { + $self->error ("cannot list objects: $@"); + return; + } + + return @objects; +} + ############################################################################## # ACL reports ############################################################################## diff --git a/server/wallet-report b/server/wallet-report index 1c8f914..bc499d4 100755 --- a/server/wallet-report +++ b/server/wallet-report @@ -23,6 +23,7 @@ Wallet reporting help: objects All objects objects acl Objects granting permissions to that ACL objects flag Objects with that flag set + objects history History of all objects objects owner Objects owned by that owner objects type Objects of that type objects unused Objects that have never been gotten @@ -75,7 +76,12 @@ sub command { print $HELP; } elsif ($command eq 'objects') { die "too many arguments to objects\n" if @args > 2; - my @objects = $report->objects (@args); + my @objects; + if (@args && $args[0] eq 'history') { + @objects = $report->objects_history (@args); + } else { + @objects = $report->objects (@args); + } if (!@objects and $report->error) { die $report->error, "\n"; } -- cgit v1.2.3 From ac97f9268b927cec5af229f496b9dd66332445e4 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Tue, 17 Feb 2015 12:27:04 -0800 Subject: Updated documentation for duo and password objects The documentation now includes information about the Duo file types, and the new password types. This is both the general information, and the Stanford-specific naming docs. Change-Id: Iae256224a063ce42f22cd933ef7bb3ab402e0e2d --- docs/objects-and-schemes | 24 +++++++++--- docs/stanford-naming | 97 +++++++++++++++++++++++++++++++----------------- 2 files changed, 81 insertions(+), 40 deletions(-) diff --git a/docs/objects-and-schemes b/docs/objects-and-schemes index 97e6289..763a24b 100644 --- a/docs/objects-and-schemes +++ b/docs/objects-and-schemes @@ -10,17 +10,21 @@ Introduction Object Types - duo + duo-ldap + duo-pam + duo-radius + duo-rdp Stores the configuration for a Duo Security integration. Duo is a cloud provider of multifactor authentication services. A Duo integration consists of some local configuration and a secret key that permits verification of a second factor using the Duo cloud service. - Currently, only UNIX integrations are supported. In the future, this - object type will likely be split into several object types - corresponding to the supported types of Duo integrations. + Each of these types is the same except for the output, which is + specialized towards giving information in the format suited for a + specific application. - Implemented via Wallet::Object::Duo. + Implemented via Wallet::Object::Duo::PAM, Wallet::Object::Duo::RDP, + Wallet::Object::Duo::LDAPProxy, Wallet::Object::Duo::RadiusProxy. file @@ -33,6 +37,16 @@ Object Types Implemented via Wallet::Object::File. + password + + Stores a file with single password in it and allows retrieval of that + file. This is built on the file object and is almost entirely + identical in function. It adds the ability to automatically generate + randomized content if you get the object before it's been stored, + letting you get autogenerated passwords. + + Implemented via Wallet::Object::Password. + keytab Stores a keytab representing private keys for a given Kerberos diff --git a/docs/stanford-naming b/docs/stanford-naming index c86c820..cb05a23 100644 --- a/docs/stanford-naming +++ b/docs/stanford-naming @@ -90,27 +90,6 @@ Object Naming (OLD: --htpasswd-) - password-ipmi/ - - Stores the password for remote IPMI/iLO/ILOM access to the - system. - - (OLD: --password-ipmi) - - password-root/ - - Stores the root password for a given server. - - (OLD: --password-root) - - password-tivoli/ - - Stores the Tivoli TSM backup password for a given server. See - also tivoli-key/, but depending on what one wants to do - with the password, this may be a better representation. - - (OLD: --password-tivoli) - ssh-/ Stores the SSH private key for . For shared private keys @@ -197,20 +176,6 @@ Object Naming (OLD: --gpg-key) - password/// - - A password for some account, service, keystore, or something - similar that is not covered by one of the more specific naming - conventions, such as a password used to connect to a remote ssh - service. is the service that uses this password and - is the thing the password is used for (such as the remote - account name). This may be a file containing only the password, - or a configuration file of some type that includes a field name - and the password. (However, use the db type described above for - database passwords.) - - (OLD: --password-) - properties//[/] The properties file for a Java application that contains some @@ -262,6 +227,68 @@ Object Naming --pam- --puppetconf --shibboleth + --password-ipmi + --password-root + --password-tivoli + --password- + + Replaced by password objects: + + password-ipmi/ + password-root/ + password-tivoli/ + + password/// should be replaced by the password + service/// object if a single password, or by + the file object db/* or config/* format if the object contains more + than just the bare password. + + Password + + Passwords are a recent type and so most password data is actually + in file objects. However, we'd like to move things there both for + the added features of password objects to self-set, and because it + helps clean up the file namespace a little more. + + Host-based: + + ipmi/ + + Stores the password for remote IPMI/iLO/ILOM access to the + system. + + tivoli/ + + Stores the Tivoli TSM backup password for a given server. See + also tivoli-key/ in the file section, but depending on + what one wants to do with the password, this may be a better + representation. + + root/ + + Stores the root password for a given server. + + system// + + Stores the password for a non-root system account, such as a user + required for file uploads. + + app// + + Stores an application password bound to a certain server. + + Service-based: + + service/// + + A password for some account, service, keystore, or something + similar that is not covered by one of the more specific naming + conventions, such as a password used to connect to a remote ssh + service. is the service that uses this password and + is the thing the password is used for (such as the remote + account name). This should only be for something including the + password and nothing else. See the file password/ object name + for something that includes more data. ACL Naming -- cgit v1.2.3 From 7181c4e75fbd3e0a4d16aad5f227fd4ecb94ccc0 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Tue, 17 Feb 2015 12:29:11 -0800 Subject: Added service type to Stanford policy for password Added to the password object type a new naming set for service/*, specifically for things that belong to a non-host-specific service. Change-Id: I1481d48319a5833f00eae940a6d2ca912874bb01 --- perl/lib/Wallet/Policy/Stanford.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/perl/lib/Wallet/Policy/Stanford.pm b/perl/lib/Wallet/Policy/Stanford.pm index 4a3c445..39a8174 100644 --- a/perl/lib/Wallet/Policy/Stanford.pm +++ b/perl/lib/Wallet/Policy/Stanford.pm @@ -82,7 +82,8 @@ our %PASSWORD_TYPE = ( 'root' => { host => 1 }, 'tivoli' => { host => 1 }, 'system' => { host => 1, extra => 1, need_extra => 1 }, - 'app' => { extra => 1 }, + 'app' => { host => 1, extra => 1, need_extra => 1 }, + 'service' => { extra => 1, need_extra => 1 }, ); # Host-based file object types for the legacy file object naming scheme. -- cgit v1.2.3 From ef46bf3e857043ce226ec9b9918605df57da87f9 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Tue, 17 Feb 2015 12:30:27 -0800 Subject: Added a contrib script for history actions Commerzbank offered a script for searching and editing the wallet history. The coding style is very different from our own, so I'm including this as a contrib script for now. Change-Id: I20516d63ad6f633ad0efc3977d990fa1e7a5ebd9 --- contrib/commerzbank/wallet-history | 475 +++++++++++++++++++++++++++++++++++++ 1 file changed, 475 insertions(+) create mode 100755 contrib/commerzbank/wallet-history diff --git a/contrib/commerzbank/wallet-history b/contrib/commerzbank/wallet-history new file mode 100755 index 0000000..9826057 --- /dev/null +++ b/contrib/commerzbank/wallet-history @@ -0,0 +1,475 @@ +#!/usr/bin/perl -w +# +# -*- perl -*- +# +#-------------------------------------------------------------------------------------------------------------- +# Program : wallet-history +# Function : Tool for listing and/or modifying Wallet's object history +# Author : Commerzbank AG +# History : 20.01.2014 - V0.1 - First version - maxcrc +# : 21.01.2014 - V0.2 - Fixed some minor bugs and code formatting - Gerhard Stahl +# Added some remarks for nessesary fixes +# : 29.01.2014 - V0.2 - Fixed issue related to variable comparision of undef values - maxcrc +# Reformatted code to use functions like debug, usage - maxcrc +# : 06.02.2014 - V0.3 - Added support for ACL history in addition to object history - maxcrc +# : 07.02.2014 - V0.4 - A lot of improvements, run with --help to see - maxcrc +# --acl and --obj argument names removed, please use --acls and --objs +# --from and --to argument names replaced with --start and --end +# Added new filtering args --from, --action, --acl, --by, --type and --name +# Added new --version action type +# Added new --columns action type +# : 08.02.2014 - V0.5 - Added support for ACL names via new ah_name column - maxcrc +# +#-------------------------------------------------------------------------------------------------------------- +# +# Usage: +# +# type ./wallet-history with no arguments to get the full help text (or with --help). +# +# In general, use a command of the form: +# +# perl wallet-history.pl ... (t.b.d.)... +# +#-------------------------------------------------------------------------------------------------------------- +# Version. + +my $VERSION = "0.5"; + +=head1 Needed Modules + +Here is the list of modules we need: + +=cut + +use strict; +use Getopt::Long; +use Wallet::Schema; +use DateTime; +use DateTime::Format::Strptime; +#use DBIx::ResultSet; +use DBI; +#use DateTime (); +#use Scalar::Util; + +my $debug_on = 0; + +=head1 Subroutines + +=head2 Utility subroutines + +=head3 usage - display a usage message + +=cut + +sub usage() { + print "wallet-history is tool for listing and/or modifying Wallet's history\n"; + print "usage:\n"; + print "wallet-history [mandatory action] [optional query type] [optional filtering arguments]\n"; + print " where mandatory action is one of:\n"; + print " --help - display this help information\n"; + print " --version - display app version\n"; + print " --columns - display column names (use with --objs or --acls)\n"; + print " --list - list ACL or object history with/without from/to date filters\n"; + print " --clear - clear ACL or object history with/without from/to date filters\n"; + print " optional query type is one of:\n"; + print " --acls - query is for ACL history (default is --objs)\n"; + print " --objs - query is for object history (this is default)\n"; + print " optional filtering data entries by (any [name] can be regular expression):\n"; + print " --start [date/time] - starting date\n"; + print " --end [date/time] - ending date\n"; + print " --from [name] - computer name\n"; + print " --action [name] - action\n"; + print " --acl [name] - acl (with --acls only)\n"; + print " --by [name] - by address (with --acls only)\n"; + print " --type [name] - type (with --objs only)\n"; + print " --name [name] - host name with --objs, acl name with --acls\n"; +} + + +=head3 usage - prints debug messages + +=cut + +sub debug { + my $msg = @_; + return unless ( $debug_on == 1); + print $msg . "\n"; +} + + +my $parser = DateTime::Format::Strptime->new( + pattern => '%Y-%m-%dT%H:%M:%S', + on_error => 'croak', + ); + +my $opt_result = undef; +my $an_action_help = undef; +my $an_action_version = undef; +my $an_action_columns = undef; +my $an_action_list = undef; +my $an_action_clear = undef; +my $is_action_acls = 0; +my $is_action_objs = 1; +my $str_date_from = undef; +my $str_date_to = undef; +my $str_filter_computer_name = undef; +my $str_filter_action = undef; +my $str_filter_acl = undef; +my $str_filter_by = undef; +my $str_filter_type = undef; +my $str_filter_name = undef; + $opt_result = GetOptions ( + "help" => \$an_action_help + , "version" => \$an_action_version + , "columns" => \$an_action_columns + , "list" => \$an_action_list + , "clear" => \$an_action_clear + , "acls" => \$is_action_acls + , "objs" => \$is_action_objs + , "start=s" => \$str_date_from + , "end=s" => \$str_date_to + , "from=s" => \$str_filter_computer_name + , "action=s" => \$str_filter_action + , "acl=s" => \$str_filter_acl + , "by=s" => \$str_filter_by + , "type=s" => \$str_filter_type + , "name=s" => \$str_filter_name + ); + + if( ( defined $an_action_help ) or ( defined $an_action_version ) ) { + if( defined $an_action_version ) { + print "wallet-history version is $VERSION\n"; + } + if( defined $an_action_help ) { + usage; + } + exit 0; # FIN # + } + +my $str_resultset_name = undef; + if( $is_action_acls ) { + $is_action_objs = 0; + $str_resultset_name = 'AclHistory'; + } else { + $is_action_objs = 1; + $str_resultset_name = 'ObjectHistory'; + } + + if( defined $an_action_columns ) { + if( $is_action_acls ) { + print "\"ah_on\" \"ah_acl\" \"ah_by\" \"ah_action\" \"ah_from\"\n"; + } else { if( $is_action_objs ) { + print "\"oh_on\" \"oh_by\" \"oh_type\" \"oh_name\" \"oh_action\" \"oh_from\"\n"; + } else { + die "Critical internal error - unknown dataset type\n"; + } } + exit 0; # FIN # + } + + +my $date_from = undef; + if ( defined $str_date_from ) { + $date_from = $parser->parse_datetime( $str_date_from ); + debug "Using minimal(from) date $date_from\n"; + } +my $date_to = undef; + if ( defined $str_date_to ) { + $date_to = $parser->parse_datetime( $str_date_to ); + debug "Using maximal(to) date $date_to\n"; + } + + if ( $an_action_list || $an_action_clear ) { + my $schema = Wallet::Schema->connect; + my @data_entries; + eval { + my @data_entries_rs = $schema->resultset( $str_resultset_name )->search(); # ( $search_ref, $options_ref ); + for my $entry_rs (@data_entries_rs) { + if( $is_action_acls ) { + # begin - working with ACLs history + my $is_filter_passed = 1; + my $str_date_on = "" . $entry_rs->ah_on; + my $date_on = $parser->parse_datetime( $str_date_on ); + debug "date on as string ", $str_date_on, "\n"; + debug "date on as date ", $date_on, "\n"; + if ( ( defined $date_from ) || ( defined $date_to ) ) { + if ( ( defined $date_from ) && ( $date_from > $date_on ) ) { + $is_filter_passed = 0; + } + if ( ( defined $date_to ) && ( $date_to < $date_on ) ) { + $is_filter_passed = 0; + } + } # if ( ( defined $date_from ) || ( defined $date_to ) ) + if( $is_filter_passed != 0 && ( defined $str_filter_computer_name ) ) { + my $str_text_to_test = "" . $entry_rs->ah_from; + if( not ( $str_text_to_test =~ /$str_filter_computer_name/ ) ) { + $is_filter_passed = 0; + } + } # if( $is_filter_passed != 0 && ( defined $str_filter_computer_name ) ) + if( $is_filter_passed != 0 && ( defined $str_filter_action ) ) { + my $str_text_to_test = "" . $entry_rs->ah_action; + if( not ( $str_text_to_test =~ /$str_filter_action/ ) ) { + $is_filter_passed = 0; + } + } # if( $is_filter_passed != 0 && ( defined $str_filter_action ) ) + if( $is_filter_passed != 0 && ( defined $str_filter_acl ) ) { + my $str_text_to_test = "" . $entry_rs->ah_acl; + if( not ( $str_text_to_test =~ /$str_filter_acl/ ) ) { + $is_filter_passed = 0; + } + } # if( $is_filter_passed != 0 && ( defined $str_filter_acl ) ) + if( $is_filter_passed != 0 && ( defined $str_filter_by ) ) { + my $str_text_to_test = "" . $entry_rs->ah_by; + if( not ( $str_text_to_test =~ /$str_filter_by/ ) ) { + $is_filter_passed = 0; + } + } # if( $is_filter_passed != 0 && ( defined $str_filter_by ) ) + if( $is_filter_passed != 0 && ( defined $str_filter_name ) ) { + my $str_text_to_test = "" . $entry_rs->ah_name; + if( not ( $str_text_to_test =~ /$str_filter_name/ ) ) { + $is_filter_passed = 0; + } + } # if( $is_filter_passed != 0 && ( defined $str_filter_name ) ) + if( $is_filter_passed != 0 ) { + push (@data_entries, [$entry_rs->ah_on, $entry_rs->ah_acl, $entry_rs->ah_by, $entry_rs->ah_action, $entry_rs->ah_from, $entry_rs->ah_name ]); + debug "Found: \"", $entry_rs->ah_on, "\" \"", $entry_rs->ah_acl, "\" \"", $entry_rs->ah_by, "\" \"", $entry_rs->ah_action, "\" \"", $entry_rs->ah_from, "\" \"", $entry_rs->ah_name, "\n"; + } # if( $is_filter_passed != 0 ) + debug "walked throgh dates \"", ref($date_from), "-", $date_from, "\" \"", ref($date_on), "-", $date_on, "\" \"", ref($date_to), "-", $date_to, "\" result is ", $is_filter_passed, "\n"; + # end - working with ACLs history + } else { if( $is_action_objs ) { + # begin - working with objects history + my $is_filter_passed = 1; + my $str_date_on = "" . $entry_rs->oh_on; + my $date_on = $parser->parse_datetime( $str_date_on ); + debug "date on as string ", $str_date_on, "\n"; + debug "date on as date ", $date_on, "\n"; + if ( ( defined $date_from ) || ( defined $date_to ) ) { + if ( ( defined $date_from ) && ( $date_from > $date_on ) ) { + $is_filter_passed = 0; + } + if ( ( defined $date_to ) && ( $date_to < $date_on ) ) { + $is_filter_passed = 0; + } + } # if ( ( defined $date_from ) || ( defined $date_to ) ) + if( $is_filter_passed != 0 && ( defined $str_filter_computer_name ) ) { + my $str_text_to_test = "" . $entry_rs->oh_from; + if( not ( $str_text_to_test =~ /$str_filter_computer_name/ ) ) { + $is_filter_passed = 0; + } + } # if( $is_filter_passed != 0 && ( defined $str_filter_computer_name ) ) + if( $is_filter_passed != 0 && ( defined $str_filter_action ) ) { + my $str_text_to_test = "" . $entry_rs->oh_action; + if( not ( $str_text_to_test =~ /$str_filter_action/ ) ) { + $is_filter_passed = 0; + } + } # if( $is_filter_passed != 0 && ( defined $str_filter_action ) ) + if( $is_filter_passed != 0 && ( defined $str_filter_type ) ) { + my $str_text_to_test = "" . $entry_rs->oh_type; + if( not ( $str_text_to_test =~ /$str_filter_type/ ) ) { + $is_filter_passed = 0; + } + } # if( $is_filter_passed != 0 && ( defined $str_filter_type ) ) + if( $is_filter_passed != 0 && ( defined $str_filter_name ) ) { + my $str_text_to_test = "" . $entry_rs->oh_name; + if( not ( $str_text_to_test =~ /$str_filter_name/ ) ) { + $is_filter_passed = 0; + } + } # if( $is_filter_passed != 0 && ( defined $str_filter_name ) ) + if( $is_filter_passed != 0 ) { + push (@data_entries, [$entry_rs->oh_on, $entry_rs->oh_by, $entry_rs->oh_type, $entry_rs->oh_name, $entry_rs->oh_action, $entry_rs->oh_from ]); + debug "Found: \"", $entry_rs->oh_on, "\" \"", $entry_rs->oh_by, "\" \"", $entry_rs->oh_type, "\" \"", $entry_rs->oh_name, "\" \"", $entry_rs->oh_action, "\" \"", $entry_rs->oh_from, "\"", "\n"; + } # if( $is_filter_passed != 0 ) + debug "walked throgh dates \"", ref($date_from), "-", $date_from, "\" \"", ref($date_on), "-", $date_on, "\" \"", ref($date_to), "-", $date_to, "\" result is ", $is_filter_passed, "\n"; + # end - working with objects history + } else { + die "Critical internal error - unknown dataset type\n"; + } } + } # for my $entry_rs (@data_entries_rs) + }; # eval + if ($@) { + print "cannot list data entries: $@"; + exit -1; + } + + if( $is_action_acls ) { + # begin - working with ACLs history + for my $group (@data_entries) { + if ( $an_action_clear ) { + my $is_deleted_ok = 0; + print "\"", join ("\" \"", @$group), "\" ... "; + my $sql_delete_error = undef; + eval { + my $str_ah_on = "" . $group->[0]; + my $str_ah_acl = "" . $group->[1]; + my $str_ah_by = "" . $group->[2]; + my $str_ah_action = "" . $group->[3]; + my $str_ah_from = "" . $group->[4]; + my $str_ah_name = "" . $group->[5]; + + my $string = $str_ah_on; # '01234567890'; + my $find = 'T'; # '0'; + my $replace = ' '; # 'a'; + my $pos = index($string, $find); + while ( $pos > -1 ) { + substr( $string, $pos, length( $find ), $replace ); + $pos = index( $string, $find, $pos + length( $replace )); + } + $str_ah_on = $string; + + debug "Searching \"$str_ah_on\" \"$str_ah_acl\" \"$str_ah_by\" \"$str_ah_action\" \"$str_ah_from\" \"$str_ah_name\" ... \n"; + + #my @data_entries_rs = + # $schema->resultset('AclHistory')->search( [ + # { ah_on => $str_ah_on } + # , { ah_acl => $str_ah_acl } + # , { ah_by => $str_ah_by } + # , { ah_action => $str_ah_action } + # , { ah_from => $str_ah_from } + # , { ah_name => $str_ah_name } + # ] ); # -> delete; + + #my @data_entries_rs = + # $schema->resultset('AclHistory')->search( [ + # { ah_on => { '=' , $str_ah_on } } + # , { ah_acl => { '=' , $str_ah_acl } } + # , { ah_by => { '=' , $str_ah_by } } + # , { ah_action => { '=' , $str_ah_action } } + # , { ah_from => { '=' , $str_ah_from } } + # , { ah_name => { '=' , $str_ah_name } } + # ] ); # -> delete; + + my $str_sql_del = "DELETE FROM acl_history WHERE ah_on = TO_DATE(\'" . $str_ah_on . "\', 'YYYY-MM-DD HH24:MI:SS') AND ah_acl = \'" . $str_ah_acl . "\' AND ah_by = \'" . $str_ah_by . "\' AND ah_action = \'" . $str_ah_action . "\' AND ah_from = \'" . $str_ah_from . "\' AND ah_name = \'" . $str_ah_name . "\'"; + + #$schema->storage->debug(1); + my @ret = $schema->storage->dbh_do( + sub { + my ($storage, $dbh, @args) = @_; + debug "Attempting to ", $str_sql_del, "\n"; + debug "dbh is ", $dbh, "\n"; + my $sth = $dbh->prepare( $str_sql_del ); + #$sth->{PrintError} = 1; + #$sth->{RaiseError} = 1; + debug "sth is ", $sth, "\n"; + my $ret = $sth->execute(); # or die "Can't execute SQL statement: $DBI::errstr\n"; + debug "ret is ", $ret, "\n"; + debug "error string is", $sth->errstr(), "\n"; + if ( $ret == 0 ) { + $sql_delete_error = "Internal error"; + } else { + $is_deleted_ok = 1; + } + }, + $str_sql_del + ); + + #$is_deleted_ok = 1; + + #for my $entry_rs (@data_entries_rs) { + # print "Will delete: \"", $entry_rs->ah_on, "\" \"", $entry_rs->ah_acl, "\" \"", $entry_rs->ah_by, "\" \"", $entry_rs->ah_action, "\" \"", $entry_rs->ah_from, "\"", "\n"; + #} # for my $entry_rs (@data_entries_rs) + + }; # eval + if ( $is_deleted_ok ) { + print "Deleted\n"; + } else { + print "Error ", $sql_delete_error, "\n"; + } + } else { # else from if ( $an_action_clear ) + print "\"", join ("\" \"", @$group), "\"\n"; + } # else from if ( $an_action_clear ) + } # for my $group (@data_entries) + # end - working with ACLs history + } else { if( $is_action_objs ) { + # begin - working with objects history + for my $group (@data_entries) { + if ( $an_action_clear ) { + my $is_deleted_ok = 0; + print "\"", join ("\" \"", @$group), "\" ... "; + my $sql_delete_error = undef; + eval { + my $str_oh_on = "" . $group->[0]; + my $str_oh_by = "" . $group->[1]; + my $str_oh_type = "" . $group->[2]; + my $str_oh_name = "" . $group->[3]; + my $str_oh_action = "" . $group->[4]; + my $str_oh_from = "" . $group->[5]; + + my $string = $str_oh_on; # '01234567890'; + my $find = 'T'; # '0'; + my $replace = ' '; # 'a'; + my $pos = index($string, $find); + while ( $pos > -1 ) { + substr( $string, $pos, length( $find ), $replace ); + $pos = index( $string, $find, $pos + length( $replace )); + } + $str_oh_on = $string; + + debug "Searching \"$str_oh_on\" \"$str_oh_by\" \"$str_oh_type\" \"$str_oh_name\" \"$str_oh_action\" \"$str_oh_from\" ... \n"; + + #my @data_entries_rs = + # $schema->resultset('ObjectHistory')->search( [ + # { oh_on => $str_oh_on } + # , { oh_type => $str_oh_type } + # , { oh_name => $str_oh_name } + # , { oh_action => $str_oh_action } + # , { oh_from => $str_oh_from } + # ] ); # -> delete; + + #my @data_entries_rs = + # $schema->resultset('ObjectHistory')->search( [ + # { oh_on => { '=' , $str_oh_on } } + # , { oh_type => { '=' , $str_oh_type } } + # , { oh_name => { '=' , $str_oh_name } } + # , { oh_action => { '=' , $str_oh_action } } + # , { oh_from => { '=' , $str_oh_from } } + # ] ); # -> delete; + + my $str_sql_del = "DELETE FROM object_history WHERE oh_on = TO_DATE(\'" . $str_oh_on . "\', 'YYYY-MM-DD HH24:MI:SS') AND oh_type = \'" . $str_oh_type . "\' AND oh_name = \'" . $str_oh_name . "\' AND oh_action = \'" . $str_oh_action . "\' AND oh_from = \'" . $str_oh_from . "\'"; + + #$schema->storage->debug(1); + my @ret = $schema->storage->dbh_do( + sub { + my ($storage, $dbh, @args) = @_; + debug "Attempting to ", $str_sql_del, "\n"; + debug "dbh is ", $dbh, "\n"; + my $sth = $dbh->prepare( $str_sql_del ); + #$sth->{PrintError} = 1; + #$sth->{RaiseError} = 1; + debug "sth is ", $sth, "\n"; + my $ret = $sth->execute(); # or die "Can't execute SQL statement: $DBI::errstr\n"; + debug "ret is ", $ret, "\n"; + debug "error string is", $sth->errstr(), "\n"; + if ( $ret == 0 ) { + $sql_delete_error = "Internal error"; + } else { + $is_deleted_ok = 1; + } + }, + $str_sql_del + ); + + #$is_deleted_ok = 1; + + #for my $entry_rs (@data_entries_rs) { + # print "Will delete: \"", $entry_rs->oh_on, "\" \"", $entry_rs->oh_type, "\" \"", $entry_rs->oh_name, "\" \"", $entry_rs->oh_action, "\" \"", $entry_rs->oh_from, "\"", "\n"; + #} # for my $entry_rs (@data_entries_rs) + + }; # eval + if ( $is_deleted_ok ) { + print "Deleted\n"; + } else { + print "Error ", $sql_delete_error, "\n"; + } + } else { # else from if ( $an_action_clear ) + print "\"", join ("\" \"", @$group), "\"\n"; + } # else from if ( $an_action_clear ) + } # for my $group (@data_entries) + # end - working with objects history + } else { + die "Critical internal error - unknown dataset type\n"; + } } + + exit 0; # FIN # + } # if ( $an_action_list || $an_action_clear ) + + usage; + exit 0; # FIN # -- cgit v1.2.3 From 956cc93882ebf1fa331a75abe8a85cdbcad64486 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Tue, 17 Feb 2015 12:40:39 -0800 Subject: Updated NEWS with changes so far Change-Id: Icb894b4b52e6b5c07a7c12251b1f4c79025c7bc6 --- NEWS | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/NEWS b/NEWS index 272b109..64c0fbc 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,24 @@ User-Visible wallet Changes +wallet 1.3 (xxxx-xx-xx) + + A new object type, password (Wallet::Object::Password), is now + supported. This is a subclass of the file object that will randomly + generate content for the object if you do a get before storing any + content inside it. + + Added an acl replace command, to change all objects owned by one ACL + to be owned by another. + + Added a report for unstored objects to wallet-report, and cleaned up + the help for the existing unused report that implied it showed + unstored as well as unused. + + Took contributions from Commerzbank AG on the wallet history. Added + a command to dump all object history for searching on to + wallet-report, and added a new script for more detailed object + history operations to the contrib directory. + wallet 1.2 (2014-12-08) The duo object type has been split into several sub-types, each for a -- cgit v1.2.3 From ed628d6c89d63fdcbf038ce19776e3047c1105e2 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Tue, 17 Feb 2015 13:46:11 -0800 Subject: Added ssl-chain name prefix to Stanford policy Added for SSL files including the root cert as well, used in splunk. Change-Id: I1faaa840d309ae4370ae26da5b51c0cee84d7558 --- perl/lib/Wallet/Policy/Stanford.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/perl/lib/Wallet/Policy/Stanford.pm b/perl/lib/Wallet/Policy/Stanford.pm index 39a8174..362f098 100644 --- a/perl/lib/Wallet/Policy/Stanford.pm +++ b/perl/lib/Wallet/Policy/Stanford.pm @@ -68,6 +68,7 @@ our %FILE_TYPE = ( properties => { extra => 1 }, 'ssh-dsa' => { host => 1, extra => 1 }, 'ssh-rsa' => { host => 1, extra => 1 }, + 'ssl-chain' => { host => 1, extra => 1 }, 'ssl-key' => { host => 1, extra => 1 }, 'ssl-keypair' => { host => 1, extra => 1 }, 'ssl-keystore' => { extra => 1 }, -- cgit v1.2.3 From 1575e25c9e9edc8d577a0f1118732c98871984e0 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Tue, 17 Feb 2015 14:32:59 -0800 Subject: Added reports to list all types and acl schemes Two new reports, 'types' and 'schemes'. These will print out all configured types and acl schemes. Change-Id: Ib06d37755fe80c168a6f723c9a1e683fdf5dfcde --- perl/lib/Wallet/Report.pm | 38 ++++++++++++++++++++++++++++++++++++++ perl/t/general/report.t | 26 +++++++++++++++++++++++++- server/wallet-report | 16 ++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/perl/lib/Wallet/Report.pm b/perl/lib/Wallet/Report.pm index 912bc17..4d92d64 100644 --- a/perl/lib/Wallet/Report.pm +++ b/perl/lib/Wallet/Report.pm @@ -284,6 +284,27 @@ sub objects_history { return @objects; } +############################################################################## +# Type reports +############################################################################## + +# Return an alphabetical list of all valid types set up, along with the class +# that they belong to. +sub types { + my ($self) = @_; + + my (@types); + my @types_rs = $self->{schema}->resultset('Type')->all; + for my $type_rs (@types_rs) { + my $name = $type_rs->ty_name; + my $class = $type_rs->ty_class; + push(@types, [ $name, $class ]); + } + + @types = sort { $a->[0] cmp $b->[0] } @types; + return @types; +} + ############################################################################## # ACL reports ############################################################################## @@ -527,6 +548,23 @@ sub owners { return @owners; } +# Return an alphabetical list of all valid types set up, along with the class +# that they belong to. +sub acl_schemes { + my ($self) = @_; + + my (@schemes); + my @acls_rs = $self->{schema}->resultset('AclScheme')->all; + for my $acl_rs (@acls_rs) { + my $name = $acl_rs->as_name; + my $class = $acl_rs->as_class; + push(@schemes, [ $name, $class ]); + } + + @schemes = sort { $a->[0] cmp $b->[0] } @schemes; + return @schemes; +} + ############################################################################## # Auditing ############################################################################## diff --git a/perl/t/general/report.t b/perl/t/general/report.t index 8d348ed..a63ab79 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 => 197; +use Test::More tests => 215; use Wallet::Admin; use Wallet::Report; @@ -41,6 +41,30 @@ is (scalar (@acls), 1, 'One ACL in the database'); is ($acls[0][0], 1, ' and that is ACL ID 1'); is ($acls[0][1], 'ADMIN', ' with the right name'); +# Check to see that we have all types that we expect. +my @types = $report->types; +is (scalar (@types), 10, 'There are ten types created'); +is ($types[0][0], 'base', ' and the first member is correct'); +is ($types[1][0], 'duo', ' and the second member is correct'); +is ($types[2][0], 'duo-ldap', ' and the third member is correct'); +is ($types[3][0], 'duo-pam', ' and the fourth member is correct'); +is ($types[4][0], 'duo-radius', ' and the fifth member is correct'); +is ($types[5][0], 'duo-rdp', ' and the sixth member is correct'); +is ($types[6][0], 'file', ' and the seventh member is correct'); +is ($types[7][0], 'keytab', ' and the eighth member is correct'); +is ($types[8][0], 'password', ' and the nineth member is correct'); +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), 6, 'There are six 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], 'netdb', ' and the fifth member is correct'); +is ($schemes[5][0], 'netdb-root', ' and the sixth member is correct'); + # Create an object. my $server = eval { Wallet::Server->new ('admin@EXAMPLE.COM', 'localhost') }; is ($@, '', 'Creating a server instance did not die'); diff --git a/server/wallet-report b/server/wallet-report index bc499d4..6d1436c 100755 --- a/server/wallet-report +++ b/server/wallet-report @@ -29,6 +29,8 @@ Wallet reporting help: objects unused Objects that have never been gotten objects unstored Objects that have never been stored owners All ACL entries owning matching objects + schemes All configured ACL schemes + types All configured wallet types EOH ############################################################################## @@ -98,6 +100,20 @@ sub command { for my $entry (@entries) { print join (' ', @$entry), "\n"; } + } elsif ($command eq 'schemes') { + die "too many arguments to schemes\n" if @args > 0; + my @schemes = $report->acl_schemes; + for my $entry (@schemes) { + print join (' ', @$entry), "\n"; + } + + } elsif ($command eq 'types') { + die "too many arguments to types\n" if @args > 0; + my @types = $report->types; + for my $entry (@types) { + print join (' ', @$entry), "\n"; + } + } else { die "unknown command $command\n"; } -- cgit v1.2.3 From 000b338694fae87996220336678fe990a1c3e3e1 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Wed, 18 Feb 2015 15:17:51 -0800 Subject: Added new method for wallet-backend, update update will work generally like get, but only for objects that have a concept of updating content automatically, like keytabs and passwords. For these, the content will be updated before sending to the client. In a later release get for keytabs will be modified to never update the kvno before sending to the user, and so the unchanging flag will be phased out in lieu of explicitly using the method that does what you want. Change-Id: I96a84416c5e50278eb29fe07052dde6e063bc071 --- NEWS | 9 ++++++ client/wallet.pod | 13 +++++++++ perl/lib/Wallet/Object/Base.pm | 9 ++++++ perl/lib/Wallet/Object/Keytab.pm | 59 ++++++++++++++++++++++++++------------ perl/lib/Wallet/Object/Password.pm | 32 ++++++++++++++++----- perl/lib/Wallet/Server.pm | 15 ++++++++++ perl/t/object/base.t | 5 +++- perl/t/object/password.t | 8 +++++- server/wallet-backend | 16 +++++++++++ 9 files changed, 138 insertions(+), 28 deletions(-) diff --git a/NEWS b/NEWS index 64c0fbc..664da05 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,15 @@ wallet 1.3 (xxxx-xx-xx) generate content for the object if you do a get before storing any content inside it. + Added a new command to wallet-backend, update. This will update the + contents of an object before running a get on it, and is only valid + for objects that can automatically get new content, such as keytab + and password objects. A keytab will get a new kvno regardless of + the unchanging flag if called with update. In a future release get + will be changed to never update a keytab, and the unchanging flag + will be ignored. Please start moving to use get or update as the + situation warrants. + Added an acl replace command, to change all objects owned by one ACL to be owned by another. diff --git a/client/wallet.pod b/client/wallet.pod index 20d1874..672f0e4 100644 --- a/client/wallet.pod +++ b/client/wallet.pod @@ -385,6 +385,19 @@ If an object with type and name does not already exist when this command is issued (as checked with the check interface), B will attempt to automatically create it (using autocreate). +=item update + +Prints to standard output the data associated with the object identified +by and , or stores it in a file if the B<-f> option was +given. This will generate new data in the object, and only works for +objects that support generating new data automatically, such as keytabs or +passwords. Types that do not support generating new data will fail and +direct you to use get instead. + +If an object with type and name does not already exist when +this command is issued (as checked with the check interface), B +will attempt to automatically create it (using autocreate). + =back =head1 ATTRIBUTES diff --git a/perl/lib/Wallet/Object/Base.pm b/perl/lib/Wallet/Object/Base.pm index bdd61fb..97e6127 100644 --- a/perl/lib/Wallet/Object/Base.pm +++ b/perl/lib/Wallet/Object/Base.pm @@ -609,6 +609,15 @@ sub history { # The get methods must always be overridden by the subclass. sub get { die "Do not instantiate Wallet::Object::Base directly\n"; } +# The update method should only work if a subclass supports it as something +# different from get. That makes it explicit about whether the subclass has +# a meaningful update. +sub update { + my ($self) = @_; + $self->error ("update is not supported for this type, use get instead"); + return; +} + # Provide a default store implementation that returns an immutable object # error so that auto-generated types don't have to provide their own. sub store { diff --git a/perl/lib/Wallet/Object/Keytab.pm b/perl/lib/Wallet/Object/Keytab.pm index 975179b..c625766 100644 --- a/perl/lib/Wallet/Object/Keytab.pm +++ b/perl/lib/Wallet/Object/Keytab.pm @@ -28,6 +28,37 @@ use Wallet::Kadmin; # that it will sort properly. $VERSION = '0.09'; +############################################################################## +# Shared methods +############################################################################## + +# Generate a keytab into a temporary file and then return that as the return +# value. Used by both get and update, as the only difference is how we +# handle the unchanging flag. +sub retrieve { + my ($self, $operation, $user, $host, $time) = @_; + $time ||= time; + my $id = $self->{type} . ':' . $self->{name}; + if ($self->flag_check ('locked')) { + $self->error ("cannot get $id: object is locked"); + return; + } + my $kadmin = $self->{kadmin}; + my $result; + if ($operation eq 'get' && $self->flag_check ('unchanging')) { + $result = $kadmin->keytab ($self->{name}); + } else { + my @enctypes = $self->attr ('enctypes'); + $result = $kadmin->keytab_rekey ($self->{name}, @enctypes); + } + if (defined $result) { + $self->log_action ($operation, $user, $host, $time); + } else { + $self->error ($kadmin->error); + } + return $result; +} + ############################################################################## # Enctype restriction ############################################################################## @@ -314,25 +345,15 @@ sub destroy { # return that as the return value. sub get { my ($self, $user, $host, $time) = @_; - $time ||= time; - my $id = $self->{type} . ':' . $self->{name}; - if ($self->flag_check ('locked')) { - $self->error ("cannot get $id: object is locked"); - return; - } - my $kadmin = $self->{kadmin}; - my $result; - if ($self->flag_check ('unchanging')) { - $result = $kadmin->keytab ($self->{name}); - } else { - my @enctypes = $self->attr ('enctypes'); - $result = $kadmin->keytab_rekey ($self->{name}, @enctypes); - } - if (defined $result) { - $self->log_action ('get', $user, $host, $time); - } else { - $self->error ($kadmin->error); - } + my $result = $self->retrieve ('get', $user, $host, $time); + return $result; +} + +# Our update implementation. Generate a new keytab regardless of the +# unchanging flag. +sub update { + my ($self, $user, $host, $time) = @_; + my $result = $self->retrieve ('update', $user, $host, $time); return $result; } diff --git a/perl/lib/Wallet/Object/Password.pm b/perl/lib/Wallet/Object/Password.pm index d06c8a6..3fd6ec8 100644 --- a/perl/lib/Wallet/Object/Password.pm +++ b/perl/lib/Wallet/Object/Password.pm @@ -57,12 +57,12 @@ sub file_path { } ############################################################################## -# Core methods +# Shared methods ############################################################################## # Return the contents of the file. -sub get { - my ($self, $user, $host, $time) = @_; +sub retrieve { + my ($self, $operation, $user, $host, $time) = @_; $time ||= time; my $id = $self->{type} . ':' . $self->{name}; if ($self->flag_check ('locked')) { @@ -72,13 +72,13 @@ sub get { my $path = $self->file_path; return unless $path; - # If nothing is yet stored, generate a random password and save it to - # the file. + # If nothing is yet stored, or we have requested an update, generate a + # random password and save it to the file. my $schema = $self->{schema}; my %search = (ob_type => $self->{type}, ob_name => $self->{name}); my $object = $schema->resultset('Object')->find (\%search); - unless ($object->ob_stored_on) { + if (!$object->ob_stored_on || $operation eq 'update') { unless (open (FILE, '>', $path)) { $self->error ("cannot store initial settings for $id: $!\n"); return; @@ -103,10 +103,28 @@ sub get { $self->error ("cannot get $id: $!"); return; } - $self->log_action ('get', $user, $host, $time); + $self->log_action ($operation, $user, $host, $time); return $data; } +############################################################################## +# Core methods +############################################################################## + +# Return the contents of the file. +sub get { + my ($self, $user, $host, $time) = @_; + my $result = $self->retrieve ('get', $user, $host, $time); + return $result; +} + +# Return the contents of the file after resetting them to a random string. +sub update { + my ($self, $user, $host, $time) = @_; + my $result = $self->retrieve ('update', $user, $host, $time); + return $result; +} + 1; __END__ diff --git a/perl/lib/Wallet/Server.pm b/perl/lib/Wallet/Server.pm index 6af0570..3ef5954 100644 --- a/perl/lib/Wallet/Server.pm +++ b/perl/lib/Wallet/Server.pm @@ -516,6 +516,21 @@ sub get { return $result; } +# Retrieve the information associated with an object, updating the current +# information if we are of a type that allows autogenerated information. +# Returns undef and sets the internal error if the retrieval fails or if the +# user isn't authorized. If the object doesn't exist, attempts dynamic +# creation of the object using the default ACL mappings (if any). +sub update { + my ($self, $type, $name) = @_; + my $object = $self->retrieve ($type, $name); + return unless defined $object; + return unless $self->acl_verify ($object, 'get'); + my $result = $object->update ($self->{user}, $self->{host}); + $self->error ($object->error) unless defined $result; + return $result; +} + # Store new data in an object, or returns undef and sets the internal error if # the object can't be found or if the user isn't authorized. Also don't # permit storing undef, although storing the empty string is fine. If the diff --git a/perl/t/object/base.t b/perl/t/object/base.t index ee9ff4b..8fedd64 100755 --- a/perl/t/object/base.t +++ b/perl/t/object/base.t @@ -12,7 +12,7 @@ use strict; use warnings; use POSIX qw(strftime); -use Test::More tests => 137; +use Test::More tests => 139; use Wallet::ACL; use Wallet::Admin; @@ -208,6 +208,9 @@ is ($object->flag_clear ('locked', @trace), 1, 'Clearing locked succeeds'); eval { $object->get (@trace) }; is ($@, "Do not instantiate Wallet::Object::Base directly\n", 'Get fails with the right error'); +ok (!$object->update (@trace), 'Update fails'); +is ($object->error, 'update is not supported for this type, use get instead', + ' with the right error'); ok (! $object->store ("Some data", @trace), 'Store fails'); is ($object->error, "cannot store keytab:$princ: object type is immutable", ' with the right error'); diff --git a/perl/t/object/password.t b/perl/t/object/password.t index c0f2fbc..4fe6b50 100644 --- a/perl/t/object/password.t +++ b/perl/t/object/password.t @@ -13,7 +13,7 @@ use strict; use warnings; use POSIX qw(strftime); -use Test::More tests => 31; +use Test::More tests => 33; use Wallet::Admin; use Wallet::Config; @@ -111,6 +111,12 @@ ok (-f 'test-files/09/test', ' and the file exists'); is (contents ('test-files/09/test'), 'bar', ' with the right contents'); is ($object->get (@trace), "bar\n\0baz\n", ' and get returns correctly'); +# And check to make sure update changes the contents. +$pwd = $object->update (@trace); +isnt ($pwd, "bar\n\0baz\n", 'Update changes the contents'); +like ($pwd, qr{^.{$Wallet::Config::PWD_LENGTH_MIN}$}, + ' to a random password string of the right length'); + # Clean up. $admin->destroy; END { diff --git a/server/wallet-backend b/server/wallet-backend index dcf2300..ea3e21e 100755 --- a/server/wallet-backend +++ b/server/wallet-backend @@ -315,6 +315,14 @@ sub command { } 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"; } @@ -611,6 +619,14 @@ Stores for the object identified by and for later retrieval with C. Not all object types support this. If is not given as an argument, it will be read from standard input. +=item update + +Prints to standard output the data associated with the object identified +by and . 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 -- cgit v1.2.3 From f3da929ff35f24163ab56dbd9615ee6a1c5336d8 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Wed, 18 Feb 2015 21:48:56 -0800 Subject: wallet-summary: Fixed wrong name in a function call Change-Id: I4bcc9c318ab3ec09add026e14204d929125302b7 --- contrib/wallet-summary | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/wallet-summary b/contrib/wallet-summary index 5cbf6e0..ba224d0 100755 --- a/contrib/wallet-summary +++ b/contrib/wallet-summary @@ -146,7 +146,7 @@ if ($mail) { } # Run the report. -my @principals = read_dump (); +my @principals = list_keytabs (); report_principals (@principals); # If -m was given, take the saved report and mail it as well. -- cgit v1.2.3 From 45a7c9d2896cf2e0d1548fd98b3b78f9f812744f Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Thu, 16 Apr 2015 14:58:58 -0700 Subject: wallet-report: Added report of all host-based objects for host "wallet-report objects host " reports on all objects that belong to the given host. This can be used to query things for retiring systems. Change-Id: Ib1c8e5978fed141d54ecc8504b56b43c037f9b17 --- perl/lib/Wallet/Config.pm | 28 +++++++++++++++ perl/lib/Wallet/Policy/Stanford.pm | 49 +++++++++++++++++--------- perl/lib/Wallet/Report.pm | 70 +++++++++++++++++++++++++++++++++++++- perl/t/general/report.t | 18 +++++++++- perl/t/policy/stanford.t | 28 +++++++++++++-- server/wallet-report | 3 ++ 6 files changed, 176 insertions(+), 20 deletions(-) diff --git a/perl/lib/Wallet/Config.pm b/perl/lib/Wallet/Config.pm index 76c7ecd..b3e1931 100644 --- a/perl/lib/Wallet/Config.pm +++ b/perl/lib/Wallet/Config.pm @@ -792,6 +792,34 @@ keytab objects for particular principals have fully-qualified hostnames: Objects that aren't of type C or which aren't for a host-based key have no naming requirements enforced by this example. +=head1 OBJECT HOST-BASED NAMES + +The above demonstrates having a host-based naming convention, where we +expect one part of an object name to be the name of the host that this +object is for. The most obvious examples are those keytab objects +above, where we want certain keytab names to be in the form of +/. It's then also useful to provide a Perl function +named is_for_host which then can be used to tell if a given object is a +host-based keytab for a specific host. This function is then called by +the objects_hostname in Wallet::Report to give a list of all host-based +objects for a given hostname. It should return true if the given object +is a host-based object for the hostname, otherwise false. + +An example that matches the same policy as the last verify_name example +would be: + + sub is_for_host { + my ($type, $name, $hostname) = @_; + my %host_based = map { $_ => 1 } + qw(HTTP cifs host imap ldap nfs pop sieve smtp webauth); + return 0 unless $type eq 'keytab'; + return 0 unless $name =~ m%/%; + my ($service, $instance) = split ('/', $name, 2); + return 0 unless $host_based{$service}; + return 1 if $hostname eq $instance; + return 0; + } + =head1 ACL NAMING ENFORCEMENT Similar to object names, by default wallet permits administrators to diff --git a/perl/lib/Wallet/Policy/Stanford.pm b/perl/lib/Wallet/Policy/Stanford.pm index 362f098..86e204e 100644 --- a/perl/lib/Wallet/Policy/Stanford.pm +++ b/perl/lib/Wallet/Policy/Stanford.pm @@ -25,8 +25,8 @@ our (@EXPORT_OK, $VERSION); # against circular module loading (not that we load any modules, but # consistency is good). BEGIN { - $VERSION = '1.00'; - @EXPORT_OK = qw(default_owner verify_name); + $VERSION = '1.01'; + @EXPORT_OK = qw(default_owner verify_name is_for_host); } ############################################################################## @@ -87,6 +87,18 @@ our %PASSWORD_TYPE = ( 'service' => { extra => 1, need_extra => 1 }, ); +# Mappings that let us determine the host for a host-based object, if any. +our %HOST_FOR = ( + 'keytab' => \&_host_for_keytab, + 'file' => \&_host_for_file, + 'password' => \&_host_for_password, + 'duo' => \&_host_for_duo, + 'duo-pam' => \&_host_for_duo, + 'duo-radius' => \&_host_for_duo, + 'duo-ldap' => \&_host_for_duo, + 'duo-rdp' => \&_host_for_duo, +); + # Host-based file object types for the legacy file object naming scheme. our @FILE_HOST_LEGACY = qw(htpasswd ssh-rsa ssh-dsa ssl-key tivoli-key); @@ -204,6 +216,23 @@ sub _host_for_duo { return $name; } +# Take a object type and name, along with a host name, and use these to +# decide if the given object is host-based and matches the given host. +sub is_for_host { + my ($type, $name, $host) = @_; + + # If we have a possible host mapping, get the host and see if it matches. + if (defined($HOST_FOR{$type})) { + my $object_host = $HOST_FOR{$type}->($name); + return 0 unless $object_host; + if ($host eq $object_host) { + return 1; + } + } + + return 0; +} + # The default owner of host-based objects should be the host keytab and the # NetDB ACL for that host, with one twist. If the creator of a new node is # using a root instance, we want to require everyone managing that node be @@ -211,21 +240,9 @@ sub _host_for_duo { sub default_owner { my ($type, $name) = @_; - # How to determine the host for host-based objects. - my %host_for = ( - 'keytab' => \&_host_for_keytab, - 'file' => \&_host_for_file, - 'password' => \&_host_for_password, - 'duo' => \&_host_for_duo, - 'duo-pam' => \&_host_for_duo, - 'duo-radius' => \&_host_for_duo, - 'duo-ldap' => \&_host_for_duo, - 'duo-rdp' => \&_host_for_duo, - ); - # If we have a possible host mapping, see if we can use that. - if (defined($host_for{$type})) { - my $host = $host_for{$type}->($name); + if (defined($HOST_FOR{$type})) { + my $host = $HOST_FOR{$type}->($name); if ($host) { my $acl_name = "host/$host"; my @acl; diff --git a/perl/lib/Wallet/Report.pm b/perl/lib/Wallet/Report.pm index 4d92d64..fc7bb4d 100644 --- a/perl/lib/Wallet/Report.pm +++ b/perl/lib/Wallet/Report.pm @@ -249,7 +249,7 @@ sub objects { # Farms out specific statement to another subroutine for specific search # types, but each case should return ob_type and ob_name in that order. sub objects_history { - my ($self, $type, @args) = @_; + my ($self, $search_type, @args) = @_; undef $self->{error}; # All fields in the order we want to see them. @@ -284,6 +284,56 @@ sub objects_history { return @objects; } +# Returns a list of all objects stored in the wallet database in the form of +# type and name pairs. On error and for an empty database, the empty list +# will be returned. To distinguish between an empty list and an error, call +# error(), which will return undef if there was no error. Farms out specific +# statement to another subroutine for specific search types, but each case +# should return ob_type and ob_name in that order. +sub objects_hostname { + my ($self, $type, $hostname) = @_; + undef $self->{error}; + + # Make sure we have a given hostname. + if (!$hostname) { + $self->error ("object hosts requires one argument to search"); + return; + } + + # If we don't have a way to get host-based object lists, quit. + unless (defined &Wallet::Config::is_for_host) { + $self->error ('no host-based policy defined'); + return; + } + + # Search on all objects. + my %search = (); + my %options = (order_by => [ qw/ob_type ob_name/ ], + select => [ qw/ob_type ob_name/ ]); + + my @objects; + my $schema = $self->{schema}; + eval { + my @objects_rs = $schema->resultset('Object')->search (\%search, + \%options); + + # Check to see if an object is for the given host and add to list if + # so. + for my $object_rs (@objects_rs) { + my $type = $object_rs->ob_type; + my $name = $object_rs->ob_name; + next unless &Wallet::Config::is_for_host($type, $name, $hostname); + push (@objects, [ $type, $name ]); + } + }; + if ($@) { + $self->error ("cannot list objects: $@"); + return; + } + + return @objects; +} + ############################################################################## # Type reports ############################################################################## @@ -753,6 +803,24 @@ empty search result, the caller should call error(). error() is guaranteed to return the error message if there was an error and undef if there was no error. +=item objects_history(TYPE) + +Returns a dump of the entire object history table. The return value is +a list of references to each field in that table, in the following order: + + oh_on, oh_by, oh_type, oh_name, oh_action, oh_from + +=item objects_hostname(TYPE, HOSTNAME) + +Returns a list of all host-based objects for a given hostname. The +output is identical to the general objects command, but we need to +separate this out because the way it searches is very different. + +Returns the empty list on failure. To distinguish between this and an +empty search result, the caller should call error(). error() is +guaranteed to return the error message if there was an error and undef if +there was no error. + =item owners(TYPE, NAME) Returns a list of all ACL lines contained in owner ACLs for objects diff --git a/perl/t/general/report.t b/perl/t/general/report.t index a63ab79..170fe29 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 => 215; +use Test::More tests => 218; use Wallet::Admin; use Wallet::Report; @@ -281,6 +281,22 @@ is (scalar (@lines), 1, 'Searching for ACL naming violations finds one'); is ($lines[0][0], 3, ' and the first has the right ID'); is ($lines[0][1], 'second', ' and the right name'); +# Set a host-based object matching script so that we can test the host report. +# The deactivation trick isn't needed here. +package Wallet::Config; +sub is_for_host { + my ($type, $name, $host) = @_; + my ($service, $principal) = split ('/', $name, 2); + return 0 unless $service && $principal; + return 1 if $host eq $principal; + return 0; +} +package main; +@lines = $report->objects_hostname ('host', 'admin'); +is (scalar (@lines), 1, 'Searching for host-based objects finds one'); +is ($lines[0][0], 'base', ' and the first has the right type'); +is ($lines[0][1], 'service/admin', ' and the right name'); + # Set up a file bucket so that we can create an object we can retrieve. system ('rm -rf test-files') == 0 or die "cannot remove test-files\n"; mkdir 'test-files' or die "cannot create test-files: $!\n"; diff --git a/perl/t/policy/stanford.t b/perl/t/policy/stanford.t index 555086c..9ed0fa6 100755 --- a/perl/t/policy/stanford.t +++ b/perl/t/policy/stanford.t @@ -16,7 +16,7 @@ use 5.008; use strict; use warnings; -use Test::More tests => 101; +use Test::More tests => 130; use lib 't/lib'; use Util; @@ -24,7 +24,8 @@ use Util; # Load the naming policy module. BEGIN { use_ok('Wallet::Admin'); - use_ok('Wallet::Policy::Stanford', qw(default_owner verify_name)); + use_ok('Wallet::Policy::Stanford', + qw(default_owner verify_name is_for_host)); use_ok('Wallet::Server'); } @@ -101,6 +102,29 @@ for my $name (@INVALID_FILES) { isnt(verify_name('file', $name), undef, "Invalid file $name"); } +# Now test a few cases for checking to see if a file is host-based. We don't +# test the legacy examples because they're more complicated and less obvious. +for my $name (@VALID_KEYTABS) { + my $hostname = 'example.stanford.edu'; + if ($name =~ m{\b$hostname\b}) { + is(is_for_host('keytab', $name, $hostname), 1, + "Keytab $name belongs to $hostname"); + } else { + is(is_for_host('keytab', $name, $hostname), 0, + "Keytab $name doesn't belong to $hostname"); + } +} +for my $name (@VALID_FILES) { + my $hostname = 'example.stanford.edu'; + if ($name =~ m{\b$hostname\b}) { + is(is_for_host('file', $name, $hostname), 1, + "File $name belongs to $hostname"); + } else { + is(is_for_host('file', $name, $hostname), 0, + "File $name doesn't belong to $hostname"); + } +} + # Now we need an actual database. Use Wallet::Admin to set it up. db_setup; my $setup = eval { Wallet::Admin->new }; diff --git a/server/wallet-report b/server/wallet-report index 6d1436c..77a2f8a 100755 --- a/server/wallet-report +++ b/server/wallet-report @@ -24,6 +24,7 @@ Wallet reporting help: objects acl Objects granting permissions to that ACL objects flag Objects with that flag set objects history History of all objects + objects host All host-based objects for a specific host objects owner Objects owned by that owner objects type Objects of that type objects unused Objects that have never been gotten @@ -81,6 +82,8 @@ sub command { my @objects; if (@args && $args[0] eq 'history') { @objects = $report->objects_history (@args); + } elsif (@args && $args[0] eq 'host') { + @objects = $report->objects_hostname (@args); } else { @objects = $report->objects (@args); } -- cgit v1.2.3 From 0f943b75d34623b6825a0acf34ee2cd965bc6799 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Fri, 17 Apr 2015 13:30:45 -0700 Subject: ACL.pm: Fixed capitalization of ACL in pod Change-Id: I9e4632f3ff81f916f9157ef8128b20915ecded08 --- perl/lib/Wallet/ACL.pm | 2 +- perl/lib/Wallet/Object/Duo/RDP.pm | 204 -------------------------------------- 2 files changed, 1 insertion(+), 205 deletions(-) delete mode 100644 perl/lib/Wallet/Object/Duo/RDP.pm diff --git a/perl/lib/Wallet/ACL.pm b/perl/lib/Wallet/ACL.pm index 370df8b..a090256 100644 --- a/perl/lib/Wallet/ACL.pm +++ b/perl/lib/Wallet/ACL.pm @@ -673,7 +673,7 @@ Note that rename() operations are not logged in the ACL history. =item replace(ID) Replace this ACL with another. This goes through each object owned by -the ACL and changes its ownership to the new ACL, leaving this acl owning +the ACL and changes its ownership to the new ACL, leaving this ACL owning nothing (and probably then needing to be deleted). Returns true on success and false on failure. On failure, the caller should call error() to get the error message. diff --git a/perl/lib/Wallet/Object/Duo/RDP.pm b/perl/lib/Wallet/Object/Duo/RDP.pm deleted file mode 100644 index c74661c..0000000 --- a/perl/lib/Wallet/Object/Duo/RDP.pm +++ /dev/null @@ -1,204 +0,0 @@ -# Wallet::Object::Duo::RDP -- Duo RDP int. object implementation for wallet -# -# Written by Russ Allbery -# Jon Robertson -# Copyright 2014 -# The Board of Trustees of the Leland Stanford Junior University -# -# See LICENSE for licensing terms. - -############################################################################## -# Modules and declarations -############################################################################## - -package Wallet::Object::Duo::RDP; -require 5.006; - -use strict; -use warnings; -use vars qw(@ISA $VERSION); - -use JSON; -use Net::Duo::Admin; -use Net::Duo::Admin::Integration; -use Perl6::Slurp qw(slurp); -use Wallet::Config (); -use Wallet::Object::Duo; - -@ISA = qw(Wallet::Object::Duo); - -# 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'; - -############################################################################## -# Core methods -############################################################################## - -# Override create to provide the specific Duo integration type that will be -# used in the remote Duo record. -sub create { - my ($class, $type, $name, $schema, $creator, $host, $time) = @_; - - $time ||= time; - my $self = $class->SUPER::create ($type, $name, $schema, $creator, $host, - $time, 'rdp'); - return $self; -} - -# Override get to output the data in a specific format used by Duo's RDP -# module. -sub get { - my ($self, $user, $host, $time) = @_; - $time ||= time; - - # Check that the object isn't locked. - my $id = $self->{type} . ':' . $self->{name}; - if ($self->flag_check ('locked')) { - $self->error ("cannot get $id: object is locked"); - return; - } - - # Retrieve the integration from Duo. - my $key; - eval { - my %search = (du_name => $self->{name}); - my $row = $self->{schema}->resultset ('Duo')->find (\%search); - $key = $row->get_column ('du_key'); - }; - if ($@) { - $self->error ($@); - return; - } - my $integration = Net::Duo::Admin::Integration->new ($self->{duo}, $key); - - # We also need the admin server name, which we can get from the Duo object - # configuration with a bit of JSON decoding. - my $json = JSON->new->utf8 (1)->relaxed (1); - my $config = $json->decode (scalar slurp $Wallet::Config::DUO_KEY_FILE); - - # Construct the returned file. - my $output; - $output .= "Integration key: $key\n"; - $output .= 'Secret key: ' . $integration->secret_key . "\n"; - $output .= "Host: $config->{api_hostname}\n"; - - # Log the action and return. - $self->log_action ('get', $user, $host, $time); - return $output; -} - -1; -__END__ - -############################################################################## -# Documentation -############################################################################## - -=for stopwords -Allbery Duo integration DBH keytab RDP - -=head1 NAME - -Wallet::Object::Duo::RDP - Duo RDP int. object implementation for wallet - -=head1 SYNOPSIS - - my @name = qw(duo-rdp host.example.com); - my @trace = ($user, $host, time); - my $object = Wallet::Object::Duo::RDP->create (@name, $schema, @trace); - my $config = $object->get (@trace); - $object->destroy (@trace); - -=head1 DESCRIPTION - -Wallet::Object::Duo::RDP is a representation of Duo integrations with -the wallet, specifically to output Duo integrations to set up an RDP -integration. This can be used to set up remote logins, or all Windows -logins period if so selected in Duo's software. It implements the -wallet object API and provides the necessary glue to create a Duo -integration, return a configuration file containing the key and API -information for that integration, and delete the integration from Duo -when the wallet object is destroyed. - -Because the Duo RDP software is configured by a GUI, the information -returned for a get operation is a simple set that's readable but not -useful for directly plugging into a config file. The values would need -to be cut and pasted into the GUI. - -This object can be retrieved repeatedly without changing the secret key, -matching Duo's native behavior with integrations. To change the keys of -the integration, delete it and recreate it. - -To use this object, at least one configuration parameter must be set. See -L for details on supported configuration parameters and -information about how to set wallet configuration. - -=head1 METHODS - -This object mostly inherits from Wallet::Object::Duo. See the -documentation for that class for all generic methods. Below are only -those methods that are overridden or behave specially for this -implementation. - -=over 4 - -=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME]) - -This will override the Wallet::Object::Duo class with the information -needed to create a specific integration type in Duo. It creates a new -object with the given TYPE and NAME (TYPE is normally C and must -be for the rest of the wallet system to use the right class, but this -module doesn't check for ease of subclassing), using DBH as the handle -to the wallet metadata database. PRINCIPAL, HOSTNAME, and DATETIME are -stored as history information. PRINCIPAL should be the user who is -creating the object. If DATETIME isn't given, the current time is -used. - -When a new Duo integration object is created, a new integration will be -created in the configured Duo account and the integration key will be -stored in the wallet object. If the integration already exists, create() -will fail. - -If create() fails, it throws an exception. - -=item get(PRINCIPAL, HOSTNAME [, DATETIME]) - -Retrieves the configuration information for the Duo integration and -returns that information in the format expected by the configuration file -for the Duo UNIX integration. Returns undef on failure. The caller -should call error() to get the error message if get() returns undef. - -The returned configuration look look like: - - Integration key: - Secret key: - Host: - -The C parameter will be taken from the configuration file pointed -to by the DUO_KEY_FILE configuration variable. - -PRINCIPAL, HOSTNAME, and DATETIME are stored as history information. -PRINCIPAL should be the user who is downloading the keytab. If DATETIME -isn't given, the current time is used. - -=back - -=head1 LIMITATIONS - -Only one Duo account is supported for a given wallet implementation. - -=head1 SEE ALSO - -Net::Duo(3), Wallet::Config(3), Wallet::Object::Duo(3), wallet-backend(8) - -This module is part of the wallet system. The current version is -available from L. - -=head1 AUTHORS - -Russ Allbery -Jon Robertson - -=cut -- cgit v1.2.3 From feacbd7d685b1790579f949b3e72a48412835d92 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Fri, 17 Apr 2015 13:41:52 -0700 Subject: Merged all Duo objects into one module To handle local proliferation of Duo integration type requests, all Duo types have been merged into one module that will pick up and decide integration specifics off of the object type. If you are using the Duo types locally already, you'll want to load perl/sql/wallet-1.3-update-duo.sql to your database to update the old object types to all use the Duo module. All existing Duo integrations have been added to the module for handling, but nothing new has been added to the wallet object types. Since there are a lot of Duo integrations, sites should only manually add the ones they're interested in to the wallet types table. Change-Id: If9c9a0a3e77923354f31d8f9c98a519c93df200b --- perl/lib/Wallet/Admin.pm | 8 +- perl/lib/Wallet/Object/Duo.pm | 121 ++++++++++++++++-- perl/lib/Wallet/Object/Duo/LDAPProxy.pm | 202 ----------------------------- perl/lib/Wallet/Object/Duo/PAM.pm | 205 ------------------------------ perl/lib/Wallet/Object/Duo/RadiusProxy.pm | 204 ----------------------------- perl/sql/wallet-1.3-update-duo.sql | 9 ++ perl/t/object/duo-ldap.t | 21 ++- perl/t/object/duo-pam.t | 20 ++- perl/t/object/duo-radius.t | 21 ++- perl/t/object/duo-rdp.t | 20 ++- 10 files changed, 162 insertions(+), 669 deletions(-) delete mode 100644 perl/lib/Wallet/Object/Duo/LDAPProxy.pm delete mode 100644 perl/lib/Wallet/Object/Duo/PAM.pm delete mode 100644 perl/lib/Wallet/Object/Duo/RadiusProxy.pm create mode 100644 perl/sql/wallet-1.3-update-duo.sql diff --git a/perl/lib/Wallet/Admin.pm b/perl/lib/Wallet/Admin.pm index a8b8368..b38cc94 100644 --- a/perl/lib/Wallet/Admin.pm +++ b/perl/lib/Wallet/Admin.pm @@ -126,10 +126,10 @@ sub default_data { # types default rows. my @record = ([ qw/ty_name ty_class/ ], [ 'duo', 'Wallet::Object::Duo' ], - [ 'duo-ldap', 'Wallet::Object::Duo::LDAPProxy' ], - [ 'duo-pam', 'Wallet::Object::Duo::PAM' ], - [ 'duo-radius', 'Wallet::Object::Duo::RadiusProxy' ], - [ 'duo-rdp', 'Wallet::Object::Duo::RDP' ], + [ 'duo-ldap', 'Wallet::Object::Duo' ], + [ 'duo-pam', 'Wallet::Object::Duo' ], + [ 'duo-radius', 'Wallet::Object::Duo' ], + [ 'duo-rdp', 'Wallet::Object::Duo' ], [ 'file', 'Wallet::Object::File' ], [ 'password', 'Wallet::Object::Password' ], [ 'keytab', 'Wallet::Object::Keytab' ], diff --git a/perl/lib/Wallet/Object/Duo.pm b/perl/lib/Wallet/Object/Duo.pm index d08294b..d0901de 100644 --- a/perl/lib/Wallet/Object/Duo.pm +++ b/perl/lib/Wallet/Object/Duo.pm @@ -29,7 +29,100 @@ use Wallet::Object::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.02'; +$VERSION = '0.03'; + +# Mappings from our types into what Duo calls the integration types. +our %DUO_TYPES = ( + 'duo' => { + integration => 'unix', + output => \&_output_generic, + }, + 'duo-ldap' => { + integration => 'ldapproxy', + output => \&_output_ldap, + }, + 'duo-pam' => { + integration => 'unix', + output => \&_output_pam, + }, + 'duo-radius' => { + integration => 'radius', + output => \&_output_radius, + }, + ); + +# Extra types to add. These are all just named as the Duo integration name +# with duo- before it and go to the generic output. Put them here to prevent +# pages of settings. These are also not all actually set as types in the +# types table to prevent overpopulation. You should manually create the +# entries in that table for any Duo integrations you want to add. +our @EXTRA_TYPES = ('accountsapi', 'adfs', 'adminapi', 'array', 'barracuda', + 'cisco', 'citrixcag', 'citrixns', 'confluence', 'drupal', + 'f5bigip', 'f5firepass', 'fortinet', 'jira', 'juniper', + 'juniperuac', 'lastpass', 'okta', 'onelogin', 'openvpn', + 'openvpnas', 'owa', 'paloalto', 'rdgateway', 'rdp', + 'rdweb', 'rest', 'rras', 'shibboleth', 'sonicwallsra', + 'splunk', 'tmg', 'uag', 'verify', 'vmwareview', 'websdk', + 'wordpress'); +for my $type (@EXTRA_TYPES) { + my $wallet_type = 'duo-'.$type; + $DUO_TYPES{$wallet_type}{integration} = $type; + $DUO_TYPES{$wallet_type}{output} = \&_output_generic; +}; + +############################################################################## +# Get output methods +############################################################################## + +# Output for any miscellaneous Duo integration, usually those that use a GUI +# to set information and so don't need a custom configuration file. +sub _output_generic { + my ($key, $secret, $hostname) = @_; + + my $output; + $output .= "Integration key: $key\n"; + $output .= "Secret key: $secret\n"; + $output .= "Host: $hostname\n"; + + return $output; +} + +# Output for the Duo unix integration, which hooks into the PAM stack. +sub _output_pam { + my ($key, $secret, $hostname) = @_; + + my $output = "[duo]\n"; + $output .= "ikey = $key\n"; + $output .= "skey = $secret\n"; + $output .= "host = $hostname\n"; + + return $output; +} + +# Output for the radius proxy, which can be plugged into the proxy config. +sub _output_radius { + my ($key, $secret, $hostname) = @_; + + my $output = "[radius_server_challenge]\n"; + $output .= "ikey = $key\n"; + $output .= "skey = $secret\n"; + $output .= "api_host = $hostname\n"; + $output .= "client = radius_client\n"; + + return $output; +} + +# Output for the LDAP proxy, which can be plugged into the proxy config. +sub _output_ldap { + my ($key, $secret, $hostname) = @_; + + my $output = "[ldap_server_challenge]\n"; + $output .= "ikey = $key\n"; + $output .= "skey = $secret\n"; + $output .= "api_host = $hostname\n"; + + return $output; +} ############################################################################## # Core methods @@ -86,7 +179,7 @@ sub new { # great here since we don't have a way to communicate the error back to the # caller. sub create { - my ($class, $type, $name, $schema, $creator, $host, $time, $duo_type) = @_; + my ($class, $type, $name, $schema, $creator, $host, $time) = @_; # We have to have a Duo integration key file set. if (not $Wallet::Config::DUO_KEY_FILE) { @@ -95,6 +188,12 @@ sub create { my $key_file = $Wallet::Config::DUO_KEY_FILE; my $agent = $Wallet::Config::DUO_AGENT; + # Make sure this is actually a type we know about, since this handler + # can handle many types. + if (!exists $DUO_TYPES{$type}) { + die "$type is not a valid duo integration\n"; + } + # Construct the Net::Duo::Admin object. require Net::Duo::Admin; my $duo = Net::Duo::Admin->new ( @@ -106,7 +205,7 @@ sub create { # Create the object in Duo. require Net::Duo::Admin::Integration; - $duo_type ||= $Wallet::Config::DUO_TYPE; + my $duo_type = $DUO_TYPES{$type}{integration}; my %data = ( name => "$name ($duo_type)", notes => 'Managed by wallet', @@ -201,11 +300,17 @@ sub get { my $json = JSON->new->utf8 (1)->relaxed (1); my $config = $json->decode (scalar slurp $Wallet::Config::DUO_KEY_FILE); - # Construct the returned file. - my $output; - $output .= "Integration key: $key\n"; - $output .= 'Secret key: ' . $integration->secret_key . "\n"; - $output .= "Host: $config->{api_hostname}\n"; + # Construct the returned file. Assume the generic handler in case there + # is no valid handler, though that shouldn't happen. + my $output_sub; + my $type = $self->{type}; + if (exists $DUO_TYPES{$type}{output}) { + $output_sub = $DUO_TYPES{$type}{output}; + } else { + $output_sub = \&_output_generic; + } + my $output = $output_sub->($key, $integration->secret_key, + $config->{api_hostname}); # Log the action and return. $self->log_action ('get', $user, $host, $time); diff --git a/perl/lib/Wallet/Object/Duo/LDAPProxy.pm b/perl/lib/Wallet/Object/Duo/LDAPProxy.pm deleted file mode 100644 index 23894ac..0000000 --- a/perl/lib/Wallet/Object/Duo/LDAPProxy.pm +++ /dev/null @@ -1,202 +0,0 @@ -# Wallet::Object::Duo::LDAPProxy -- Duo auth proxy integration for LDAP -# -# Written by Jon Robertson -# Copyright 2014 -# The Board of Trustees of the Leland Stanford Junior University -# -# See LICENSE for licensing terms. - -############################################################################## -# Modules and declarations -############################################################################## - -package Wallet::Object::Duo::LDAPProxy; -require 5.006; - -use strict; -use warnings; -use vars qw(@ISA $VERSION); - -use JSON; -use Net::Duo::Admin; -use Net::Duo::Admin::Integration; -use Perl6::Slurp qw(slurp); -use Wallet::Config (); -use Wallet::Object::Duo; - -@ISA = qw(Wallet::Object::Duo); - -# 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'; - -############################################################################## -# Core methods -############################################################################## - -# Override create to provide the specific Duo integration type that will be -# used in the remote Duo record. -sub create { - my ($class, $type, $name, $schema, $creator, $host, $time) = @_; - - $time ||= time; - my $self = $class->SUPER::create ($type, $name, $schema, $creator, $host, - $time, 'ldapproxy'); - return $self; -} - -# Override get to output the data in a specific format used for Duo LDAP -# integration -sub get { - my ($self, $user, $host, $time) = @_; - $time ||= time; - - # Check that the object isn't locked. - my $id = $self->{type} . ':' . $self->{name}; - if ($self->flag_check ('locked')) { - $self->error ("cannot get $id: object is locked"); - return; - } - - # Retrieve the integration from Duo. - my $key; - eval { - my %search = (du_name => $self->{name}); - my $row = $self->{schema}->resultset ('Duo')->find (\%search); - $key = $row->get_column ('du_key'); - }; - if ($@) { - $self->error ($@); - return; - } - my $integration = Net::Duo::Admin::Integration->new ($self->{duo}, $key); - - # We also need the admin server name, which we can get from the Duo object - # configuration with a bit of JSON decoding. - my $json = JSON->new->utf8 (1)->relaxed (1); - my $config = $json->decode (scalar slurp $Wallet::Config::DUO_KEY_FILE); - - # Construct the returned file. - my $output = "[ldap_server_challenge]\n"; - $output .= "ikey = $key\n"; - $output .= 'skey = ' . $integration->secret_key . "\n"; - $output .= "api_host = $config->{api_hostname}\n"; - - # Log the action and return. - $self->log_action ('get', $user, $host, $time); - return $output; -} - -1; -__END__ - -############################################################################## -# Documentation -############################################################################## - -=for stopwords -Allbery Duo integration DBH keytab LDAP auth - -=head1 NAME - -Wallet::Object::Duo::LDAPProxy - Duo auth proxy integration for LDAP - -=head1 SYNOPSIS - - my @name = qw(duo-ldap host.example.com); - my @trace = ($user, $host, time); - my $object = Wallet::Object::Duo::LDAPProxy->create (@name, $schema, @trace); - my $config = $object->get (@trace); - $object->destroy (@trace); - -=head1 DESCRIPTION - -Wallet::Object::Duo::LDAPProxy is a representation of Duo -integrations with the wallet, specifically to output Duo integrations -in a format that can easily be pulled into configuring the Duo -Authentication Proxy for LDAP. It implements the wallet object API -and provides the necessary glue to create a Duo integration, return a -configuration file containing the key and API information for that -integration, and delete the integration from Duo when the wallet object -is destroyed. - -The integration information is always returned in the configuration file -format expected by the Authentication Proxy for Duo in configuring it -for LDAP. - -This object can be retrieved repeatedly without changing the secret key, -matching Duo's native behavior with integrations. To change the keys of -the integration, delete it and recreate it. - -To use this object, at least one configuration parameter must be set. See -L for details on supported configuration parameters and -information about how to set wallet configuration. - -=head1 METHODS - -This object mostly inherits from Wallet::Object::Duo. See the -documentation for that class for all generic methods. Below are only -those methods that are overridden or behave specially for this -implementation. - -=over 4 - -=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME]) - -This will override the Wallet::Object::Duo class with the information -needed to create a specific integration type in Duo. It creates a new -object with the given TYPE and NAME (TYPE is normally C and -must be for the rest of the wallet system to use the right class, but -this module doesn't check for ease of subclassing), using DBH as the -handle to the wallet metadata database. PRINCIPAL, HOSTNAME, and -DATETIME are stored as history information. PRINCIPAL should be the -user who is creating the object. If DATETIME isn't given, the current -time is used. - -When a new Duo integration object is created, a new integration will be -created in the configured Duo account and the integration key will be -stored in the wallet object. If the integration already exists, create() -will fail. - -If create() fails, it throws an exception. - -=item get(PRINCIPAL, HOSTNAME [, DATETIME]) - -Retrieves the configuration information for the Duo integration and -returns that information in the format expected by the configuration file -for the Duo UNIX integration. Returns undef on failure. The caller -should call error() to get the error message if get() returns undef. - -The returned configuration look look like: - - [ldap_server_challenge] - ikey = - skey = - api_host = - -The C parameter will be taken from the configuration file pointed -to by the DUO_KEY_FILE configuration variable. - -PRINCIPAL, HOSTNAME, and DATETIME are stored as history information. -PRINCIPAL should be the user who is downloading the keytab. If DATETIME -isn't given, the current time is used. - -=back - -=head1 LIMITATIONS - -Only one Duo account is supported for a given wallet implementation. - -=head1 SEE ALSO - -Net::Duo(3), Wallet::Config(3), Wallet::Object::Duo(3), wallet-backend(8) - -This module is part of the wallet system. The current version is -available from L. - -=head1 AUTHORS - -Jon Robertson - -=cut diff --git a/perl/lib/Wallet/Object/Duo/PAM.pm b/perl/lib/Wallet/Object/Duo/PAM.pm deleted file mode 100644 index d9d17f8..0000000 --- a/perl/lib/Wallet/Object/Duo/PAM.pm +++ /dev/null @@ -1,205 +0,0 @@ -# Wallet::Object::Duo::PAM -- Duo PAM int. object implementation for wallet -# -# Written by Russ Allbery -# Jon Robertson -# Copyright 2014 -# The Board of Trustees of the Leland Stanford Junior University -# -# See LICENSE for licensing terms. - -############################################################################## -# Modules and declarations -############################################################################## - -package Wallet::Object::Duo::PAM; -require 5.006; - -use strict; -use warnings; -use vars qw(@ISA $VERSION); - -use JSON; -use Net::Duo::Admin; -use Net::Duo::Admin::Integration; -use Perl6::Slurp qw(slurp); -use Wallet::Config (); -use Wallet::Object::Duo; - -@ISA = qw(Wallet::Object::Duo); - -# 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'; - -############################################################################## -# Core methods -############################################################################## - -# Override create to provide the specific Duo integration type that will be -# used in the remote Duo record. -sub create { - my ($class, $type, $name, $schema, $creator, $host, $time) = @_; - - $time ||= time; - my $self = $class->SUPER::create ($type, $name, $schema, $creator, $host, - $time, 'unix'); - return $self; -} - -# Override get to output the data in a specific format used by Duo's PAM -# module. -sub get { - my ($self, $user, $host, $time) = @_; - $time ||= time; - - # Check that the object isn't locked. - my $id = $self->{type} . ':' . $self->{name}; - if ($self->flag_check ('locked')) { - $self->error ("cannot get $id: object is locked"); - return; - } - - # Retrieve the integration from Duo. - my $key; - eval { - my %search = (du_name => $self->{name}); - my $row = $self->{schema}->resultset ('Duo')->find (\%search); - $key = $row->get_column ('du_key'); - }; - if ($@) { - $self->error ($@); - return; - } - my $integration = Net::Duo::Admin::Integration->new ($self->{duo}, $key); - - # We also need the admin server name, which we can get from the Duo object - # configuration with a bit of JSON decoding. - my $json = JSON->new->utf8 (1)->relaxed (1); - my $config = $json->decode (scalar slurp $Wallet::Config::DUO_KEY_FILE); - - # Construct the returned file. - my $output = "[duo]\n"; - $output .= "ikey = $key\n"; - $output .= 'skey = ' . $integration->secret_key . "\n"; - $output .= "host = $config->{api_hostname}\n"; - - # Log the action and return. - $self->log_action ('get', $user, $host, $time); - return $output; -} - -1; -__END__ - -############################################################################## -# Documentation -############################################################################## - -=for stopwords -Allbery Duo integration DBH keytab - -=head1 NAME - -Wallet::Object::Duo::PAM - Duo PAM int. object implementation for wallet - -=head1 SYNOPSIS - - my @name = qw(duo-pam host.example.com); - my @trace = ($user, $host, time); - my $object = Wallet::Object::Duo::PAM->create (@name, $schema, @trace); - my $config = $object->get (@trace); - $object->destroy (@trace); - -=head1 DESCRIPTION - -Wallet::Object::Duo::PAM is a representation of Duo integrations with -the wallet, specifically to output Duo integrations in a format that -can easily be pulled into configuring the Duo PAM interface. It -implements the wallet object API and provides the necessary glue to -create a Duo integration, return a configuration file containing the key -and API information for that integration, and delete the integration from -Duo when the wallet object is destroyed. - -The integration information is always returned in the configuration file -format expected by the Duo UNIX integration. The results of retrieving -this object will be text, suitable for putting in the UNIX integration -configuration file, containing the integration key, secret key, and admin -hostname for that integration. - -This object can be retrieved repeatedly without changing the secret key, -matching Duo's native behavior with integrations. To change the keys of -the integration, delete it and recreate it. - -To use this object, at least one configuration parameter must be set. See -L for details on supported configuration parameters and -information about how to set wallet configuration. - -=head1 METHODS - -This object mostly inherits from Wallet::Object::Duo. See the -documentation for that class for all generic methods. Below are only -those methods that are overridden or behave specially for this -implementation. - -=over 4 - -=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME]) - -This will override the Wallet::Object::Duo class with the information -needed to create a specific integration type in Duo. It creates a new -object with the given TYPE and NAME (TYPE is normally C and must -be for the rest of the wallet system to use the right class, but this -module doesn't check for ease of subclassing), using DBH as the handle -to the wallet metadata database. PRINCIPAL, HOSTNAME, and DATETIME are -stored as history information. PRINCIPAL should be the user who is -creating the object. If DATETIME isn't given, the current time is -used. - -When a new Duo integration object is created, a new integration will be -created in the configured Duo account and the integration key will be -stored in the wallet object. If the integration already exists, create() -will fail. - -If create() fails, it throws an exception. - -=item get(PRINCIPAL, HOSTNAME [, DATETIME]) - -Retrieves the configuration information for the Duo integration and -returns that information in the format expected by the configuration file -for the Duo UNIX integration. Returns undef on failure. The caller -should call error() to get the error message if get() returns undef. - -The returned configuration look look like: - - [duo] - ikey = - skey = - host = - -The C parameter will be taken from the configuration file pointed -to by the DUO_KEY_FILE configuration variable. - -PRINCIPAL, HOSTNAME, and DATETIME are stored as history information. -PRINCIPAL should be the user who is downloading the keytab. If DATETIME -isn't given, the current time is used. - -=back - -=head1 LIMITATIONS - -Only one Duo account is supported for a given wallet implementation. - -=head1 SEE ALSO - -Net::Duo(3), Wallet::Config(3), Wallet::Object::Duo(3), wallet-backend(8) - -This module is part of the wallet system. The current version is -available from L. - -=head1 AUTHORS - -Russ Allbery -Jon Robertson - -=cut diff --git a/perl/lib/Wallet/Object/Duo/RadiusProxy.pm b/perl/lib/Wallet/Object/Duo/RadiusProxy.pm deleted file mode 100644 index a1f6e24..0000000 --- a/perl/lib/Wallet/Object/Duo/RadiusProxy.pm +++ /dev/null @@ -1,204 +0,0 @@ -# Wallet::Object::Duo::RadiusProxy -- Duo auth proxy integration for radius -# -# Written by Jon Robertson -# Copyright 2014 -# The Board of Trustees of the Leland Stanford Junior University -# -# See LICENSE for licensing terms. - -############################################################################## -# Modules and declarations -############################################################################## - -package Wallet::Object::Duo::RadiusProxy; -require 5.006; - -use strict; -use warnings; -use vars qw(@ISA $VERSION); - -use JSON; -use Net::Duo::Admin; -use Net::Duo::Admin::Integration; -use Perl6::Slurp qw(slurp); -use Wallet::Config (); -use Wallet::Object::Duo; - -@ISA = qw(Wallet::Object::Duo); - -# 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'; - -############################################################################## -# Core methods -############################################################################## - -# Override create to provide the specific Duo integration type that will be -# used in the remote Duo record. -sub create { - my ($class, $type, $name, $schema, $creator, $host, $time) = @_; - - $time ||= time; - my $self = $class->SUPER::create ($type, $name, $schema, $creator, $host, - $time, 'radius'); - return $self; -} - -# Override get to output the data in a specific format used for Duo radius -# integration -sub get { - my ($self, $user, $host, $time) = @_; - $time ||= time; - - # Check that the object isn't locked. - my $id = $self->{type} . ':' . $self->{name}; - if ($self->flag_check ('locked')) { - $self->error ("cannot get $id: object is locked"); - return; - } - - # Retrieve the integration from Duo. - my $key; - eval { - my %search = (du_name => $self->{name}); - my $row = $self->{schema}->resultset ('Duo')->find (\%search); - $key = $row->get_column ('du_key'); - }; - if ($@) { - $self->error ($@); - return; - } - my $integration = Net::Duo::Admin::Integration->new ($self->{duo}, $key); - - # We also need the admin server name, which we can get from the Duo object - # configuration with a bit of JSON decoding. - my $json = JSON->new->utf8 (1)->relaxed (1); - my $config = $json->decode (scalar slurp $Wallet::Config::DUO_KEY_FILE); - - # Construct the returned file. - my $output = "[radius_server_challenge]\n"; - $output .= "ikey = $key\n"; - $output .= 'skey = ' . $integration->secret_key . "\n"; - $output .= "api_host = $config->{api_hostname}\n"; - $output .= "client = radius_client\n"; - - # Log the action and return. - $self->log_action ('get', $user, $host, $time); - return $output; -} - -1; -__END__ - -############################################################################## -# Documentation -############################################################################## - -=for stopwords -Allbery Duo integration DBH keytab auth - -=head1 NAME - -Wallet::Object::Duo::RadiusProxy - Duo auth proxy integration for RADIUS - -=head1 SYNOPSIS - - my @name = qw(duo-radius host.example.com); - my @trace = ($user, $host, time); - my $object = Wallet::Object::Duo::RadiusProxy->create (@name, $schema, @trace); - my $config = $object->get (@trace); - $object->destroy (@trace); - -=head1 DESCRIPTION - -Wallet::Object::Duo::RadiusProxy is a representation of Duo -integrations with the wallet, specifically to output Duo integrations -in a format that can easily be pulled into configuring the Duo -Authentication Proxy for Radius. It implements the wallet object API -and provides the necessary glue to create a Duo integration, return a -configuration file containing the key and API information for that -integration, and delete the integration from Duo when the wallet object -is destroyed. - -The integration information is always returned in the configuration file -format expected by the Authentication Proxy for Duo in configuring it -for Radius. - -This object can be retrieved repeatedly without changing the secret key, -matching Duo's native behavior with integrations. To change the keys of -the integration, delete it and recreate it. - -To use this object, at least one configuration parameter must be set. See -L for details on supported configuration parameters and -information about how to set wallet configuration. - -=head1 METHODS - -This object mostly inherits from Wallet::Object::Duo. See the -documentation for that class for all generic methods. Below are only -those methods that are overridden or behave specially for this -implementation. - -=over 4 - -=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME]) - -This will override the Wallet::Object::Duo class with the information -needed to create a specific integration type in Duo. It creates a new -object with the given TYPE and NAME (TYPE is normally C and -must be for the rest of the wallet system to use the right class, but -this module doesn't check for ease of subclassing), using DBH as the -handle to the wallet metadata database. PRINCIPAL, HOSTNAME, and -DATETIME are stored as history information. PRINCIPAL should be the -user who is creating the object. If DATETIME isn't given, the current -time is used. - -When a new Duo integration object is created, a new integration will be -created in the configured Duo account and the integration key will be -stored in the wallet object. If the integration already exists, create() -will fail. - -If create() fails, it throws an exception. - -=item get(PRINCIPAL, HOSTNAME [, DATETIME]) - -Retrieves the configuration information for the Duo integration and -returns that information in the format expected by the configuration file -for the Duo UNIX integration. Returns undef on failure. The caller -should call error() to get the error message if get() returns undef. - -The returned configuration look look like: - - [radius_server_challenge] - ikey = - skey = - api_host = - client = radius_client - -The C parameter will be taken from the configuration file pointed -to by the DUO_KEY_FILE configuration variable. - -PRINCIPAL, HOSTNAME, and DATETIME are stored as history information. -PRINCIPAL should be the user who is downloading the keytab. If DATETIME -isn't given, the current time is used. - -=back - -=head1 LIMITATIONS - -Only one Duo account is supported for a given wallet implementation. - -=head1 SEE ALSO - -Net::Duo(3), Wallet::Config(3), Wallet::Object::Duo(3), wallet-backend(8) - -This module is part of the wallet system. The current version is -available from L. - -=head1 AUTHORS - -Jon Robertson - -=cut diff --git a/perl/sql/wallet-1.3-update-duo.sql b/perl/sql/wallet-1.3-update-duo.sql new file mode 100644 index 0000000..affadcd --- /dev/null +++ b/perl/sql/wallet-1.3-update-duo.sql @@ -0,0 +1,9 @@ +-- +-- Run on installing wallet 1.3 in order to update what the Duo types +-- point to for modules. +-- + +UPDATE types set ty_class='Wallet::Object::Duo' where ty_name='duo-ldap'; +UPDATE types set ty_class='Wallet::Object::Duo' where ty_name='duo-pam'; +UPDATE types set ty_class='Wallet::Object::Duo' where ty_name='duo-radius'; +UPDATE types set ty_class='Wallet::Object::Duo' where ty_name='duo-rdp'; diff --git a/perl/t/object/duo-ldap.t b/perl/t/object/duo-ldap.t index 3648eba..8a00dbb 100644 --- a/perl/t/object/duo-ldap.t +++ b/perl/t/object/duo-ldap.t @@ -26,7 +26,7 @@ BEGIN { BEGIN { use_ok('Wallet::Admin'); use_ok('Wallet::Config'); - use_ok('Wallet::Object::Duo::LDAPProxy'); + use_ok('Wallet::Object::Duo'); } use lib 't/lib'; @@ -53,15 +53,14 @@ my $mock = Net::Duo::Mock::Agent->new ({ key_file => 't/data/duo/keys.json' }); # Test error handling in the absence of configuration. my $object = eval { - Wallet::Object::Duo::LDAPProxy->new ('duo-ldap', 'test', $schema); + Wallet::Object::Duo->new ('duo-ldap', 'test', $schema); }; -is ($object, undef, 'Wallet::Object::Duo::LDAPProxy new with no config failed'); +is ($object, undef, 'Wallet::Object::Duo new with no config failed'); is ($@, "duo object implementation not configured\n", '...with correct error'); $object = eval { - Wallet::Object::Duo::LDAPProxy->create ('duo-ldap', 'test', $schema, - @trace); + Wallet::Object::Duo->create ('duo-ldap', 'test', $schema, @trace); }; -is ($object, undef, 'Wallet::Object::Duo::LDAPProxy creation with no config failed'); +is ($object, undef, 'Wallet::Object::Duo creation with no config failed'); is ($@, "duo object implementation not configured\n", '...with correct error'); # Set up the Duo configuration. @@ -83,9 +82,8 @@ $mock->expect ( response_file => 't/data/duo/integration.json', } ); -$object = Wallet::Object::Duo::LDAPProxy->create ('duo-ldap', 'test', $schema, - @trace); -isa_ok ($object, 'Wallet::Object::Duo::LDAPProxy'); +$object = Wallet::Object::Duo->create ('duo-ldap', 'test', $schema, @trace); +isa_ok ($object, 'Wallet::Object::Duo'); # Check the metadata about the new wallet object. $expected = <<"EOO"; @@ -127,7 +125,7 @@ is ($object->flag_clear ('locked', @trace), 1, '...and clearing locked flag works'); # Create a new object by wallet type and name. -$object = Wallet::Object::Duo::LDAPProxy->new ('duo-ldap', 'test', $schema); +$object = Wallet::Object::Duo->new ('duo-ldap', 'test', $schema); # Test deleting an integration. We can't test this entirely properly because # currently Net::Duo::Mock::Agent doesn't support stacking multiple expected @@ -144,8 +142,7 @@ TODO: { local $TODO = 'Net::Duo::Mock::Agent not yet capable'; is ($object->destroy (@trace), 1, 'Duo object deletion succeeded'); - $object = eval { Wallet::Object::Duo::LDAPProxy->new ('duo-ldap', 'test', - $schema) }; + $object = eval { Wallet::Object::Duo->new ('duo-ldap', 'test', $schema) }; is ($object, undef, '...and now object cannot be retrieved'); is ($@, "cannot find duo:test\n", '...with correct error'); } diff --git a/perl/t/object/duo-pam.t b/perl/t/object/duo-pam.t index 7b88787..047343e 100644 --- a/perl/t/object/duo-pam.t +++ b/perl/t/object/duo-pam.t @@ -26,7 +26,7 @@ BEGIN { BEGIN { use_ok('Wallet::Admin'); use_ok('Wallet::Config'); - use_ok('Wallet::Object::Duo::PAM'); + use_ok('Wallet::Object::Duo'); } use lib 't/lib'; @@ -53,14 +53,14 @@ my $mock = Net::Duo::Mock::Agent->new ({ key_file => 't/data/duo/keys.json' }); # Test error handling in the absence of configuration. my $object = eval { - Wallet::Object::Duo::PAM->new ('duo-pam', 'test', $schema); + Wallet::Object::Duo->new ('duo-pam', 'test', $schema); }; -is ($object, undef, 'Wallet::Object::Duo::PAM new with no config failed'); +is ($object, undef, 'Wallet::Object::Duo new with no config failed'); is ($@, "duo object implementation not configured\n", '...with correct error'); $object = eval { - Wallet::Object::Duo::PAM->create ('duo-pam', 'test', $schema, @trace); + Wallet::Object::Duo->create ('duo-pam', 'test', $schema, @trace); }; -is ($object, undef, 'Wallet::Object::Duo::PAM creation with no config failed'); +is ($object, undef, 'Wallet::Object::Duo creation with no config failed'); is ($@, "duo object implementation not configured\n", '...with correct error'); # Set up the Duo configuration. @@ -82,9 +82,8 @@ $mock->expect ( response_file => 't/data/duo/integration.json', } ); -$object = Wallet::Object::Duo::PAM->create ('duo-pam', 'test', $schema, - @trace); -isa_ok ($object, 'Wallet::Object::Duo::PAM'); +$object = Wallet::Object::Duo->create ('duo-pam', 'test', $schema, @trace); +isa_ok ($object, 'Wallet::Object::Duo'); # Check the metadata about the new wallet object. $expected = <<"EOO"; @@ -126,7 +125,7 @@ is ($object->flag_clear ('locked', @trace), 1, '...and clearing locked flag works'); # Create a new object by wallet type and name. -$object = Wallet::Object::Duo::PAM->new ('duo-pam', 'test', $schema); +$object = Wallet::Object::Duo->new ('duo-pam', 'test', $schema); # Test deleting an integration. We can't test this entirely properly because # currently Net::Duo::Mock::Agent doesn't support stacking multiple expected @@ -143,8 +142,7 @@ TODO: { local $TODO = 'Net::Duo::Mock::Agent not yet capable'; is ($object->destroy (@trace), 1, 'Duo object deletion succeeded'); - $object = eval { Wallet::Object::Duo::PAM->new ('duo-pam', 'test', - $schema) }; + $object = eval { Wallet::Object::Duo->new ('duo-pam', 'test', $schema) }; is ($object, undef, '...and now object cannot be retrieved'); is ($@, "cannot find duo:test\n", '...with correct error'); } diff --git a/perl/t/object/duo-radius.t b/perl/t/object/duo-radius.t index f258518..55cbb9d 100644 --- a/perl/t/object/duo-radius.t +++ b/perl/t/object/duo-radius.t @@ -26,7 +26,7 @@ BEGIN { BEGIN { use_ok('Wallet::Admin'); use_ok('Wallet::Config'); - use_ok('Wallet::Object::Duo::RadiusProxy'); + use_ok('Wallet::Object::Duo'); } use lib 't/lib'; @@ -53,17 +53,16 @@ my $mock = Net::Duo::Mock::Agent->new ({ key_file => 't/data/duo/keys.json' }); # Test error handling in the absence of configuration. my $object = eval { - Wallet::Object::Duo::RadiusProxy->new ('duo-raduys', 'test', $schema); + Wallet::Object::Duo->new ('duo-radius', 'test', $schema); }; is ($object, undef, - 'Wallet::Object::Duo::RadiusProxy new with no config failed'); + 'Wallet::Object::Duo new with no config failed'); is ($@, "duo object implementation not configured\n", '...with correct error'); $object = eval { - Wallet::Object::Duo::RadiusProxy->create ('duo-radius', 'test', $schema, - @trace); + Wallet::Object::Duo->create ('duo-radius', 'test', $schema, @trace); }; is ($object, undef, - 'Wallet::Object::Duo::RadiusProxy creation with no config failed'); + 'Wallet::Object::Duo creation with no config failed'); is ($@, "duo object implementation not configured\n", '...with correct error'); # Set up the Duo configuration. @@ -85,9 +84,8 @@ $mock->expect ( response_file => 't/data/duo/integration-radius.json', } ); -$object = Wallet::Object::Duo::RadiusProxy->create ('duo-radius', 'test', - $schema, @trace); -isa_ok ($object, 'Wallet::Object::Duo::RadiusProxy'); +$object = Wallet::Object::Duo->create ('duo-radius', 'test', $schema, @trace); +isa_ok ($object, 'Wallet::Object::Duo'); # Check the metadata about the new wallet object. $expected = <<"EOO"; @@ -130,8 +128,7 @@ is ($object->flag_clear ('locked', @trace), 1, '...and clearing locked flag works'); # Create a new object by wallet type and name. -$object = Wallet::Object::Duo::RadiusProxy->new ('duo-radius', 'test', - $schema); +$object = Wallet::Object::Duo->new ('duo-radius', 'test', $schema); # Test deleting an integration. We can't test this entirely properly because # currently Net::Duo::Mock::Agent doesn't support stacking multiple expected @@ -149,7 +146,7 @@ TODO: { is ($object->destroy (@trace), 1, 'Duo object deletion succeeded'); $object = eval { - Wallet::Object::Duo::RadiusProxy->new ('duo-radius', 'test', $schema); + Wallet::Object::Duo->new ('duo-radius', 'test', $schema); }; is ($object, undef, '...and now object cannot be retrieved'); is ($@, "cannot find duo:test\n", '...with correct error'); diff --git a/perl/t/object/duo-rdp.t b/perl/t/object/duo-rdp.t index 9b2d566..25060ac 100644 --- a/perl/t/object/duo-rdp.t +++ b/perl/t/object/duo-rdp.t @@ -26,7 +26,7 @@ BEGIN { BEGIN { use_ok('Wallet::Admin'); use_ok('Wallet::Config'); - use_ok('Wallet::Object::Duo::RDP'); + use_ok('Wallet::Object::Duo'); } use lib 't/lib'; @@ -53,14 +53,14 @@ my $mock = Net::Duo::Mock::Agent->new ({ key_file => 't/data/duo/keys.json' }); # Test error handling in the absence of configuration. my $object = eval { - Wallet::Object::Duo::RDP->new ('duo-rdp', 'test', $schema); + Wallet::Object::Duo->new ('duo-rdp', 'test', $schema); }; -is ($object, undef, 'Wallet::Object::Duo::RDP new with no config failed'); +is ($object, undef, 'Wallet::Object::Duo new with no config failed'); is ($@, "duo object implementation not configured\n", '...with correct error'); $object = eval { - Wallet::Object::Duo::RDP->create ('duo-rdp', 'test', $schema, @trace); + Wallet::Object::Duo->create ('duo-rdp', 'test', $schema, @trace); }; -is ($object, undef, 'Wallet::Object::Duo::RDP creation with no config failed'); +is ($object, undef, 'Wallet::Object::Duo creation with no config failed'); is ($@, "duo object implementation not configured\n", '...with correct error'); # Set up the Duo configuration. @@ -82,9 +82,8 @@ $mock->expect ( response_file => 't/data/duo/integration-rdp.json', } ); -$object = Wallet::Object::Duo::RDP->create ('duo-rdp', 'test', $schema, - @trace); -isa_ok ($object, 'Wallet::Object::Duo::RDP'); +$object = Wallet::Object::Duo->create ('duo-rdp', 'test', $schema, @trace); +isa_ok ($object, 'Wallet::Object::Duo'); # Check the metadata about the new wallet object. $expected = <<"EOO"; @@ -125,7 +124,7 @@ is ($object->flag_clear ('locked', @trace), 1, '...and clearing locked flag works'); # Create a new object by wallet type and name. -$object = Wallet::Object::Duo::RDP->new ('duo-rdp', 'test', $schema); +$object = Wallet::Object::Duo->new ('duo-rdp', 'test', $schema); # Test deleting an integration. We can't test this entirely properly because # currently Net::Duo::Mock::Agent doesn't support stacking multiple expected @@ -142,8 +141,7 @@ TODO: { local $TODO = 'Net::Duo::Mock::Agent not yet capable'; is ($object->destroy (@trace), 1, 'Duo object deletion succeeded'); - $object = eval { Wallet::Object::Duo::RDP->new ('duo-rdp', 'test', - $schema) }; + $object = eval { Wallet::Object::Duo->new ('duo-rdp', 'test', $schema) }; is ($object, undef, '...and now object cannot be retrieved'); is ($@, "cannot find duo:test\n", '...with correct error'); } -- cgit v1.2.3 From 2e3c457d3b0ae813c00183334e80b9bd92ab6c90 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Fri, 8 May 2015 14:36:46 -0700 Subject: wallet-contacts: Added basic email sending to the contacts The email sending will only replace the To: field with the contacts and do no other template parsing, so it is currently limited. Change-Id: I4c653cf7bfe3ed2d9ca16299a4f937e015966554 --- contrib/wallet-contacts | 72 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/contrib/wallet-contacts b/contrib/wallet-contacts index 2799db3..ce16ab1 100755 --- a/contrib/wallet-contacts +++ b/contrib/wallet-contacts @@ -3,7 +3,7 @@ # wallet-contacts -- Report contact addresses for matching wallet objects. # # Written by Russ Allbery -# Copyright 2009 +# Copyright 2009, 2015 # The Board of Trustees of the Leland Stanford Junior University # # See LICENSE for licensing terms. @@ -12,16 +12,32 @@ # Modules and declarations ############################################################################## -require 5.006; - +use 5.010; +use autodie; use strict; +use warnings; use Getopt::Long qw(GetOptions); -use Wallet::Admin (); +use Perl6::Slurp; +use Wallet::Report (); # Used to cache lookups of e-mail addresses by identifiers. our %EMAIL; +############################################################################## +# Mail sending +############################################################################## + +# Given a message, mail it through sendmail. +sub mail { + my ($message) = @_; + + open (MAIL, '| /usr/sbin/sendmail -t -oi -oem') + or die "$0: cannot fork sendmail: $!\n"; + print MAIL $message; + close (MAIL); +} + ############################################################################## # whois lookups ############################################################################## @@ -79,9 +95,12 @@ sub whois_lookup { ############################################################################## # Read in command-line options. -my ($help); +my ($help, $mail, $dryrun); Getopt::Long::config ('no_ignore_case', 'bundling'); -GetOptions ('help|h' => \$help) or exit 1; +GetOptions ('help|h' => \$help, + 'mail=s' => \$mail, + 'dryrun' => \$dryrun, + ) or exit 1; if ($help) { print "Feeding myself to perldoc, please wait....\n"; exec ('perldoc', '-t', $0); @@ -95,10 +114,10 @@ if (@ARGV > 2 or not defined $name) { $0 =~ s%.*/%%; # Gather the list of ACL lines. -my $admin = Wallet::Admin->new; -my @lines = $admin->report_owners ($type, $name); -if (!@lines and $admin->error) { - die $admin->error, "\n"; +my $report = Wallet::Report->new; +my @lines = $report->owners ($type, $name); +if (!@lines and $report->error) { + die $report->error, "\n"; } # Now, for each line, turn it into an e-mail address. krb5 ACLs go as-is if @@ -127,10 +146,28 @@ for (@lines) { } # We now have a list of e-mail addresses. De-duplicate and then print them -# out. +# out or mail to them. my %seen; @email = grep { !$seen{$_}++ } sort @email; -print join ("\n", @email, ''); +if ($mail) { + if (!-e $mail) { + die "mail file $mail does not exist!\n"; + } + + # Load the message and set the To header. + my $message = slurp($mail); + my $mailto = join (', ', @email); + $message =~ s{^To:.*$}{To: $mailto}m; + + if ($dryrun) { + print $message; + } else { + mail ($message); + } + +} else { + print join ("\n", @email, ''); +} ############################################################################## # Documentation @@ -181,6 +218,17 @@ e-mail address for an administrator or user, it will warn but continue. Print out this documentation (which is done simply by feeding the script to C). +=item B<-mail>= + +Takes a given email message file, replaces the contents of the To: line +with the contacts found, and sends out that mail. This can be used for +simple notifications that have no template requirements. + +=item B<-dryrun> + +If --mail has been set, only print to the screen rather than actually +sending mail. Does nothing if --mail is not set. + =back =head1 CAVEATS -- cgit v1.2.3 From 626d3ee2b94384a4ffe95d5e8a907f359ff7cbfb Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Thu, 4 Jun 2015 10:56:30 -0700 Subject: ACL.pm: Error messages use name rather than ID All error messages should now use the ACL name rather than the ADL id, for readability. Change-Id: I2d1cfe806b459ef083293df4fa0b83cb4cef673b --- NEWS | 2 ++ perl/lib/Wallet/ACL.pm | 18 +++++++++--------- perl/t/general/acl.t | 6 +++--- perl/t/general/server.t | 10 +++++----- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/NEWS b/NEWS index 664da05..9e124e9 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,8 @@ wallet 1.3 (xxxx-xx-xx) Added an acl replace command, to change all objects owned by one ACL to be owned by another. + All ACL operations now refer to the ACL by name rather than ID. + Added a report for unstored objects to wallet-report, and cleaned up the help for the existing unused report that implied it showed unstored as well as unused. diff --git a/perl/lib/Wallet/ACL.pm b/perl/lib/Wallet/ACL.pm index a090256..260ff22 100644 --- a/perl/lib/Wallet/ACL.pm +++ b/perl/lib/Wallet/ACL.pm @@ -1,7 +1,7 @@ # Wallet::ACL -- Implementation of ACLs in the wallet system. # # Written by Russ Allbery -# Copyright 2007, 2008, 2010, 2013, 2014 +# Copyright 2007, 2008, 2010, 2013, 2014, 2015 # The Board of Trustees of the Leland Stanford Junior University # # See LICENSE for licensing terms. @@ -24,7 +24,7 @@ use DBI; # 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.08'; +$VERSION = '0.09'; ############################################################################## # Constructors @@ -201,7 +201,7 @@ sub rename { $guard->commit; }; if ($@) { - $self->error ("cannot rename ACL $self->{id} to $name: $@"); + $self->error ("cannot rename ACL $self->{name} to $name: $@"); return; } $self->{name} = $name; @@ -228,7 +228,7 @@ sub replace { $object->owner ($replace_id, $user, $host, $time); } } else { - $self->error ("no objects found for ACL $self->{id}"); + $self->error ("no objects found for ACL $self->{name}"); return; } return 1; @@ -284,7 +284,7 @@ sub destroy { $guard->commit; }; if ($@) { - $self->error ("cannot destroy ACL $self->{id}: $@"); + $self->error ("cannot destroy ACL $self->{name}: $@"); return; } return 1; @@ -312,7 +312,7 @@ sub add { $guard->commit; }; if ($@) { - $self->error ("cannot add $scheme:$identifier to $self->{id}: $@"); + $self->error ("cannot add $scheme:$identifier to $self->{name}: $@"); return; } return 1; @@ -339,7 +339,7 @@ sub remove { }; if ($@) { my $entry = "$scheme:$identifier"; - $self->error ("cannot remove $entry from $self->{id}: $@"); + $self->error ("cannot remove $entry from $self->{name}: $@"); return; } return 1; @@ -367,7 +367,7 @@ sub list { $guard->commit; }; if ($@) { - $self->error ("cannot retrieve ACL $self->{id}: $@"); + $self->error ("cannot retrieve ACL $self->{name}: $@"); return; } else { return @entries; @@ -422,7 +422,7 @@ sub history { $guard->commit; }; if ($@) { - $self->error ("cannot read history for $self->{id}: $@"); + $self->error ("cannot read history for $self->{name}: $@"); return; } return $output; diff --git a/perl/t/general/acl.t b/perl/t/general/acl.t index ff8ddad..80e8b3c 100755 --- a/perl/t/general/acl.t +++ b/perl/t/general/acl.t @@ -85,7 +85,7 @@ is ($acl->name, 'example', ' and the right name'); is ($acl->id, 2, ' and the right ID'); ok (! $acl->rename ('ADMIN', @trace), ' but renaming to an existing name fails'); -like ($acl->error, qr/^cannot rename ACL 2 to ADMIN: /, +like ($acl->error, qr/^cannot rename ACL example to ADMIN: /, ' with the right error'); # Test add, check, remove, list, and show. @@ -131,7 +131,7 @@ EOE is ($acl->show, $expected, ' and show returns correctly'); ok (! $acl->remove ('krb5', $admin, @trace), 'Removing a nonexistent entry fails'); -is ($acl->error, "cannot remove krb5:$admin from 2: entry not found in ACL", +is ($acl->error, "cannot remove krb5:$admin from example: entry not found in ACL", ' with the right error'); if ($acl->remove ('krb5', $user1, @trace)) { ok (1, ' but removing the first user works'); @@ -145,7 +145,7 @@ is (scalar (@entries), 1, ' and now there is one entry'); is ($entries[0][0], 'krb5', ' with the right scheme'); is ($entries[0][1], $user2, ' and the right identifier'); ok (! $acl->add ('krb5', $user2), 'Adding the same entry again fails'); -like ($acl->error, qr/^cannot add \Qkrb5:$user2\E to 2: /, +like ($acl->error, qr/^cannot add \Qkrb5:$user2\E to example: /, ' with the right error'); if ($acl->add ('krb5', '', @trace)) { ok (1, 'Adding a bad entry works'); diff --git a/perl/t/general/server.t b/perl/t/general/server.t index 0a527a5..8f4c16c 100755 --- a/perl/t/general/server.t +++ b/perl/t/general/server.t @@ -89,7 +89,7 @@ is ($server->acl_rename ('empty', 'test'), undef, is ($server->error, 'ACL empty not found', ' and returns the right error'); is ($server->acl_rename ('test', 'test2'), undef, ' and cannot rename to an existing name'); -like ($server->error, qr/^cannot rename ACL 6 to test2: /, +like ($server->error, qr/^cannot rename ACL test to test2: /, ' and returns the right error'); is ($server->acl_rename ('test', 'empty'), 1, 'Renaming does work'); is ($server->acl_rename ('test', 'empty'), undef, ' but not twice'); @@ -138,7 +138,7 @@ is ($server->error, 'ACL test not found', ' and returns the right error'); is ($server->acl_remove ('empty', 'krb5', $user2), undef, ' and removing an entry not there fails'); is ($server->error, - "cannot remove krb5:$user2 from 6: entry not found in ACL", + "cannot remove krb5:$user2 from empty: entry not found in ACL", ' and returns the right error'); is ($server->acl_show ('empty'), "Members of ACL empty (id: 6) are:\n krb5 $user1\n", @@ -148,7 +148,7 @@ is ($server->acl_remove ('empty', 'krb5', $user1), 1, is ($server->acl_remove ('empty', 'krb5', $user1), undef, ' but does not work twice'); is ($server->error, - "cannot remove krb5:$user1 from 6: entry not found in ACL", + "cannot remove krb5:$user1 from empty: entry not found in ACL", ' and returns the right error'); is ($server->acl_show ('empty'), "Members of ACL empty (id: 6) are:\n", ' and show returns the correct status'); @@ -168,7 +168,7 @@ is ($server->acl_remove ('ADMIN', 'krb5', $user1), 1, ' and then remove it'); is ($server->acl_remove ('ADMIN', 'krb5', $user1), undef, ' and remove a user not on it'); is ($server->error, - "cannot remove krb5:$user1 from 1: entry not found in ACL", + "cannot remove krb5:$user1 from ADMIN: entry not found in ACL", ' and get the right error'); # Now, create a few objects to use for testing and test the object API while @@ -994,7 +994,7 @@ is ($server->owner ('base', 'service/acl-user', 'test-destroy'), 1, is ($server->acl_destroy ('test-destroy'), undef, ' and now we cannot destroy that ACL'); is ($server->error, - 'cannot destroy ACL 9: ACL in use by base:service/acl-user', + 'cannot destroy ACL test-destroy: ACL in use by base:service/acl-user', ' with the right error'); is ($server->owner ('base', 'service/acl-user', ''), 1, ' but after we clear the owner'); -- cgit v1.2.3 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/lib/Wallet/ACL.pm | 38 +++++++- perl/lib/Wallet/ACL/Base.pm | 13 ++- perl/lib/Wallet/ACL/Nested.pm | 195 ++++++++++++++++++++++++++++++++++++++++++ perl/lib/Wallet/Admin.pm | 1 + perl/t/general/acl.t | 81 ++++++++++-------- perl/t/general/report.t | 9 +- perl/t/policy/stanford.t | 2 +- perl/t/verifier/nested.t | 84 ++++++++++++++++++ 8 files changed, 381 insertions(+), 42 deletions(-) create mode 100644 perl/lib/Wallet/ACL/Nested.pm create mode 100755 perl/t/verifier/nested.t diff --git a/perl/lib/Wallet/ACL.pm b/perl/lib/Wallet/ACL.pm index 260ff22..6d8005d 100644 --- a/perl/lib/Wallet/ACL.pm +++ b/perl/lib/Wallet/ACL.pm @@ -198,6 +198,19 @@ sub rename { $acls->ac_name ($name); $acls->update; $self->log_acl ('rename', undef, undef, $user, $host, $time); + + # Find any references to this being used as a nested verifier and + # update the name. This really breaks out of the normal flow, but + # it's hard to do otherwise. + %search = (ae_scheme => 'nested', + ae_identifier => $self->{name}, + ); + my @entries = $self->{schema}->resultset('AclEntry')->search(\%search); + for my $entry (@entries) { + $entry->ae_identifier ($name); + $entry->update; + } + $guard->commit; }; if ($@) { @@ -267,6 +280,17 @@ sub destroy { $entry->delete; } + # Find any references to this being used as a nested verifier and + # remove them. This really breaks out of the normal flow, but it's + # hard to do otherwise. + %search = (ae_scheme => 'nested', + ae_identifier => $self->{name}, + ); + @entries = $self->{schema}->resultset('AclEntry')->search(\%search); + for my $entry (@entries) { + $entry->delete; + } + # There should definitely be an ACL record to delete. %search = (ac_id => $self->{id}); my $entry = $self->{schema}->resultset('Acl')->find(\%search); @@ -302,6 +326,18 @@ sub add { $self->error ("unknown ACL scheme $scheme"); return; } + + # Check to make sure that this entry has a valid name for the scheme. + my $class = $self->scheme_mapping ($scheme); + my $object = eval { + $class->new ($identifier, $self->{schema}); + }; + unless ($object && $object->syntax_check ($identifier)) { + $self->error ("invalid ACL identifier $identifier for $scheme"); + return; + }; + + # Actually create the scheme. eval { my $guard = $self->{schema}->txn_scope_guard; my %record = (ae_id => $self->{id}, @@ -446,7 +482,7 @@ sub history { push (@{ $self->{check_errors} }, "unknown scheme $scheme"); return; } - $verifier{$scheme} = $class->new; + $verifier{$scheme} = $class->new ($identifier, $self->{schema}); unless (defined $verifier{$scheme}) { push (@{ $self->{check_errors} }, "cannot verify $scheme"); return; diff --git a/perl/lib/Wallet/ACL/Base.pm b/perl/lib/Wallet/ACL/Base.pm index a2b07cc..19ca612 100644 --- a/perl/lib/Wallet/ACL/Base.pm +++ b/perl/lib/Wallet/ACL/Base.pm @@ -20,7 +20,7 @@ use vars qw($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.02'; +$VERSION = '0.03'; ############################################################################## # Interface @@ -37,6 +37,11 @@ sub new { return $self; } +# The default name check method allows any name. +sub syntax_check { + return 1; +} + # The default check method denies all access. sub check { return 0; @@ -92,6 +97,12 @@ inherit from it. It is not used directly. Creates a new ACL verifier. The generic function provided here just creates and blesses an object. +=item syntax_check(PRINCIPAL, ACL) + +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) This method should always be overridden by child classes. The default diff --git a/perl/lib/Wallet/ACL/Nested.pm b/perl/lib/Wallet/ACL/Nested.pm new file mode 100644 index 0000000..3be84bd --- /dev/null +++ b/perl/lib/Wallet/ACL/Nested.pm @@ -0,0 +1,195 @@ +# Wallet::ACL::Nested - ACL class for nesting ACLs +# +# Written by Jon Robertson +# Copyright 2015 +# The Board of Trustees of the Leland Stanford Junior University +# +# See LICENSE for licensing terms. + +############################################################################## +# Modules and declarations +############################################################################## + +package Wallet::ACL::Nested; +require 5.006; + +use strict; +use warnings; +use vars qw($VERSION @ISA); + +use Wallet::ACL::Base; + +@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 persistant verifier, taking a database handle. This parent +# class just creates an empty object and ignores the handle. Child classes +# should override if there are necessary initialization tasks or if the handle +# will be used by the verifier. +sub new { + my $type = shift; + my ($name, $schema) = @_; + my $self = { + schema => $schema, + expanded => {}, + }; + bless ($self, $type); + return $self; +} + +# Name checking requires checking that there's an existing ACL already by +# this name. Try to create the ACL object and use that to determine. +sub syntax_check { + my ($self, $group) = @_; + + my $acl; + eval { $acl = Wallet::ACL->new ($group, $self->{schema}) }; + return 0 if $@; + return 0 unless $acl; + return 1; +} + +# For checking a nested ACL, we need to expand each entry and then 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) = @_; + unless ($principal) { + $self->error ('no principal specified'); + return; + } + unless ($group) { + $self->error ('malformed nested ACL'); + return; + } + + # Make an ACL object just so that we can use it to drop back into the + # normal ACL validation after we have expanded the nesting. + my $acl; + eval { $acl = Wallet::ACL->new ($group, $self->{schema}) }; + + # Get the list of all nested acl entries within this entry, and use it + # 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); + return 1 if $result; + } + return 0; +} + +# Get the membership of a group recursively. The final result will be a list +# of arrayrefs like that from Wallet::ACL->list, but expanded for full +# membership. +sub get_membership { + my ($self, $group) = @_; + + # Get the list of members for this nested acl. Consider any missing acls + # as empty. + my $schema = $self->{schema}; + my @members; + eval { + my $acl = Wallet::ACL->new ($group, $schema); + @members = $acl->list; + }; + + # Now go through and expand any other nested groups into their own + # memberships. + my @expanded; + for my $entry (@members) { + my ($type, $name) = @{ $entry }; + if ($type eq 'nested') { + + # Keep track of things we've already expanded and don't look them + # up again. + next if exists $self->{expanded}{$name}; + $self->{expanded}{$name} = 1; + push (@expanded, $self->get_membership ($name)); + + } else { + push (@expanded, $entry); + } + } + + return @expanded; +} + +1; +__END__ + +############################################################################## +# Documentation +############################################################################## + +=for stopwords +ACL Allbery verifier verifiers + +=head1 NAME + +Wallet::ACL::Base - Generic parent class for wallet ACL verifiers + +=head1 SYNOPSIS + + package Wallet::ACL::Simple + @ISA = qw(Wallet::ACL::Base); + sub check { + my ($self, $principal, $acl) = @_; + return ($principal eq $acl) ? 1 : 0; + } + +=head1 DESCRIPTION + +Wallet::ACL::Base is the generic parent class for wallet ACL verifiers. +It provides default functions and behavior and all ACL verifiers should +inherit from it. It is not used directly. + +=head1 METHODS + +=over 4 + +=item new() + +Creates a new ACL verifier. The generic function provided here just +creates and blesses an object. + +=item check(PRINCIPAL, ACL) + +This method should always be overridden by child classes. The default +implementation just declines all access. + +=item error([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. + +For the convenience of child classes, this method can also be called with +one or more error strings. If so, those strings are concatenated +together, trailing newlines are removed, any text of the form S> at the end of the message is stripped off, and the result is +stored as the error. Only child classes should call this method with an +error string. + +=back + +=head1 SEE ALSO + +Wallet::ACL(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/Admin.pm b/perl/lib/Wallet/Admin.pm index b38cc94..f6f1f90 100644 --- a/perl/lib/Wallet/Admin.pm +++ b/perl/lib/Wallet/Admin.pm @@ -118,6 +118,7 @@ sub default_data { [ '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' ], ]); diff --git a/perl/t/general/acl.t b/perl/t/general/acl.t index 80e8b3c..aad4b6d 100755 --- a/perl/t/general/acl.t +++ b/perl/t/general/acl.t @@ -12,7 +12,7 @@ use strict; use warnings; use POSIX qw(strftime); -use Test::More tests => 109; +use Test::More tests => 113; use Wallet::ACL; use Wallet::Admin; @@ -62,32 +62,6 @@ is ($@, '', ' with no exceptions'); ok ($acl->isa ('Wallet::ACL'), ' and the right class'); is ($acl->name, 'test', ' and the right name'); -# Test rename. -if ($acl->rename ('example', @trace)) { - ok (1, 'Renaming the ACL'); -} else { - is ($acl->error, '', 'Renaming the ACL'); -} -is ($acl->name, 'example', ' and the new name is right'); -is ($acl->id, 2, ' and the ID did not change'); -$acl = eval { Wallet::ACL->new ('test', $schema) }; -ok (!defined ($acl), ' and it cannot be found under the old name'); -is ($@, "ACL test not found\n", ' with the right error message'); -$acl = eval { Wallet::ACL->new ('example', $schema) }; -ok (defined ($acl), ' and it can be found with the new name'); -is ($@, '', ' with no exceptions'); -is ($acl->name, 'example', ' and the right name'); -is ($acl->id, 2, ' and the right ID'); -$acl = eval { Wallet::ACL->new (2, $schema) }; -ok (defined ($acl), ' and it can still found by ID'); -is ($@, '', ' with no exceptions'); -is ($acl->name, 'example', ' and the right name'); -is ($acl->id, 2, ' and the right ID'); -ok (! $acl->rename ('ADMIN', @trace), - ' but renaming to an existing name fails'); -like ($acl->error, qr/^cannot rename ACL example to ADMIN: /, - ' with the right error'); - # Test add, check, remove, list, and show. my @entries = $acl->list; is (scalar (@entries), 0, 'ACL starts empty'); @@ -124,14 +98,14 @@ is ($entries[0][1], $user1, ' and the right identifier for 1'); is ($entries[1][0], 'krb5', ' and the right scheme for 2'); is ($entries[1][1], $user2, ' and the right identifier for 2'); my $expected = <<"EOE"; -Members of ACL example (id: 2) are: +Members of ACL test (id: 2) are: krb5 $user1 krb5 $user2 EOE is ($acl->show, $expected, ' and show returns correctly'); ok (! $acl->remove ('krb5', $admin, @trace), 'Removing a nonexistent entry fails'); -is ($acl->error, "cannot remove krb5:$admin from example: entry not found in ACL", +is ($acl->error, "cannot remove krb5:$admin from test: entry not found in ACL", ' with the right error'); if ($acl->remove ('krb5', $user1, @trace)) { ok (1, ' but removing the first user works'); @@ -145,7 +119,7 @@ is (scalar (@entries), 1, ' and now there is one entry'); is ($entries[0][0], 'krb5', ' with the right scheme'); is ($entries[0][1], $user2, ' and the right identifier'); ok (! $acl->add ('krb5', $user2), 'Adding the same entry again fails'); -like ($acl->error, qr/^cannot add \Qkrb5:$user2\E to example: /, +like ($acl->error, qr/^cannot add \Qkrb5:$user2\E to test: /, ' with the right error'); if ($acl->add ('krb5', '', @trace)) { ok (1, 'Adding a bad entry works'); @@ -159,7 +133,7 @@ is ($entries[0][1], '', ' and the right identifier for 1'); is ($entries[1][0], 'krb5', ' and the right scheme for 2'); is ($entries[1][1], $user2, ' and the right identifier for 2'); $expected = <<"EOE"; -Members of ACL example (id: 2) are: +Members of ACL test (id: 2) are: krb5 krb5 $user2 EOE @@ -187,17 +161,50 @@ if ($acl->remove ('krb5', '', @trace)) { } @entries = $acl->list; is (scalar (@entries), 0, ' and now there are no entries'); -is ($acl->show, "Members of ACL example (id: 2) are:\n", ' and show concurs'); +is ($acl->show, "Members of ACL test (id: 2) are:\n", ' and show concurs'); is ($acl->check ($user2), 0, ' and the second user check fails'); is (scalar ($acl->check_errors), '', ' with no error message'); +# Test rename. +my $acl_nest = eval { Wallet::ACL->create ('test-nesting', $schema, @trace) }; +ok (defined ($acl_nest), 'ACL creation for setting up nested'); +if ($acl_nest->add ('nested', 'test', @trace)) { + ok (1, ' and adding the nesting'); +} else { + is ($acl_nest->error, '', ' and adding the nesting'); +} +if ($acl->rename ('example', @trace)) { + ok (1, 'Renaming the ACL'); +} else { + is ($acl->error, '', 'Renaming the ACL'); +} +is ($acl->name, 'example', ' and the new name is right'); +is ($acl->id, 2, ' and the ID did not change'); +$acl = eval { Wallet::ACL->new ('test', $schema) }; +ok (!defined ($acl), ' and it cannot be found under the old name'); +is ($@, "ACL test not found\n", ' with the right error message'); +$acl = eval { Wallet::ACL->new ('example', $schema) }; +ok (defined ($acl), ' and it can be found with the new name'); +is ($@, '', ' with no exceptions'); +is ($acl->name, 'example', ' and the right name'); +is ($acl->id, 2, ' and the right ID'); +$acl = eval { Wallet::ACL->new (2, $schema) }; +ok (defined ($acl), ' and it can still found by ID'); +is ($@, '', ' with no exceptions'); +is ($acl->name, 'example', ' and the right name'); +is ($acl->id, 2, ' and the right ID'); +ok (! $acl->rename ('ADMIN', @trace), + ' but renaming to an existing name fails'); +like ($acl->error, qr/^cannot rename ACL example to ADMIN: /, + ' with the right error'); +@entries = $acl_nest->list; +is ($entries[0][1], 'example', ' and the name in a nested ACL updated'); + # Test history. my $date = strftime ('%Y-%m-%d %H:%M:%S', localtime $trace[2]); my $history = <<"EOO"; $date create by $admin from $host -$date rename from test - by $admin from $host $date add krb5 $user1 by $admin from $host $date add krb5 $user2 @@ -210,6 +217,8 @@ $date remove krb5 $user2 by $admin from $host $date remove krb5 by $admin from $host +$date rename from test + by $admin from $host EOO is ($acl->history, $history, 'History is correct'); @@ -225,11 +234,13 @@ is ($@, "ACL example not found\n", ' with the right error message'); $acl = eval { Wallet::ACL->new (2, $schema) }; ok (!defined ($acl), ' or by ID'); is ($@, "ACL 2 not found\n", ' with the right error message'); +@entries = $acl_nest->list; +is (scalar (@entries), 0, ' and it is no longer a nested entry'); $acl = eval { Wallet::ACL->create ('example', $schema, @trace) }; ok (defined ($acl), ' and creating another with the same name works'); is ($@, '', ' with no exceptions'); is ($acl->name, 'example', ' and the right name'); -like ($acl->id, qr{\A[23]\z}, ' and an ID of 2 or 3'); +like ($acl->id, qr{\A[34]\z}, ' and an ID of 3 or 4'); # Test replace. by creating three acls, then assigning two objects to the # first, one to the second, and another to the third. Then replace the first diff --git a/perl/t/general/report.t b/perl/t/general/report.t index 170fe29..6f6b750 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 => 218; +use Test::More tests => 219; use Wallet::Admin; use Wallet::Report; @@ -57,13 +57,14 @@ 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), 6, 'There are six acl schemes created'); +is (scalar (@schemes), 7, '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], 'netdb', ' and the fifth member is correct'); -is ($schemes[5][0], 'netdb-root', ' and the sixth 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'); # Create an object. my $server = eval { Wallet::Server->new ('admin@EXAMPLE.COM', 'localhost') }; diff --git a/perl/t/policy/stanford.t b/perl/t/policy/stanford.t index 9ed0fa6..c58985b 100755 --- a/perl/t/policy/stanford.t +++ b/perl/t/policy/stanford.t @@ -140,7 +140,7 @@ is( 'example.stanford.edu'), 1, '...with netdb ACL line' -); + ); is( $server->acl_add('host/example.stanford.edu', 'krb5', 'host/example.stanford.edu@stanford.edu'), 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 e0b2ecf96ebc49fabd9c4b6af5c50b203d2b4832 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Mon, 8 Jun 2015 13:08:43 -0700 Subject: Wallet/Server.pm: Fix sorting of ACLs and entries There was an older mistake in sorting ACLs and entries, using && instead of || when sorting. Problem and fix pointed out to Chris Law. Change-Id: Iab46b4bcbd842978f88a7d9f63958ebea4806413 --- perl/lib/Wallet/Server.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/perl/lib/Wallet/Server.pm b/perl/lib/Wallet/Server.pm index 3ef5954..946ba10 100644 --- a/perl/lib/Wallet/Server.pm +++ b/perl/lib/Wallet/Server.pm @@ -154,8 +154,8 @@ sub create_check { $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; + @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; -- cgit v1.2.3 From 0b4201c8a65102227685f5cbe4f81407dce7e0b5 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Mon, 8 Jun 2015 21:59:25 -0700 Subject: Nested.pm: Updated comments around constructor Removed some default text and explained why we grab the database handle for future use. Change-Id: I50b3ae06c1761453de3140d501830c245d550c04 --- perl/lib/Wallet/ACL/Nested.pm | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/perl/lib/Wallet/ACL/Nested.pm b/perl/lib/Wallet/ACL/Nested.pm index 3be84bd..945d881 100644 --- a/perl/lib/Wallet/ACL/Nested.pm +++ b/perl/lib/Wallet/ACL/Nested.pm @@ -30,10 +30,8 @@ $VERSION = '0.01'; # Interface ############################################################################## -# Creates a new persistant verifier, taking a database handle. This parent -# class just creates an empty object and ignores the handle. Child classes -# should override if there are necessary initialization tasks or if the handle -# will be used by the verifier. +# Creates a new persistant verifier, taking a database handle to use for +# syntax check validation. sub new { my $type = shift; my ($name, $schema) = @_; -- cgit v1.2.3 From 43f386a6e3d0c141cd732b5ef5c2be8349f51f03 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Tue, 9 Jun 2015 13:06:56 -0700 Subject: ACL.pm: Destroying a nested ACL will now fail When destroying an ACL nested in other ACLs, we now fail with an explanation rather than going through to remove all the places it's nested. That's more in line with how we handle trying to destroy ACLs that own things. Change-Id: I8bc0530e37c54369ec52d9b369f8fabe98def77a --- perl/lib/Wallet/ACL.pm | 23 ++++++++++++----------- perl/t/general/acl.t | 14 +++++++++++--- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/perl/lib/Wallet/ACL.pm b/perl/lib/Wallet/ACL.pm index 6d8005d..f875185 100644 --- a/perl/lib/Wallet/ACL.pm +++ b/perl/lib/Wallet/ACL.pm @@ -273,19 +273,20 @@ sub destroy { die "ACL in use by ".$entry->ob_type.":".$entry->ob_name; } - # Delete any entries (there may or may not be any). - my %search = (ae_id => $self->{id}); - @entries = $self->{schema}->resultset('AclEntry')->search(\%search); - for my $entry (@entries) { - $entry->delete; + # Also make certain the ACL isn't being nested in another. + my %search = (ae_scheme => 'nested', + ae_identifier => $self->{name}); + my %options = (join => 'acls', + prefetch => 'acls'); + @entries = $self->{schema}->resultset('AclEntry')->search(\%search, + \%options); + if (@entries) { + my ($entry) = @entries; + die "ACL is nested in ACL ".$entry->acls->ac_name; } - # Find any references to this being used as a nested verifier and - # remove them. This really breaks out of the normal flow, but it's - # hard to do otherwise. - %search = (ae_scheme => 'nested', - ae_identifier => $self->{name}, - ); + # Delete any entries (there may or may not be any). + %search = (ae_id => $self->{id}); @entries = $self->{schema}->resultset('AclEntry')->search(\%search); for my $entry (@entries) { $entry->delete; diff --git a/perl/t/general/acl.t b/perl/t/general/acl.t index aad4b6d..4de7493 100755 --- a/perl/t/general/acl.t +++ b/perl/t/general/acl.t @@ -12,7 +12,7 @@ use strict; use warnings; use POSIX qw(strftime); -use Test::More tests => 113; +use Test::More tests => 115; use Wallet::ACL; use Wallet::Admin; @@ -223,10 +223,18 @@ EOO is ($acl->history, $history, 'History is correct'); # Test destroy. +$acl->destroy (@trace); +is ($acl->error, 'cannot destroy ACL example: ACL is nested in ACL test-nesting', + 'Destroying a nested ACL fails'); +if ($acl_nest->remove ('nested', 'example', @trace)) { + ok (1, ' and removing the nesting succeeds'); +} else { + is ($acl_nest->error, '', 'and removing the nesting succeeds'); +} if ($acl->destroy (@trace)) { - ok (1, 'Destroying the ACL works'); + ok (1, ' and now destroying the ACL works'); } else { - is ($acl->error, '', 'Destroying the ACL works'); + is ($acl->error, '', ' and now destroying the ACL works'); } $acl = eval { Wallet::ACL->new ('example', $schema) }; ok (!defined ($acl), ' and now cannot be found'); -- cgit v1.2.3 From 5d668b86ced32e84fd0f49046326a0a5e20dc8eb Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Tue, 9 Jun 2015 15:04:14 -0700 Subject: Added wallet report for nested ACL We needed a way to report on where all a specific ACL might be nested, since we can't destroy an ACL until it's no longer being nested. For the immediate this is part of wallet-report. Change-Id: I41c11b73325d1eb3a28289eac3505bf965877be1 --- perl/lib/Wallet/Report.pm | 47 ++++++++++++++++++++++++++++++++++++++++------- perl/t/general/report.t | 9 ++++++++- server/wallet-report | 1 + 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/perl/lib/Wallet/Report.pm b/perl/lib/Wallet/Report.pm index fc7bb4d..353cd97 100644 --- a/perl/lib/Wallet/Report.pm +++ b/perl/lib/Wallet/Report.pm @@ -359,8 +359,7 @@ sub types { # ACL reports ############################################################################## -# Returns the SQL statement required to find and return all ACLs in the -# database. +# Returns the array of all ACLs in the database. sub acls_all { my ($self) = @_; my @acls; @@ -384,7 +383,7 @@ sub acls_all { return (@acls); } -# Returns the SQL statement required to find all empty ACLs in the database. +# Returns the array of all empty ACLs in the database. sub acls_empty { my ($self) = @_; my @acls; @@ -410,9 +409,36 @@ sub acls_empty { return (@acls); } -# Returns the SQL statement and the field required to find ACLs containing the -# specified entry. The identifier is automatically surrounded by wildcards to -# do a substring search. +# Returns the array of ACLs that nest a given ACL. +sub acls_nesting { + my ($self, $name) = @_; + my @acls; + + my $schema = $self->{schema}; + my %search = (ae_scheme => 'nested', + ae_identifier => $name); + my %options = (join => 'acl_entries', + prefetch => 'acl_entries', + order_by => [ qw/ac_id/ ], + select => [ qw/ac_id ac_name/ ]); + + eval { + my @acls_rs = $schema->resultset('Acl')->search (\%search, \%options); + for my $acl_rs (@acls_rs) { + push (@acls, [ $acl_rs->ac_id, $acl_rs->ac_name ]); + } + }; + + if ($@) { + $self->error ("cannot list ACLs: $@"); + return; + } + return (@acls); +} + +# Returns the array of all ACLs containing the specified entry. The given +# identifier is automatically surrounded by wildcards to do a substring +# search. sub acls_entry { my ($self, $type, $identifier) = @_; my @acls; @@ -440,7 +466,7 @@ sub acls_entry { return (@acls); } -# Returns the SQL statement required to find unused ACLs. +# Returns the array of all unused ACLs. sub acls_unused { my ($self) = @_; my @acls; @@ -553,6 +579,13 @@ sub acls { @acls = $self->acls_empty; } elsif ($type eq 'unused') { @acls = $self->acls_unused; + } elsif ($type eq 'nesting') { + if (@args == 0) { + $self->error ('ACL nesting search requires an ACL to search'); + return; + } else { + @acls = $self->acls_nesting (@args); + } } else { $self->error ("unknown search type: $type"); return; diff --git a/perl/t/general/report.t b/perl/t/general/report.t index 6f6b750..a841acd 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 => 219; +use Test::More tests => 222; use Wallet::Admin; use Wallet::Report; @@ -366,6 +366,13 @@ is ($server->acl_add ('third', 'base', 'baz'), 1, is (scalar (@acls), 0, 'There are no duplicate ACLs'); is ($report->error, undef, ' and no error'); +# See if the acl nesting report works correctly. +is ($server->acl_add ('fourth', 'nested', 'second'), 1, + 'Adding an ACL as a nested entry for another works'); +@acls = $report->acls ('nesting', 'second'); +is (scalar (@acls), 1, ' and the nested report shows one nesting'); +is ($acls[0][1], 'fourth', ' with the correct ACL nesting it'); + # Clean up. $admin->destroy; system ('rm -r test-files') == 0 or die "cannot remove test-files\n"; diff --git a/server/wallet-report b/server/wallet-report index 77a2f8a..4719a8a 100755 --- a/server/wallet-report +++ b/server/wallet-report @@ -17,6 +17,7 @@ Wallet reporting help: acls duplicate ACLs that duplicate another acls empty All empty ACLs acls entry ACLs containing this entry (wildcarded) + acls nesting ACLs containing this ACL as a nested entry acls unused ACLs that are not referenced by any object audit acls name ACLs failing the naming policy audit objects name Objects failing the naming policy -- cgit v1.2.3 From 2e00a586996cf4e53bd9d03afca7d46471549171 Mon Sep 17 00:00:00 2001 From: Jon Robertson Date: Tue, 25 Aug 2015 14:13:09 -0700 Subject: stanford.t: Added netdb configuration to policy tests Since we now check to see if something is a valid netdb node entry for the ACL verifiers, we need to have a valid netdb setup to run. Change-Id: Ic2651f8b8b306dfa1f426d91f329b5100a9a1d64 --- perl/t/policy/stanford.t | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/perl/t/policy/stanford.t b/perl/t/policy/stanford.t index c58985b..0c3dd2d 100755 --- a/perl/t/policy/stanford.t +++ b/perl/t/policy/stanford.t @@ -29,6 +29,11 @@ BEGIN { use_ok('Wallet::Server'); } +# Set up our configuration for netdb, needed for the netdb verifier. +$Wallet::Config::NETDB_REALM = 'stanford.edu'; +$Wallet::Config::NETDB_REMCTL_CACHE = $ENV{KRB5CCNAME}; +$Wallet::Config::NETDB_REMCTL_HOST = 'netdb-node-roles-rc.stanford.edu'; + # Various valid keytab names. my @VALID_KEYTABS = qw(host/example.stanford.edu HTTP/example.stanford.edu service/example example/cgi class-example01/cgi dept-01example/cgi -- 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(-) 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 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 0eb853eb2ef7e7063c0219ce2cbd1e239d5579b7 Mon Sep 17 00:00:00 2001 From: Bill MacAllister Date: Thu, 3 Dec 2015 00:27:33 +0000 Subject: Implement support for managed Active Directory keytabs This version implements Active Directory as the store for keytabs. The interface to Active Directory uses a combination of direct LDAP queries and the msktutil utility. This version does not support the wallet unchanging flag. Unchanging requires that a keytab be retrieved without changing the password/kvno which is not supported by msktutil. --- NEWS | 9 + perl/lib/Wallet/Kadmin.pm | 3 + perl/lib/Wallet/Kadmin/AD.pm | 440 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 452 insertions(+) create mode 100644 perl/lib/Wallet/Kadmin/AD.pm diff --git a/NEWS b/NEWS index 272b109..0e268a9 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,14 @@ User-Visible wallet Changes +wallet 1.3 (2015-11-27) + + This version implements Active Directory as the store for keytabs. + The interface to Active Directory uses a combination of direct + LDAP queries and the msktutil utility. This version does not + support the wallet unchanging flag. Unchanging requires that a + keytab be retrieved without changing the password/kvno which is + not supported by msktutil. + wallet 1.2 (2014-12-08) The duo object type has been split into several sub-types, each for a diff --git a/perl/lib/Wallet/Kadmin.pm b/perl/lib/Wallet/Kadmin.pm index 65a5700..cb3bd47 100644 --- a/perl/lib/Wallet/Kadmin.pm +++ b/perl/lib/Wallet/Kadmin.pm @@ -69,6 +69,9 @@ sub new { } elsif (lc ($Wallet::Config::KEYTAB_KRBTYPE) eq 'heimdal') { require Wallet::Kadmin::Heimdal; $kadmin = Wallet::Kadmin::Heimdal->new; + } elsif (lc ($Wallet::Config::KEYTAB_KRBTYPE) eq 'ad') { + require Wallet::Kadmin::AD; + $kadmin = Wallet::Kadmin::AD->new; } else { my $type = $Wallet::Config::KEYTAB_KRBTYPE; die "unknown KEYTAB_KRBTYPE setting: $type\n"; diff --git a/perl/lib/Wallet/Kadmin/AD.pm b/perl/lib/Wallet/Kadmin/AD.pm new file mode 100644 index 0000000..acdd144 --- /dev/null +++ b/perl/lib/Wallet/Kadmin/AD.pm @@ -0,0 +1,440 @@ +# Wallet::Kadmin::AD -- Wallet Kerberos administration API for AD. +# +# Written by Bill MacAllister +# Based on work by Russ Allbery and +# Jon Robertson +# Copyright 2015 +# Dropbox +# +# See LICENSE for licensing terms. + +############################################################################## +# Modules and declarations +############################################################################## + +package Wallet::Kadmin::AD; +require 5.006; + +use strict; +use warnings; +use vars qw(@ISA $VERSION); + +use Wallet::Config (); +use Wallet::Kadmin (); + +use Authen::SASL (); +use Net::LDAP; +use IPC::Run qw( run timeout ); + +@ISA = qw(Wallet::Kadmin); + +# 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'; + +############################################################################## +# kadmin Interaction +############################################################################## + +# Make sure that principals are well-formed and don't contain characters that +# will cause us problems when talking to kadmin. Takes a principal and +# returns true if it's okay, false otherwise. Note that we do not permit +# realm information here. +sub valid_principal { + my ($self, $principal) = @_; + my $valid = 0; + if ($principal =~ m,^(host|service)(/[\w_.-]+)?\z,) { + my $k_type = $1; + my $k_id = $2; + if ($k_type eq 'host') { + $valid = 1 if $k_id =~ m/[.]/xms; + } elsif ($k_type eq 'service') { + $valid = 1 if length($k_id) < 19; + } + } + return $valid; +} + +# Connect to the Active Directory server using LDAP. The connection is +# used to retrieve information about existing keytabs since msktutil +# does not have this functionality. +sub ldap_connect { + my ($self) = @_; + + if (!-e $Wallet::Config::AD_CACHE) { + die 'Missing kerberos ticket cache ' . $Wallet::Config::AD_CACHE; + } + + my $ldap; + eval { + local $ENV{KRB5CCNAME} = $Wallet::Config::AD_CACHE; + my $sasl = Authen::SASL->new (mechanism => 'GSSAPI'); + $ldap + = Net::LDAP->new ($Wallet::Config::KEYTAB_HOST, onerror => 'die'); + my $mesg = eval { $ldap->bind (undef, sasl => $sasl) }; + }; + if ($@) { + my $error = $@; + chomp $error; + 1 while ($error =~ s/ at \S+ line \d+\.?\z//); + die "LDAP bind to AD failed: $error\n"; + } + + return $ldap; +} + +sub ldap_base_filter { + my ($self, $principal) = @_; + my $base; + my $filter; + if ($principal =~ m,^host/(\S+),xms) { + my $fqdn = $1; + my $host = $fqdn; + $host =~ s/[.].*//xms; + $base = $Wallet::Config::AD_COMPUTER_DN; + $filter = "(samAccountName=${host}\$)"; + } elsif ($principal =~ m,^service/(\S+),xms) { + my $id = $1; + $base = $Wallet::Config::AD_USER_DN; + $filter = "(servicePrincipalName=service/${id})"; + } + return ($base, $filter); +} + +# TODO: Get a keytab from the keytab cache. +sub get_ad_keytab { + my ($self, $principal) = @_; + return; +} + +# Run a msktutil command and capture the output. Returns the output, +# either as a list of lines or, in scalar context, as one string. +# Depending on the exit status of msktutil or on the eval trap to know +# when the msktutil command fails. The error string returned from the +# call to run frequently contains information about a success rather +# that error output. +sub msktutil { + my ($self, $args_ref) = @_; + unless (defined ($Wallet::Config::KEYTAB_PRINCIPAL) + and defined ($Wallet::Config::KEYTAB_FILE) + and defined ($Wallet::Config::KEYTAB_REALM)) { + die "keytab object implementation not configured\n"; + } + unless (defined ($Wallet::Config::AD_SERVER) + and defined ($Wallet::Config::AD_COMPUTER_DN) + and defined ($Wallet::Config::AD_USER_DN) + and defined ($Wallet::Config::AD_KEYTAB_BUCKET)) { + die "Active Directory support not configured\n"; + } + my @args = @{$args_ref}; + my @cmd = ($Wallet::Config::AD_MSKTUTIL); + push @cmd, @args; + + my $in; + my $out; + my $err; + my $err_msg; + my $err_no; + eval { + local $ENV{KRB5CCNAME} = $Wallet::Config::AD_CACHE; + run \@cmd, \$in, \$out, \$err, timeout(120); + if ($?) { + $err_no = $?; + } + }; + if ($@) { + $err_msg .= "ERROR ($err_no): $@\n"; + } + if ($err_no || $err_msg) { + if ($err) { + $err_msg .= "ERROR: $err\n" + } + if ($Wallet::Config::AD_DEBUG) { + $err_msg .= 'Problem command: ' . join(' ', @cmd) . "\n"; + } + die $err_msg; + } else { + if ($err) { + $out .= "\n" . $err; + } + } + return $out; +} + +# Either create or update a keytab for the principal. Return the +# name of the keytab file created. +sub ad_create_update { + my ($self, $principal, $action) = @_; + my $keytab = $Wallet::Config::KEYTAB_TMP . "/keytab.$$"; + if (-e $keytab) { + unlink $keytab or die "Problem deleting $keytab\n"; + } + my @cmd = ('--' . $action); + push @cmd, '--server', $Wallet::Config::AD_SERVER; + push @cmd, '--enctypes', '0x4'; + push @cmd, '--enctypes', '0x8'; + push @cmd, '--enctypes', '0x10'; + push @cmd, '--keytab', $keytab; + push @cmd, '--realm', $Wallet::Config::KEYTAB_REALM; + if ($principal =~ m,^host/(\S+),xms) { + my $fqdn = $1; + my $host = $fqdn; + $host =~ s/[.].*//xms; + push @cmd, '--dont-expire-password'; + push @cmd, '--computer-name', $host; + push @cmd, '--upn', "host/$fqdn"; + push @cmd, '--hostname', $fqdn; + } elsif ($principal =~ m,^service/(\S+),xms) { + my $service_id = $1; + push @cmd, '--use-service-account'; + push @cmd, '--service', "service/$service_id"; + push @cmd, '--account-name', "srv-${service_id}"; + push @cmd, '--no-pac'; + } + $self->msktutil(\@cmd); + return $keytab; +} + +############################################################################## +# Public interfaces +############################################################################## + +# Set a callback to be called for forked kadmin processes. +sub fork_callback { + my ($self, $callback) = @_; + $self->{fork_callback} = $callback; +} + +# Check whether a given principal already exists. Returns true if so, +# false otherwise. The best way to do this with AD is to perform an +# ldap query. +sub exists { + my ($self, $principal) = @_; + return unless $self->valid_principal ($principal); + + my $ldap = $self->ldap_connect(); + my ($base, $filter) = $self->ldap_base_filter($principal); + my @attrs = ('objectClass', 'msds-KeyVersionNumber'); + + my $result; + eval { + $result = $ldap->search( + base => $base, + scope => 'subtree', + filter => $filter, + attrs => \@attrs + ); + }; + if ($@) { + my $error = $@; + die "LDAP search error: $error\n"; + } + if ($result->code) { + my $m; + if ($Wallet::Config::AD_DEBUG) { + $m .= "INFO base:$base filter:$filter scope:subtree\n"; + } + $m .= 'ERROR:' . $result->error . "\n"; + die $m + } + if ($result->count > 1) { + my $m = "ERROR: too many AD entries for this keytab\n"; + for my $entry ($result->entries) { + $m .= 'INFO: dn found ' . $entry->dn . "\n"; + } + die $m; + } + if ($result->count) { + for my $entry ($result->entries) { + return $entry->get_value('msds-KeyVersionNumber'); + } + } else { + return 0; + } + return; +} + +# Call msktutil to Create a principal in Kerberos. Sets the error and +# returns undef on failure, and returns 1 on either success or the +# principal already existing. Note, this creates a keytab that is +# never used because it is not returned to the user. +sub create { + my ($self, $principal) = @_; + unless ($self->valid_principal ($principal)) { + die "ERROR: invalid principal name $principal\n"; + return; + } + return 1 if $self->exists($principal); + my $file = $self->ad_create_update($principal, 'create'); + if (-e $file) { + unlink $file or die "Problem deleting $file\n"; + } + return 1; +} + +# TODO: Return a keytab. Need to create a local keytab cache when +# a keytab is marked unchanging and return that. +sub keytab { + my ($self, $principal) = @_; + unless ($self->valid_principal ($principal)) { + die "ERROR: invalid principal name $principal\n"; + return; + } + my $file = 'call to route to get the file name of local keytab file'; + if (!-e $file) { + die "ERROR: keytab file $file does not exist.\n"; + } + return $self->read_keytab ($file); +} + +# Update a keytab for a principal. This action changes the AD +# password for the principal and increments the kvno. The enctypes +# passed in are ignored. +sub keytab_rekey { + my ($self, $principal, @enctypes) = @_; + unless ($self->valid_principal ($principal)) { + die "ERROR: invalid principal name: $principal\n"; + return; + } + if (!$self->exists($principal)) { + die "ERROR: $principal does not exist\n"; + } + unless ($self->valid_principal($principal)) { + die "ERROR: invalid principal name $principal\n"; + return; + } + my $file = $self->ad_create_update($principal, 'update'); + return $self->read_keytab ($file); +} + +# Delete a principal from Kerberos. Return true if successful, false +# otherwise. If the deletion fails, sets the error. If the principal +# doesn't exist, return success; we're bringing reality in line with +# our expectations. For AD this means just delete the object using +# LDAP. +sub destroy { + my ($self, $principal) = @_; + unless ($self->valid_principal ($principal)) { + $self->error ("invalid principal name: $principal"); + } + my $exists = $self->exists ($principal); + if (!defined $exists) { + return; + } elsif (not $exists) { + return 1; + } + + my $k_type; + my $k_id; + my $dn; + if ($principal =~ m,^(host|service)/(\S+),xms) { + $k_type = $1; + $k_id = $2; + if ($k_type eq 'host') { + my $host = $k_id; + $host =~ s/[.].*//; + $dn = "cn=${host}," . $Wallet::Config::AD_COMPUTER_DN; + } elsif ($k_type eq 'service') { + $dn = "cn=srv-${k_id}," . $Wallet::Config::AD_USER_DN; + } + } + + my $ldap = $self->ldap_connect(); + my $msgid = $ldap->delete($dn); + if ($msgid->code) { + my $m; + if ($Wallet::Config::AD_DEBUG) { + $m .= "ERROR: Problem deleting $dn\n"; + } + $m .= $msgid->error; + die $m; + } + return 1; +} + +# Create a new AD kadmin object. Very empty for the moment, but later it +# will probably fill out if we go to using a module rather than calling +# kadmin directly. +sub new { + my ($class) = @_; + unless (defined ($Wallet::Config::KEYTAB_TMP)) { + die "KEYTAB_TMP configuration variable not set\n"; + } + my $self = {}; + bless ($self, $class); + return $self; +} + +1; +__END__ + +############################################################################## +# Documentation +############################################################################## + +=for stopwords +rekeying rekeys remctl backend keytabs keytab kadmin KDC API Allbery +unlinked + +=head1 NAME + +Wallet::Kadmin::AD - Wallet Kerberos administration API for Active Directory + +=head1 SYNOPSIS + + my $kadmin = Wallet::Kadmin::AD->new; + $kadmin->create ('host/foo.example.com'); + my $data = $kadmin->keytab_rekey ('host/foo.example.com'); + $data = $kadmin->keytab ('host/foo.example.com'); + my $exists = $kadmin->exists ('host/oldshell.example.com'); + $kadmin->destroy ('host/oldshell.example.com') if $exists; + +=head1 DESCRIPTION + +Wallet::Kadmin::AD implements the Wallet::Kadmin API for Active +Directory Kerberos, providing an interface to create and delete +principals and create keytabs. It provides the API documented in +L for an Active Directory Kerberos KDC. + +AD erberos does not provide any method via msktuil to retrieve a +keytab for a principal without rekeying it, so the keytab() method (as +opposed to keytab_rekey(), which rekeys the principal) is implemented +using a local keytab cache. + +To use this class, several configuration parameters must be set. See +L for details. + +=head1 FILES + +=over 4 + +=item KEYTAB_TMP/keytab. + +The keytab is created in this file and then read into memory. KEYTAB_TMP +is set in the wallet configuration, and is the process ID of the +current process. The file is unlinked after being read. + +=back + +=head1 LIMITATIONS + +Currently, this implementation calls an external B program rather +than using a native Perl module and therefore requires B be +installed and parses its output. + +=head1 SEE ALSO + +msktutil, Wallet::Config(3), Wallet::Kadmin(3), +Wallet::Object::Keytab(3), wallet-backend(8) + +This module is part of the wallet system. The current version is +available from L. + +=head1 AUTHORS + +Bill MacAllister +and Russ Allbery +and Jon Robertson . + +=cut -- cgit v1.2.3 From aa5d18c209ee31bfd42bbe76e03d9ad71a0b8f51 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Mon, 14 Dec 2015 21:56:47 -0800 Subject: Update some style issues in NEWS --- NEWS | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/NEWS b/NEWS index 9e124e9..3afbd2c 100644 --- a/NEWS +++ b/NEWS @@ -1,34 +1,34 @@ User-Visible wallet Changes -wallet 1.3 (xxxx-xx-xx) +wallet 1.3 (unreleased) A new object type, password (Wallet::Object::Password), is now supported. This is a subclass of the file object that will randomly generate content for the object if you do a get before storing any content inside it. - Added a new command to wallet-backend, update. This will update the + Add a new command to wallet-backend, update. This will update the contents of an object before running a get on it, and is only valid - for objects that can automatically get new content, such as keytab - and password objects. A keytab will get a new kvno regardless of - the unchanging flag if called with update. In a future release get - will be changed to never update a keytab, and the unchanging flag - will be ignored. Please start moving to use get or update as the - situation warrants. + for objects that can automatically get new content, such as keytab and + password objects. A keytab will get a new kvno regardless of the + unchanging flag if called with update. In a future release get will + be changed to never update a keytab, and the unchanging flag will be + ignored. Please start moving to use get or update as the situation + warrants. - Added an acl replace command, to change all objects owned by one ACL - to be owned by another. + Add an acl replace command, to change all objects owned by one ACL to + be owned by another. All ACL operations now refer to the ACL by name rather than ID. - Added a report for unstored objects to wallet-report, and cleaned up - the help for the existing unused report that implied it showed - unstored as well as unused. + Add a report for unstored objects to wallet-report, and cleaned up the + help for the existing unused report that implied it showed unstored as + well as unused. - Took contributions from Commerzbank AG on the wallet history. Added - a command to dump all object history for searching on to - wallet-report, and added a new script for more detailed object - history operations to the contrib directory. + Took contributions from Commerzbank AG to improve wallet history. Add + a command to dump all object history for searching on to + wallet-report, and add a new script for more detailed object history + operations to the contrib directory. wallet 1.2 (2014-12-08) -- cgit v1.2.3 From bd0f18af97c495aa78059317830ff7e6a6fd949d Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Mon, 14 Dec 2015 22:19:11 -0800 Subject: Skip Stanford naming policy tests that require NetDB We need a fake NetDB server to test this stuff properly, but until then, just avoid running the tests. --- perl/t/policy/stanford.t | 298 ++++++++++++++++++++++++++--------------------- 1 file changed, 162 insertions(+), 136 deletions(-) diff --git a/perl/t/policy/stanford.t b/perl/t/policy/stanford.t index 0c3dd2d..d2727c8 100755 --- a/perl/t/policy/stanford.t +++ b/perl/t/policy/stanford.t @@ -130,160 +130,186 @@ for my $name (@VALID_FILES) { } } -# Now we need an actual database. Use Wallet::Admin to set it up. -db_setup; -my $setup = eval { Wallet::Admin->new }; -is($@, q{}, 'Database initialization did not die'); -is($setup->reinitialize($ADMIN), 1, 'Database initialization succeeded'); -my $server = eval { Wallet::Server->new(@TRACE) }; -is($@, q{}, 'Server creation did not die'); +# Now we need an actual database. Use Wallet::Admin to set it up. These +# remaining tests require creating NetDB ACLs, so need a Stanford Kerberos +# principal currently. +my $klist = `klist 2>&1` || ''; +SKIP: { + skip "tests useful only with Stanford Kerberos tickets", 27 + unless ($klist =~ /^(Default p|\s+P)rincipal: \S+\@stanford\.edu$/m); -# Create a host/example.stanford.edu ACL that uses the netdb ACL type. -is($server->acl_create('host/example.stanford.edu'), 1, 'Created netdb ACL'); -is( - $server->acl_add('host/example.stanford.edu', 'netdb', - 'example.stanford.edu'), - 1, - '...with netdb ACL line' - ); -is( - $server->acl_add('host/example.stanford.edu', 'krb5', - 'host/example.stanford.edu@stanford.edu'), - 1, - '...and krb5 ACL line' -); + db_setup; + my $setup = eval { Wallet::Admin->new }; + is($@, q{}, 'Database initialization did not die'); + is($setup->reinitialize($ADMIN), 1, 'Database initialization succeeded'); + my $server = eval { Wallet::Server->new(@TRACE) }; + is($@, q{}, 'Server creation did not die'); -# Likewise for host/foo.example.edu with the netdb-root ACL type. -is($server->acl_create('host/foo.stanford.edu'), 1, 'Created netdb-root ACL'); -is( - $server->acl_add('host/foo.stanford.edu', 'netdb-root', - 'foo.stanford.edu'), - 1, - '...with netdb-root ACL line' -); -is( - $server->acl_add('host/foo.stanford.edu', 'krb5', - 'host/foo.stanford.edu@stanford.edu'), - 1, - '...and krb5 ACL line' -); - -# Create a group/its-idg ACL, which will be used for autocreation of file -# objects. -is($server->acl_create('group/its-idg'), 1, 'Created group/its-idg ACL'); -is($server->acl_add('group/its-idg', 'krb5', $ADMIN), 1, '...with member'); + # Create a host/example.stanford.edu ACL that uses the netdb ACL type. + is( + $server->acl_create('host/example.stanford.edu'), + 1, + 'Created netdb ACL' + ); + is($server->error, undef, ' with no error'); + is( + $server->acl_add('host/example.stanford.edu', 'netdb', + 'example.stanford.edu'), + 1, + '...with netdb ACL line' + ); + is($server->error, undef, ' with no error'); + is( + $server->acl_add('host/example.stanford.edu', 'krb5', + 'host/example.stanford.edu@stanford.edu'), + 1, + '...and krb5 ACL line' + ); + is($server->error, undef, ' with no error'); -# Now we can test default ACLs. First, without a root instance. -local $ENV{REMOTE_USER} = $ADMIN; -is_deeply( - [default_owner('keytab', 'host/bar.stanford.edu')], - [ - 'host/bar.stanford.edu', - ['netdb', 'bar.stanford.edu'], - ['krb5', 'host/bar.stanford.edu@stanford.edu'] - ], - 'Correct default owner for host-based keytab' -); -is_deeply( - [default_owner('keytab', 'HTTP/example.stanford.edu')], - [ - 'host/example.stanford.edu', - ['netdb', 'example.stanford.edu'], - ['krb5', 'host/example.stanford.edu@stanford.edu'] - ], - '...and when netdb ACL already exists' -); -is_deeply( - [default_owner('keytab', 'webauth/foo.stanford.edu')], - [ - 'host/foo.stanford.edu', - ['netdb-root', 'foo.stanford.edu'], - ['krb5', 'host/foo.stanford.edu@stanford.edu'] - ], - '...and when netdb-root ACL already exists' -); + # Likewise for host/foo.example.edu with the netdb-root ACL type. + is( + $server->acl_create('host/foo.stanford.edu'), + 1, + 'Created netdb-root ACL' + ); + is( + $server->acl_add('host/foo.stanford.edu', 'netdb-root', + 'foo.stanford.edu'), + 1, + '...with netdb-root ACL line' + ); + is( + $server->acl_add('host/foo.stanford.edu', 'krb5', + 'host/foo.stanford.edu@stanford.edu'), + 1, + '...and krb5 ACL line' + ); -# Now with a root instance. -local $ENV{REMOTE_USER} = 'admin/root@stanford.edu'; -is_deeply( - [default_owner('keytab', 'host/bar.stanford.edu')], - [ - 'host/bar.stanford.edu', - ['netdb-root', 'bar.stanford.edu'], - ['krb5', 'host/bar.stanford.edu@stanford.edu'] - ], - 'Correct default owner for host-based keytab for /root' -); -is_deeply( - [default_owner('keytab', 'HTTP/example.stanford.edu')], - [ - 'host/example.stanford.edu', - ['netdb-root', 'example.stanford.edu'], - ['krb5', 'host/example.stanford.edu@stanford.edu'] - ], - '...and when netdb ACL already exists' -); -is_deeply( - [default_owner('keytab', 'webauth/foo.stanford.edu')], - [ - 'host/foo.stanford.edu', - ['netdb-root', 'foo.stanford.edu'], - ['krb5', 'host/foo.stanford.edu@stanford.edu'] - ], - '...and when netdb-root ACL already exists' -); + # Create a group/its-idg ACL, which will be used for autocreation of file + # objects. + is($server->acl_create('group/its-idg'), 1, 'Created group/its-idg ACL'); + is($server->acl_add('group/its-idg', 'krb5', $ADMIN), 1, '...with member'); -# Check for a type that isn't host-based. -is(default_owner('keytab', 'service/foo'), undef, - 'No default owner for service/foo'); + # Now we can test default ACLs. First, without a root instance. + local $ENV{REMOTE_USER} = $ADMIN; + is_deeply( + [default_owner('keytab', 'host/bar.stanford.edu')], + [ + 'host/bar.stanford.edu', + ['netdb', 'bar.stanford.edu'], + ['krb5', 'host/bar.stanford.edu@stanford.edu'] + ], + 'Correct default owner for host-based keytab' + ); + is_deeply( + [default_owner('keytab', 'HTTP/example.stanford.edu')], + [ + 'host/example.stanford.edu', + ['netdb', 'example.stanford.edu'], + ['krb5', 'host/example.stanford.edu@stanford.edu'] + ], + '...and when netdb ACL already exists' + ); + is_deeply( + [default_owner('keytab', 'webauth/foo.stanford.edu')], + [ + 'host/foo.stanford.edu', + ['netdb-root', 'foo.stanford.edu'], + ['krb5', 'host/foo.stanford.edu@stanford.edu'] + ], + '...and when netdb-root ACL already exists' + ); -# Check for an unknown object type. -is(default_owner('unknown', 'foo'), undef, - 'No default owner for unknown type'); + # Now with a root instance. + local $ENV{REMOTE_USER} = 'admin/root@stanford.edu'; + is_deeply( + [default_owner('keytab', 'host/bar.stanford.edu')], + [ + 'host/bar.stanford.edu', + ['netdb-root', 'bar.stanford.edu'], + ['krb5', 'host/bar.stanford.edu@stanford.edu'] + ], + 'Correct default owner for host-based keytab for /root' + ); + is_deeply( + [default_owner('keytab', 'HTTP/example.stanford.edu')], + [ + 'host/example.stanford.edu', + ['netdb-root', 'example.stanford.edu'], + ['krb5', 'host/example.stanford.edu@stanford.edu'] + ], + '...and when netdb ACL already exists' + ); + is_deeply( + [default_owner('keytab', 'webauth/foo.stanford.edu')], + [ + 'host/foo.stanford.edu', + ['netdb-root', 'foo.stanford.edu'], + ['krb5', 'host/foo.stanford.edu@stanford.edu'] + ], + '...and when netdb-root ACL already exists' + ); -# Check for autocreation mappings for host-based file objects. -is_deeply( - [default_owner('file', 'ssl-key/example.stanford.edu')], - [ - 'host/example.stanford.edu', - ['netdb-root', 'example.stanford.edu'], - ['krb5', 'host/example.stanford.edu@stanford.edu'] - ], - 'Default owner for file ssl-key/example.stanford.edu', -); -is_deeply( - [default_owner('file', 'ssl-key/example.stanford.edu/mysql')], - [ - 'host/example.stanford.edu', - ['netdb-root', 'example.stanford.edu'], - ['krb5', 'host/example.stanford.edu@stanford.edu'] - ], - 'Default owner for file ssl-key/example.stanford.edu/mysql', -); + # Check for a type that isn't host-based. + is( + default_owner('keytab', 'service/foo'), + undef, + 'No default owner for service/foo' + ); -# Check for a file object that isn't host-based. -is_deeply( - [default_owner('file', 'config/its-idg/example/foo')], - ['group/its-idg', ['krb5', $ADMIN]], - 'Default owner for file config/its-idg/example/foo', -); + # Check for an unknown object type. + is( + default_owner('unknown', 'foo'), + undef, + 'No default owner for unknown type' + ); -# Check for legacy autocreation mappings for file objects. -for my $type (qw(htpasswd ssh-rsa ssh-dsa ssl-key tivoli-key)) { - my $name = "idg-example-$type"; + # Check for autocreation mappings for host-based file objects. is_deeply( - [default_owner('file', $name)], + [default_owner('file', 'ssl-key/example.stanford.edu')], [ 'host/example.stanford.edu', ['netdb-root', 'example.stanford.edu'], ['krb5', 'host/example.stanford.edu@stanford.edu'] ], - "Default owner for file $name", + 'Default owner for file ssl-key/example.stanford.edu', ); + is_deeply( + [default_owner('file', 'ssl-key/example.stanford.edu/mysql')], + [ + 'host/example.stanford.edu', + ['netdb-root', 'example.stanford.edu'], + ['krb5', 'host/example.stanford.edu@stanford.edu'] + ], + 'Default owner for file ssl-key/example.stanford.edu/mysql', + ); + + # Check for a file object that isn't host-based. + is_deeply( + [default_owner('file', 'config/its-idg/example/foo')], + ['group/its-idg', ['krb5', $ADMIN]], + 'Default owner for file config/its-idg/example/foo', + ); + + # Check for legacy autocreation mappings for file objects. + for my $type (qw(htpasswd ssh-rsa ssh-dsa ssl-key tivoli-key)) { + my $name = "idg-example-$type"; + is_deeply( + [default_owner('file', $name)], + [ + 'host/example.stanford.edu', + ['netdb-root', 'example.stanford.edu'], + ['krb5', 'host/example.stanford.edu@stanford.edu'] + ], + "Default owner for file $name", + ); + } + + # Clean up. + $setup->destroy; } -# Clean up. -$setup->destroy; END { unlink 'wallet-db'; } -- cgit v1.2.3 From 3d9d10ceecdd9100e36e3eef547464edf3f341c6 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Mon, 14 Dec 2015 22:38:46 -0800 Subject: Better error reporting on verifier failure during add When adding a new ACL, if creation of the verifier failed, we reported a pretty minimal error message claiming that the identifier was the problem. It can't possibly be the problem when the constructor fails. Report the actual failure more directly. --- perl/lib/Wallet/ACL.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/perl/lib/Wallet/ACL.pm b/perl/lib/Wallet/ACL.pm index f875185..862b88f 100644 --- a/perl/lib/Wallet/ACL.pm +++ b/perl/lib/Wallet/ACL.pm @@ -333,6 +333,10 @@ sub add { my $object = eval { $class->new ($identifier, $self->{schema}); }; + if ($@) { + $self->error ("cannot create ACL verifier: $@"); + return; + } unless ($object && $object->syntax_check ($identifier)) { $self->error ("invalid ACL identifier $identifier for $scheme"); return; -- cgit v1.2.3 From 4a777845b06b62a6deb1df5e69cc9b21226c3c2f Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Mon, 14 Dec 2015 22:39:43 -0800 Subject: Add documentation for Wallet::ACL::Nested --- perl/lib/Wallet/ACL/Nested.pm | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/perl/lib/Wallet/ACL/Nested.pm b/perl/lib/Wallet/ACL/Nested.pm index 945d881..07833f8 100644 --- a/perl/lib/Wallet/ACL/Nested.pm +++ b/perl/lib/Wallet/ACL/Nested.pm @@ -133,22 +133,25 @@ ACL Allbery verifier verifiers =head1 NAME -Wallet::ACL::Base - Generic parent class for wallet ACL verifiers +Wallet::ACL::Nested - Wallet ACL verifier to check another ACL =head1 SYNOPSIS - package Wallet::ACL::Simple - @ISA = qw(Wallet::ACL::Base); - sub check { - my ($self, $principal, $acl) = @_; - return ($principal eq $acl) ? 1 : 0; + my $verifier = Wallet::ACL::Nested->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::Base is the generic parent class for wallet ACL verifiers. -It provides default functions and behavior and all ACL verifiers should -inherit from it. It is not used directly. +Wallet::ACL::Nested checks whether the principal is permitted by another +named ACL and, if so, returns success. It is used to nest one ACL inside +another. =head1 METHODS @@ -156,26 +159,19 @@ inherit from it. It is not used directly. =item new() -Creates a new ACL verifier. The generic function provided here just -creates and blesses an object. +Creates a new ACL verifier. =item check(PRINCIPAL, ACL) -This method should always be overridden by child classes. The default -implementation just declines all access. +Returns true if PRINCIPAL is granted access according to the nested ACL, +specified by name. Returns false if it is not, and undef on error. =item error([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. - -For the convenience of child classes, this method can also be called with -one or more error strings. If so, those strings are concatenated -together, trailing newlines are removed, any text of the form S> at the end of the message is stripped off, and the result is -stored as the error. Only child classes should call this method with an -error string. +after an undef return from any other instance method. The returned errors +will generally come from the nested child ACL. =back @@ -188,6 +184,6 @@ available from L. =head1 AUTHOR -Russ Allbery +Jon Robertson =cut -- cgit v1.2.3 From d1b81776c05b858dca73c58a900c56d41f9c0e9b Mon Sep 17 00:00:00 2001 From: Bill MacAllister Date: Tue, 29 Dec 2015 20:03:02 +0000 Subject: Add error check for partially created AD keytabs The msktutil script does not always signal error conditions. This change implements a check that examines the output from msktutil and reports and error when the keytab creation fails to create the keytab but does create a computer entry in the directory. If an error is detected the directory entry is deleted leaving the directory in a clean state. Also, support has been added for output of debugging information to syslog using the AD_DEBUG configuration variable. Finally perltidy suggested changes were made to AD.pm. --- perl/lib/Wallet/Kadmin/AD.pm | 165 ++++++++++++++++++++++++++----------------- 1 file changed, 102 insertions(+), 63 deletions(-) diff --git a/perl/lib/Wallet/Kadmin/AD.pm b/perl/lib/Wallet/Kadmin/AD.pm index acdd144..30d4e9e 100644 --- a/perl/lib/Wallet/Kadmin/AD.pm +++ b/perl/lib/Wallet/Kadmin/AD.pm @@ -28,19 +28,31 @@ use IPC::Run qw( run timeout ); @ISA = qw(Wallet::Kadmin); -# 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'; +# 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.02'; ############################################################################## # kadmin Interaction ############################################################################## -# Make sure that principals are well-formed and don't contain characters that -# will cause us problems when talking to kadmin. Takes a principal and -# returns true if it's okay, false otherwise. Note that we do not permit -# realm information here. +# Send debugging output to syslog. + +sub ad_debug { + my ($self, $l, $m) = @_; + if (!$self->{SYSLOG}) { + openlog('wallet-server', 'ndelay,nofatal', 'local3'); + $self->{SYSLOG} = 1; + } + syslog($l, $m); + return; +} + +# Make sure that principals are well-formed and don't contain +# characters that will cause us problems when talking to kadmin. +# Takes a principal and returns true if it's okay, false otherwise. +# Note that we do not permit realm information here. sub valid_principal { my ($self, $principal) = @_; my $valid = 0; @@ -69,10 +81,9 @@ sub ldap_connect { my $ldap; eval { local $ENV{KRB5CCNAME} = $Wallet::Config::AD_CACHE; - my $sasl = Authen::SASL->new (mechanism => 'GSSAPI'); - $ldap - = Net::LDAP->new ($Wallet::Config::KEYTAB_HOST, onerror => 'die'); - my $mesg = eval { $ldap->bind (undef, sasl => $sasl) }; + my $sasl = Authen::SASL->new(mechanism => 'GSSAPI'); + $ldap = Net::LDAP->new($Wallet::Config::KEYTAB_HOST, onerror => 'die'); + my $mesg = eval { $ldap->bind(undef, sasl => $sasl) }; }; if ($@) { my $error = $@; @@ -84,6 +95,8 @@ sub ldap_connect { return $ldap; } +# Construct a base filter for searching Active Directory. + sub ldap_base_filter { my ($self, $principal) = @_; my $base; @@ -92,10 +105,10 @@ sub ldap_base_filter { my $fqdn = $1; my $host = $fqdn; $host =~ s/[.].*//xms; - $base = $Wallet::Config::AD_COMPUTER_DN; - $filter = "(samAccountName=${host}\$)"; + $base = $Wallet::Config::AD_COMPUTER_DN; + $filter = "(samAccountName=${host}\$)"; } elsif ($principal =~ m,^service/(\S+),xms) { - my $id = $1; + my $id = $1; $base = $Wallet::Config::AD_USER_DN; $filter = "(servicePrincipalName=service/${id})"; } @@ -109,27 +122,32 @@ sub get_ad_keytab { } # Run a msktutil command and capture the output. Returns the output, -# either as a list of lines or, in scalar context, as one string. -# Depending on the exit status of msktutil or on the eval trap to know -# when the msktutil command fails. The error string returned from the -# call to run frequently contains information about a success rather +# either as a list of lines or, in scalar context, as one string. +# Depending on the exit status of msktutil or on the eval trap to know +# when the msktutil command fails. The error string returned from the +# call to run frequently contains information about a success rather # that error output. sub msktutil { my ($self, $args_ref) = @_; - unless (defined ($Wallet::Config::KEYTAB_PRINCIPAL) - and defined ($Wallet::Config::KEYTAB_FILE) - and defined ($Wallet::Config::KEYTAB_REALM)) { + unless (defined($Wallet::Config::KEYTAB_PRINCIPAL) + and defined($Wallet::Config::KEYTAB_FILE) + and defined($Wallet::Config::KEYTAB_REALM)) + { die "keytab object implementation not configured\n"; } - unless (defined ($Wallet::Config::AD_SERVER) - and defined ($Wallet::Config::AD_COMPUTER_DN) - and defined ($Wallet::Config::AD_USER_DN) - and defined ($Wallet::Config::AD_KEYTAB_BUCKET)) { + unless (defined($Wallet::Config::AD_SERVER) + and defined($Wallet::Config::AD_COMPUTER_DN) + and defined($Wallet::Config::AD_USER_DN) + and defined($Wallet::Config::AD_KEYTAB_BUCKET)) + { die "Active Directory support not configured\n"; } my @args = @{$args_ref}; - my @cmd = ($Wallet::Config::AD_MSKTUTIL); + my @cmd = ($Wallet::Config::AD_MSKTUTIL); push @cmd, @args; + if ($Wallet::Config::AD_DEBUG) { + $self->ad_debug(LOG_DEBUG, join(' ', @cmd)); + } my $in; my $out; @@ -148,9 +166,7 @@ sub msktutil { } if ($err_no || $err_msg) { if ($err) { - $err_msg .= "ERROR: $err\n" - } - if ($Wallet::Config::AD_DEBUG) { + $err_msg .= "ERROR: $err\n"; $err_msg .= 'Problem command: ' . join(' ', @cmd) . "\n"; } die $err_msg; @@ -159,6 +175,9 @@ sub msktutil { $out .= "\n" . $err; } } + if ($Wallet::Config::AD_DEBUG) { + $self->ad_debug(LOG_DEBUG, $out); + } return $out; } @@ -171,28 +190,37 @@ sub ad_create_update { unlink $keytab or die "Problem deleting $keytab\n"; } my @cmd = ('--' . $action); - push @cmd, '--server', $Wallet::Config::AD_SERVER; - push @cmd, '--enctypes', '0x4'; - push @cmd, '--enctypes', '0x8'; - push @cmd, '--enctypes', '0x10'; - push @cmd, '--keytab', $keytab; - push @cmd, '--realm', $Wallet::Config::KEYTAB_REALM; + push @cmd, '--server', $Wallet::Config::AD_SERVER; + push @cmd, '--enctypes', '0x4'; + push @cmd, '--enctypes', '0x8'; + push @cmd, '--enctypes', '0x10'; + push @cmd, '--keytab', $keytab; + push @cmd, '--realm', $Wallet::Config::KEYTAB_REALM; + if ($principal =~ m,^host/(\S+),xms) { my $fqdn = $1; my $host = $fqdn; $host =~ s/[.].*//xms; push @cmd, '--dont-expire-password'; push @cmd, '--computer-name', $host; - push @cmd, '--upn', "host/$fqdn"; - push @cmd, '--hostname', $fqdn; + push @cmd, '--upn', "host/$fqdn"; + push @cmd, '--hostname', $fqdn; } elsif ($principal =~ m,^service/(\S+),xms) { my $service_id = $1; push @cmd, '--use-service-account'; - push @cmd, '--service', "service/$service_id"; + push @cmd, '--service', "service/$service_id"; push @cmd, '--account-name', "srv-${service_id}"; push @cmd, '--no-pac'; } - $self->msktutil(\@cmd); + my $out = $self->msktutil(\@cmd); + if ($out =~ /Error:\s+\S+\s+failed/xms) { + $self->ad_delete($principal); + my $m = "ERROR: problem creating keytab:\n" . $out; + $m .= 'INFO: the keytab used to by wallet probably has' + . " insufficient access to AD\n"; + die $m; + } + return $keytab; } @@ -211,7 +239,7 @@ sub fork_callback { # ldap query. sub exists { my ($self, $principal) = @_; - return unless $self->valid_principal ($principal); + return unless $self->valid_principal($principal); my $ldap = $self->ldap_connect(); my ($base, $filter) = $self->ldap_base_filter($principal); @@ -226,17 +254,16 @@ sub exists { attrs => \@attrs ); }; + if ($@) { my $error = $@; die "LDAP search error: $error\n"; } if ($result->code) { my $m; - if ($Wallet::Config::AD_DEBUG) { - $m .= "INFO base:$base filter:$filter scope:subtree\n"; - } + $m .= "INFO base:$base filter:$filter scope:subtree\n"; $m .= 'ERROR:' . $result->error . "\n"; - die $m + die $m; } if ($result->count > 1) { my $m = "ERROR: too many AD entries for this keytab\n"; @@ -256,16 +283,21 @@ sub exists { } # Call msktutil to Create a principal in Kerberos. Sets the error and -# returns undef on failure, and returns 1 on either success or the -# principal already existing. Note, this creates a keytab that is -# never used because it is not returned to the user. +# returns undef on failure, and returns 1 on either success or if the +# principal already exists. Note, this creates a keytab that is never +# used because it is not returned to the user. sub create { my ($self, $principal) = @_; - unless ($self->valid_principal ($principal)) { + unless ($self->valid_principal($principal)) { die "ERROR: invalid principal name $principal\n"; return; } - return 1 if $self->exists($principal); + if ($self->exists($principal)) { + if ($Wallet::Config::AD_DEBUG) { + $self->ad_debug(LOG_DEBUG, "$principal exists"); + } + return 1; + } my $file = $self->ad_create_update($principal, 'create'); if (-e $file) { unlink $file or die "Problem deleting $file\n"; @@ -277,7 +309,7 @@ sub create { # a keytab is marked unchanging and return that. sub keytab { my ($self, $principal) = @_; - unless ($self->valid_principal ($principal)) { + unless ($self->valid_principal($principal)) { die "ERROR: invalid principal name $principal\n"; return; } @@ -285,7 +317,7 @@ sub keytab { if (!-e $file) { die "ERROR: keytab file $file does not exist.\n"; } - return $self->read_keytab ($file); + return $self->read_keytab($file); } # Update a keytab for a principal. This action changes the AD @@ -293,7 +325,7 @@ sub keytab { # passed in are ignored. sub keytab_rekey { my ($self, $principal, @enctypes) = @_; - unless ($self->valid_principal ($principal)) { + unless ($self->valid_principal($principal)) { die "ERROR: invalid principal name: $principal\n"; return; } @@ -305,7 +337,7 @@ sub keytab_rekey { return; } my $file = $self->ad_create_update($principal, 'update'); - return $self->read_keytab ($file); + return $self->read_keytab($file); } # Delete a principal from Kerberos. Return true if successful, false @@ -315,16 +347,24 @@ sub keytab_rekey { # LDAP. sub destroy { my ($self, $principal) = @_; - unless ($self->valid_principal ($principal)) { - $self->error ("invalid principal name: $principal"); + unless ($self->valid_principal($principal)) { + $self->error("invalid principal name: $principal"); } - my $exists = $self->exists ($principal); + my $exists = $self->exists($principal); if (!defined $exists) { return; } elsif (not $exists) { return 1; } + return $self->ad_delete($principal); +} + +# Delete an entry from AD using LDAP. + +sub ad_delete { + my ($self, $principal) = @_; + my $k_type; my $k_id; my $dn; @@ -340,13 +380,11 @@ sub destroy { } } - my $ldap = $self->ldap_connect(); + my $ldap = $self->ldap_connect(); my $msgid = $ldap->delete($dn); if ($msgid->code) { my $m; - if ($Wallet::Config::AD_DEBUG) { - $m .= "ERROR: Problem deleting $dn\n"; - } + $m .= "ERROR: Problem deleting $dn\n"; $m .= $msgid->error; die $m; } @@ -358,11 +396,12 @@ sub destroy { # kadmin directly. sub new { my ($class) = @_; - unless (defined ($Wallet::Config::KEYTAB_TMP)) { + unless (defined($Wallet::Config::KEYTAB_TMP)) { die "KEYTAB_TMP configuration variable not set\n"; } my $self = {}; - bless ($self, $class); + $self->{SYSLOG} = undef; + bless($self, $class); return $self; } -- cgit v1.2.3 From 2a03ce35be9b900cc0fd5f305dec54ebcf3fed5a Mon Sep 17 00:00:00 2001 From: Bill MacAllister Date: Tue, 29 Dec 2015 13:57:37 -0800 Subject: Add in missing use statement for Sys::Syslog --- perl/lib/Wallet/Kadmin/AD.pm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/perl/lib/Wallet/Kadmin/AD.pm b/perl/lib/Wallet/Kadmin/AD.pm index 30d4e9e..4efc643 100644 --- a/perl/lib/Wallet/Kadmin/AD.pm +++ b/perl/lib/Wallet/Kadmin/AD.pm @@ -25,6 +25,7 @@ use Wallet::Kadmin (); use Authen::SASL (); use Net::LDAP; use IPC::Run qw( run timeout ); +use Sys::Syslog qw( :standard :macros ); @ISA = qw(Wallet::Kadmin); @@ -146,7 +147,7 @@ sub msktutil { my @cmd = ($Wallet::Config::AD_MSKTUTIL); push @cmd, @args; if ($Wallet::Config::AD_DEBUG) { - $self->ad_debug(LOG_DEBUG, join(' ', @cmd)); + $self->ad_debug('debug', join(' ', @cmd)); } my $in; @@ -176,7 +177,7 @@ sub msktutil { } } if ($Wallet::Config::AD_DEBUG) { - $self->ad_debug(LOG_DEBUG, $out); + $self->ad_debug('debug', $out); } return $out; } @@ -294,7 +295,7 @@ sub create { } if ($self->exists($principal)) { if ($Wallet::Config::AD_DEBUG) { - $self->ad_debug(LOG_DEBUG, "$principal exists"); + $self->ad_debug('debug', "$principal exists"); } return 1; } -- cgit v1.2.3 From daca82f520f51834812fab7cf15411ae54f46d8f Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 14:58:20 -0800 Subject: Document the new ACL schemes in docs/design-acl Change-Id: Idd2e1038fc02dd51aab9a9ffdd5b3400db2b106f --- docs/design-acl | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/design-acl b/docs/design-acl index 424b3c6..32ac508 100644 --- a/docs/design-acl +++ b/docs/design-acl @@ -13,7 +13,7 @@ Introduction Syntax An ACL entry in the wallet consists of two pieces of data, a - and an . is one or more characters in the set + and an . is one or more characters in the set [a-z0-9-] that identifies the ACL backend to use when interpreting this ACL. is zero or more characters including all printable ASCII characters except whitespace. Only the implementation @@ -55,6 +55,24 @@ ACL Schemes The is a fully-qualified Kerberos principal. Access is granted if the principal of the client matches . + ldap-attr + + is an an attribute followed by an equal sign and a value. + If the LDAP entry corresponding to the given principal contains the + attribute and value specified by , access is granted. + + ldap-attr-root + + This is almost identical to netdb except that the user must be in the + form of a root instance (/root) and the "/root" portion is + stripped before checking the NetDB roles. + + nested + + is the name of another ACL, and access is granted if it + is granted by that ACL. This can be used to organize multiple ACLs + into a group and apply their union to an object. + netdb is the name of a system. Access is granted if the user @@ -67,13 +85,6 @@ ACL Schemes form of a root instance (/root) and the "/root" portion is stripped before checking the NetDB roles. - ldap-entitlement - - (Not yet implemented.) is an entitlement. If the - entitlement attribute of the LDAP entry corresponding to the given - principal contains the entitlement specified in , access - is granted. - pts (Not yet implemented.) is the name of an AFS PTS group. @@ -82,6 +93,7 @@ ACL Schemes License + Copyright 2016 Russ Allbery Copyright 2006, 2007, 2008, 2013 The Board of Trustees of the Leland Stanford Junior University -- cgit v1.2.3 From d223a3eafc5eb8fe58d664994669c908b6c01346 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 15:05:26 -0800 Subject: Document the acls nested report in the man page Change-Id: Ib077a196ee5389d7ec6d90fcf411cae0a81e071d --- server/wallet-report | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/server/wallet-report b/server/wallet-report index 4719a8a..e38ef74 100755 --- a/server/wallet-report +++ b/server/wallet-report @@ -207,6 +207,10 @@ Returns all ACLs containing an entry with given scheme and identifier. The scheme must be an exact match, but the string will match any identifier containing that string. +=item acls nested + +Returns all ACLs that contain this ACL as a nested entry. + =item acls unused Returns all ACLs that are not referenced by any of the objects in the @@ -310,7 +314,9 @@ Russ Allbery =head1 COPYRIGHT AND LICENSE -Copyright 2008, 2009, 2010, 2013 The Board of Trustees of the Leland +Copyright 2016 Russ Allbery + +Copyright 2008, 2009, 2010, 2013, 2015 The Board of Trustees of the Leland Stanford Junior University Permission is hereby granted, free of charge, to any person obtaining a -- cgit v1.2.3 From f20e65a6f0efdcc10a663ecac3833645c94484ef Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 15:09:42 -0800 Subject: Add POD documentation for objects host report Change-Id: I710de6a1df01ecd9aebd202288a9efb434c09054 --- server/wallet-report | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/wallet-report b/server/wallet-report index e38ef74..3284ba3 100755 --- a/server/wallet-report +++ b/server/wallet-report @@ -278,6 +278,12 @@ those where that ACL has any other, more limited permissions. Returns all objects which have the given flag set. +=item objects host + +Returns all objects that belong to the given host. This requires adding +local configuration to identify objects that belong to a given host. See +L for more information. + =item objects owner Returns all objects owned by the given ACL name or ID. -- cgit v1.2.3 From 7187257790441c29617406217446fc358b1c336c Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 15:11:59 -0800 Subject: Add POD documentation of schemes and types reports Change-Id: I9f8f986952510f6b2d326ccaab4bb7006a033b9d --- server/wallet-report | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/wallet-report b/server/wallet-report index 3284ba3..d598aeb 100755 --- a/server/wallet-report +++ b/server/wallet-report @@ -312,6 +312,14 @@ The output will be one line per ACL line in the form: with duplicates suppressed. +=item schemes + +Returns a list of all registered ACL schemes. + +=item types + +Returns a list of all registered object types. + =back =head1 AUTHOR -- cgit v1.2.3 From 45fa535256e8272511d1f6769069536248b565dd Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 15:16:19 -0800 Subject: Remove Stanford-internal JIRA identifiers from TODO Change-Id: I97f466b2221b71ffcc60dd4f1b48e5986496ff46 --- TODO | 375 +++++++++++++++++++++++++++++++++---------------------------------- 1 file changed, 182 insertions(+), 193 deletions(-) diff --git a/TODO b/TODO index 4babbca..f235a37 100644 --- a/TODO +++ b/TODO @@ -2,290 +2,279 @@ Client: - * KERB-94: Handle duplicate kvnos in a newly returned keytab and an - existing keytab (such as when downloading an unchanging keytab and - merging it into an existing one) in some reasonable fashion. + * Handle duplicate kvnos in a newly returned keytab and an existing + keytab (such as when downloading an unchanging keytab and merging it + into an existing one) in some reasonable fashion. - * KERB-90: Support removing old kvnos from a merged keytab (similar to - kadmin ktremove old). + * Support removing old kvnos from a merged keytab (similar to kadmin + ktremove old). - * KERB-88: When reading configuration from krb5.conf, we should first try - to determine our principal from any existing Kerberos ticket cache - (after obtaining tickets if -u was given) and extract the realm from - that principal, using it as the default realm when reading - configuration information. + * When reading configuration from krb5.conf, we should first try to + determine our principal from any existing Kerberos ticket cache (after + obtaining tickets if -u was given) and extract the realm from that + principal, using it as the default realm when reading configuration + information. - * KERB-89: Add readline support to the wallet client to make it easier to - issue multiple commands. + * Add readline support to the wallet client to make it easier to issue + multiple commands. - * KERB-115: Support authenticating with a keytab. + * Support authenticating with a keytab. - * KERB-97: When obtaining tickets in the wallet client with -u, directly - obtain the service ticket we're going to use for remctl. + * When obtaining tickets in the wallet client with -u, directly obtain + the service ticket we're going to use for remctl. - * KERB-95: Provide a way to refresh a file object if and only if what's - stored on the server is different than what's on disk. This will - require server support as well for returning the checksum of a file. + * Provide a way to refresh a file object if and only if what's stored on + the server is different than what's on disk. This will require server + support as well for returning the checksum of a file. - * KERB-104: Incorporate the wallet-rekey-periodic script (currently in - contrib) into the package and teach it how to ignore foreign - credentials. + * Incorporate the wallet-rekey-periodic script (currently in contrib) + into the package and teach it how to ignore foreign credentials. Server Interface: - * KERB-126: Provide a way to get history for deleted objects and ACLs. + * Provide a way to get history for deleted objects and ACLs. - * KERB-66: Provide an interface to mass-change all instances of one ACL - to another. + * Provide an interface to mass-change all instances of one ACL to + another. - * KERB-96: Add help functions to wallet-backend, wallet-report, and - wallet-admin listing the commands. + * Add help functions to wallet-backend, wallet-report, and wallet-admin + listing the commands. - * KERB-52: Catch exceptions on object creation in wallet-backend so that - we can log those as well. + * Catch exceptions on object creation in wallet-backend so that we can + log those as well. - * KERB-114: Provide a way to list all objects for which the connecting - user has ACLs. + * Provide a way to list all objects for which the connecting user has + ACLs. - * KERB-101: Support limiting returned history information by timestamp. + * Support limiting returned history information by timestamp. - * KERB-128: Provide a REST implementation of the wallet server. + * Provide a REST implementation of the wallet server. - * KERB-79: Provide a CGI implementation of the wallet server. + * Provide a CGI implementation of the wallet server. - * KERB-111: Support setting flags and attributes on autocreate. In - general, work out a Wallet::Object::Template Perl object that I can - return that specifies things other than just the ACL. + * Support setting flags and attributes on autocreate. In general, work + out a Wallet::Object::Template Perl object that I can return that + specifies things other than just the ACL. - * KERB-93: Remove the hard-coded ADMIN ACL in the server with something - more configurable, perhaps a global ACL table or something. + * Remove the hard-coded ADMIN ACL in the server with something more + configurable, perhaps a global ACL table or something. - * KERB-68: Support leap-of-faith keying of systems by registering an - object for one-time download (ideally from a specific IP address) and - then allowing that object to be downloaded anonymously from that IP. - Relies on support for Kerberos anonymous authentication. + * Support leap-of-faith keying of systems by registering an object for + one-time download (ideally from a specific IP address) and then + allowing that object to be downloaded anonymously from that IP. Relies + on support for Kerberos anonymous authentication. - * KERB-84: Split "get" and "update" in semantics, and only do keytab - rekeying on update. "get" would not be permitted unless the keytab was - flagged as unchanging, and update would still change even an unchanging - keytab (maybe). Or, alternately, maybe we allow get of any keytab? - Requires more thought. + * Split "get" and "update" in semantics, and only do keytab rekeying on + update. "get" would not be permitted unless the keytab was flagged as + unchanging, and update would still change even an unchanging keytab + (maybe). Or, alternately, maybe we allow get of any keytab? Requires + more thought. - * KERB-118: Add command to list available types and schemes. + * Add command to list available types and schemes. - * KERB-75: Add a mechanism to automate owner updates based on - default_owner. + * Add a mechanism to automate owner updates based on default_owner. - * KERB-64: Partially merge create and autocreate. create and autocreate - should do the same thing provided there is an autocreation - configuration available. If not, autocreate should fail and create - should fall back on checking for ADMIN privileges. + * Partially merge create and autocreate. create and autocreate should do + the same thing provided there is an autocreation configuration + available. If not, autocreate should fail and create should fall back + on checking for ADMIN privileges. - * KERB-116: Support file object renaming. + * Support file object renaming. - * KERB-131: Rewrite server backends to use Net::Remctl::Backend. + * Rewrite server backends to use Net::Remctl::Backend. - * KERB-132: Merge the Wallet::Logger support written by Commerzbank AG: - create a new class that handles logging, probably based on - Log::Log4perl, and add logging points to all of the core classes. + * Merge the Wallet::Logger support written by Commerzbank AG: create a + new class that handles logging, probably based on Log::Log4perl, and + add logging points to all of the core classes. - * KERB-133: Support an authorization hook to determine whether or not to - permit autocreate. One requested example feature is to limit - autocreate of keytab objects to certain hosts involved in deployment. - It should be possible to write a hook that takes the information about - what object is being autocreated and can accept or decline. + * Support an authorization hook to determine whether or not to permit + autocreate. One requested example feature is to limit autocreate of + keytab objects to certain hosts involved in deployment. It should be + possible to write a hook that takes the information about what object + is being autocreated and can accept or decline. ACLs: - * KERB-119: Error messages from ACL operations should refer to the ACLs - by name instead of by ID. + * Error messages from ACL operations should refer to the ACLs by name + instead of by ID. - * KERB-121: Write the PTS ACL verifier. + * Write the PTS ACL verifier. - * KERB-123: Rename Wallet::ACL::* to Wallet::Verifier::*. Add - Wallet::ACL as a generic interface with Wallet::ACL::Database and - Wallet::ACL::List implementations (or some similar name) so that we can - create and check an ACL without having to write it into the database. - Redo default ACL creation using that functionality. + * Rename Wallet::ACL::* to Wallet::Verifier::*. Add Wallet::ACL as a + generic interface with Wallet::ACL::Database and Wallet::ACL::List + implementations (or some similar name) so that we can create and check + an ACL without having to write it into the database. Redo default ACL + creation using that functionality. - * KERB-67: Pass a reference to the object for which the ACL is - interpreted to the ACL API so that ACL APIs can make more complex - decisions. + * Pass a reference to the object for which the ACL is interpreted to the + ACL API so that ACL APIs can make more complex decisions. - * KERB-109: A group-in-groups ACL schema. + * A group-in-groups ACL schema. - * KERB-113: Provide an API for verifiers to syntax-check the values - before an ACL is set and implement syntax checking for the krb5 and - ldap-attr verifiers. + * Provide an API for verifiers to syntax-check the values before an ACL + is set and implement syntax checking for the krb5 and ldap-attr + verifiers. - * KERB-60: Investigate how best to support client authentication using - anonymous PKINIT for things like initial system keying. + * Investigate how best to support client authentication using anonymous + PKINIT for things like initial system keying. - * KERB-72: Generalize the current NetDB ACL type to allow a generic - remctl query for whether a particular user is authorized to create - host-based objects for a particular host. + * Generalize the current NetDB ACL type to allow a generic remctl query + for whether a particular user is authorized to create host-based + objects for a particular host. - * KERB-78: Add ldap-group ACL scheme. + * Add ldap-group ACL scheme. - * KERB-63: Provide a root-instance version of the ldap-attr (and possibly - the ldap-group) ACL schemes. + * Provide a root-instance version of the ldap-attr (and possibly the + ldap-group) ACL schemes. - * KERB-86: Add a comment field to ACLs. + * Add a comment field to ACLs. Database: - * KERB-55: Fix case-insensitivity bug in unique keys with MySQL for - objects. When creating an http/ principal when an HTTP/ - principal already existed, MySQL rejected the row entry as a duplicate. - The name should be case-sensitive. + * Fix case-insensitivity bug in unique keys with MySQL for objects. When + creating an http/ principal when an HTTP/ principal already + existed, MySQL rejected the row entry as a duplicate. The name should + be case-sensitive. - * KERB-103: On upgrades, support adding new object types and ACL - verifiers to the class tables. + * On upgrades, support adding new object types and ACL verifiers to the + class tables. Objects: - * KERB-120: Check whether we can just drop the realm restriction on - keytabs and allow the name to contain the realm if the Kerberos type is - Heimdal. + * Check whether we can just drop the realm restriction on keytabs and + allow the name to contain the realm if the Kerberos type is Heimdal. - * KERB-59: Use the Perl Authen::Krb5::Admin module instead of rolling our - own kadmin code with Expect now that MIT Kerberos has made the kadmin - API public. + * Use the Perl Authen::Krb5::Admin module instead of rolling our own + kadmin code with Expect now that MIT Kerberos has made the kadmin API + public. - * KERB-85: Implement an ssh keypair wallet object. The server can run - ssh-keygen to generate a public/private key pair and return both to the - client, which would split them apart. Used primarily for host keys. - May need a side table to store key types, or a naming convention. + * Implement an ssh keypair wallet object. The server can run ssh-keygen + to generate a public/private key pair and return both to the client, + which would split them apart. Used primarily for host keys. May need + a side table to store key types, or a naming convention. - * KERB-124: Implement an X.509 certificate object. I expect this would - store the public and private key as a single file in the same format - that Apache can read for combined public and private keys. There were - requests for storing the CSR, but I don't see why you'd want to do - that. Start with store support. The file code is mostly sufficient - here, but it would be nice to automatically support object expiration - based on the expiration time for the certificate. + * Implement an X.509 certificate object. I expect this would store the + public and private key as a single file in the same format that Apache + can read for combined public and private keys. There were requests for + storing the CSR, but I don't see why you'd want to do that. Start with + store support. The file code is mostly sufficient here, but it would + be nice to automatically support object expiration based on the + expiration time for the certificate. - * KERB-106: Implement an X.509 CA so that you can get certificate objects - without storing them first. Need to resolve naming conventions if you - want to run multiple CAs on the same wallet server (but why?). Should - this be a different type than stored certificates? Consider using - hxtool as the underlying CA mechanism. + * Implement an X.509 CA so that you can get certificate objects without + storing them first. Need to resolve naming conventions if you want to + run multiple CAs on the same wallet server (but why?). Should this be + a different type than stored certificates? Consider using hxtool as + the underlying CA mechanism. - * KERB-77: Support returning the checksum of a file object stored in - wallet so that one can determine whether the version stored on disk is - identical. + * Support returning the checksum of a file object stored in wallet so + that one can determine whether the version stored on disk is identical. - * KERB-108: Implement new password wallet object, which is like file - except that it generates a random, strong password when retrieved the - first time without being stored. + * Implement new password wallet object, which is like file except that it + generates a random, strong password when retrieved the first time + without being stored. - * KERB-71: Support interrogating objects to find all host-based objects - for a particular host, allowing cleanup of all of those host's objects - after retiring the host. + * Support interrogating objects to find all host-based objects for a + particular host, allowing cleanup of all of those host's objects after + retiring the host. - * KERB-127: Support setting the disallow-svr flag on created principals. - In general, support setting arbitrary principal flags. + * Support setting the disallow-svr flag on created principals. In + general, support setting arbitrary principal flags. Reports: - * KERB-117: Add audit for references to unknown ACLs, possibly introduced - by previous versions before ACL deletion was checked with database + * Add audit for references to unknown ACLs, possibly introduced by + previous versions before ACL deletion was checked with database backends that don't do referential integrity. - * KERB-105: Add report for all objects that have never been stored. + * Add report for all objects that have never been stored. - * KERB-122: For objects tied to hostnames, report on objects referring to - hosts which do not exist. For the initial pass, this is probably only - keytab objects with names containing a slash where the part after the - slash looks like a hostname. This may need some configuration help. + * For objects tied to hostnames, report on objects referring to hosts + which do not exist. For the initial pass, this is probably only keytab + objects with names containing a slash where the part after the slash + looks like a hostname. This may need some configuration help. - * KERB-102: Make contrib/wallet-summary generic and include it in - wallet-report, with additional configuration in Wallet::Config. - Enhance it to report on any sort of object, not just on keytabs, and to - give numbers on downloaded versus not downloaded objects. + * Make contrib/wallet-summary generic and include it in wallet-report, + with additional configuration in Wallet::Config. Enhance it to report + on any sort of object, not just on keytabs, and to give numbers on + downloaded versus not downloaded objects. - * KERB-69: Write a tool to mail the owners of wallet objects, taking the - list of objects and the mail message to send as inputs. This could - possibly use the notification service, although a version that sends - mail directly would be useful external to Stanford. + * Write a tool to mail the owners of wallet objects, taking the list of + objects and the mail message to send as inputs. This could possibly + use the notification service, although a version that sends mail + directly would be useful external to Stanford. - * KERB-134: Merge the Commerzbank AG work to dump all the object history, - applying various search criteria to it, or clear parts of the object - history. + * Merge the Commerzbank AG work to dump all the object history, applying + various search criteria to it, or clear parts of the object history. Administrative Interface: - * KERB-80: Add a function to wallet-admin to purge expired entries. - Possibly also check expiration before allowing anyone to get or store - objects. + * Add a function to wallet-admin to purge expired entries. Possibly also + check expiration before allowing anyone to get or store objects. - * KERB-58: Add a function or separate script to automate removal of - DNS-based objects for which the hosts no longer exist. Will need to - support a site-specific callout to determine whether the host exists. + * Add a function or separate script to automate removal of DNS-based + objects for which the hosts no longer exist. Will need to support a + site-specific callout to determine whether the host exists. - * KERB-54: Database creation appears not to work without the SQL files, - but it's supposed to work directly from the classes. Double-check - this. + * Database creation appears not to work without the SQL files, but it's + supposed to work directly from the classes. Double-check this. Documentation: - * KERB-82: Write a conventions document for ACL naming, object naming, - and similar issues. + * Write a conventions document for ACL naming, object naming, and similar + issues. - * KERB-125: Write a future design and roadmap document to collect notes - about how unimplemented features should be handled. + * Write a future design and roadmap document to collect notes about how + unimplemented features should be handled. - * KERB-65: Document using the wallet system over something other than - remctl. + * Document using the wallet system over something other than remctl. - * KERB-112: Document all diagnostics for all wallet APIs. + * Document all diagnostics for all wallet APIs. - * KERB-135: Document configuration with an Oracle database. + * Document configuration with an Oracle database. Code Style and Cleanup: - * KERB-98: There is a lot of duplicate code in wallet-backend. Convert - that to use some sort of data-driven model with argument count and - flags so that the method calls can be written only once. Convert - wallet-admin to use the same code. + * There is a lot of duplicate code in wallet-backend. Convert that to + use some sort of data-driven model with argument count and flags so + that the method calls can be written only once. Convert wallet-admin + to use the same code. - * KERB-100: There's a lot of code duplication in the dispatch functions - in the Wallet::Server class. Find a way to rewrite that so that the - dispatch doesn't duplicate the same code patterns. + * There's a lot of code duplication in the dispatch functions in the + Wallet::Server class. Find a way to rewrite that so that the dispatch + doesn't duplicate the same code patterns. - * KERB-73: The wallet-backend and wallet documentation share the COMMANDS - section. Work out some means to assemble the documentation without - duplicating content. + * The wallet-backend and wallet documentation share the COMMANDS section. + Work out some means to assemble the documentation without duplicating + content. - * KERB-110: The Wallet::Config class is very ugly and could use some - better internal API to reference the variables in it. + * The Wallet::Config class is very ugly and could use some better + internal API to reference the variables in it. - * KERB-76: Consider using Class::Accessor to get rid of the scaffolding - code to access object data. Alternately, consider using Moose. + * Consider using Class::Accessor to get rid of the scaffolding code to + access object data. Alternately, consider using Moose. - * KERB-130: Rewrite the error handling to use exceptions instead of the - C-style return value and separate error call. + * Rewrite the error handling to use exceptions instead of the C-style + return value and separate error call. Test Suite: - * KERB-92: The ldap-attr verifier test case is awful and completely - specific to people with admin access to the Stanford LDAP tree. Write - a real test. + * The ldap-attr verifier test case is awful and completely specific to + people with admin access to the Stanford LDAP tree. Write a real test. - * KERB-87: Rename the tests to use a subdirectory organization. + * Rename the tests to use a subdirectory organization. - * KERB-61: Add POD coverage testing using Test::POD::Coverage for the - server modules. + * Add POD coverage testing using Test::POD::Coverage for the server + modules. - * KERB-91: Rewrite the client test suite to use Perl and to make better - use of shared code so that it can be broken into function components. + * Rewrite the client test suite to use Perl and to make better use of + shared code so that it can be broken into function components. - * KERB-74: Refactor the test suite for the wallet backend to try to - reduce the duplicated code. Using a real mock infrastructure should - make this test suite much easier to write. + * Refactor the test suite for the wallet backend to try to reduce the + duplicated code. Using a real mock infrastructure should make this + test suite much easier to write. - * KERB-81: Pull common test suite code into a Perl library that can be - reused. + * Pull common test suite code into a Perl library that can be reused. - * KERB-99: Write a test suite to scan all wallet code looking for - diagnostics that aren't in the documentation and warn about them. + * Write a test suite to scan all wallet code looking for diagnostics that + aren't in the documentation and warn about them. -- cgit v1.2.3 From 3b8a786a0e4d77bfc63cc8d4373972ef578115ea Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 15:21:30 -0800 Subject: Flesh out NEWS and update TODO for merged changes Change-Id: I714a6298c36e6fd7eca6ee3acb01637a96773647 --- NEWS | 35 +++++++++++++++++++++++++++++++---- TODO | 28 +++++----------------------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/NEWS b/NEWS index 3afbd2c..48ab131 100644 --- a/NEWS +++ b/NEWS @@ -2,10 +2,27 @@ wallet 1.3 (unreleased) - A new object type, password (Wallet::Object::Password), is now - supported. This is a subclass of the file object that will randomly + A new ACL type, nested (Wallet::ACL::Nested), is now supported. The + identifier of this ACL names another ACL, and access is granted if + that ACL would grant access. This lets one combine multiple other + ACLs and apply the union to an object. 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 + must end in /root, and the LDAP entry checked will be for the same + principal without the /root component. This is useful for limiting + access to certain privileged objects to Kerberos root instances. To + enable this ACL type for an existing wallet database, use wallet-admin + to register the new verifier. + + A new object type, password (Wallet::Object::Password), is now + supported. This is a subclass of the file object that will randomly generate content for the object if you do a get before storing any - content inside it. + content inside it. To enable this object type for an existing + database, use wallet-admin to register the new object. Add a new command to wallet-backend, update. This will update the contents of an object before running a get on it, and is only valid @@ -17,7 +34,8 @@ wallet 1.3 (unreleased) warrants. Add an acl replace command, to change all objects owned by one ACL to - be owned by another. + be owned by another. This currently only handles owner, not any of + the more specific ACLs. All ACL operations now refer to the ACL by name rather than ID. @@ -25,11 +43,20 @@ wallet 1.3 (unreleased) help for the existing unused report that implied it showed unstored as well as unused. + Add reports that list all object types (types) and all ACL schemes + (schemes) currently registered in the wallet database. + + Add a report of all ACLs that nest a given ACL. This requires some + additional local configuration (and probably some code). See + Wallet::Config for more information. + Took contributions from Commerzbank AG to improve wallet history. Add a command to dump all object history for searching on to wallet-report, and add a new script for more detailed object history operations to the contrib directory. + Displays of ACLs and ACL entries are now sorted correctly. + wallet 1.2 (2014-12-08) The duo object type has been split into several sub-types, each for a diff --git a/TODO b/TODO index f235a37..24514d8 100644 --- a/TODO +++ b/TODO @@ -35,10 +35,11 @@ Server Interface: * Provide a way to get history for deleted objects and ACLs. * Provide an interface to mass-change all instances of one ACL to - another. + another. (Owner changes are currently supported, but not the other + ACLs.) - * Add help functions to wallet-backend, wallet-report, and wallet-admin - listing the commands. + * Add help functions to wallet-backend and wallet-admin listing the + commands. * Catch exceptions on object creation in wallet-backend so that we can log those as well. @@ -70,8 +71,6 @@ Server Interface: (maybe). Or, alternately, maybe we allow get of any keytab? Requires more thought. - * Add command to list available types and schemes. - * Add a mechanism to automate owner updates based on default_owner. * Partially merge create and autocreate. create and autocreate should do @@ -79,8 +78,6 @@ Server Interface: available. If not, autocreate should fail and create should fall back on checking for ADMIN privileges. - * Support file object renaming. - * Rewrite server backends to use Net::Remctl::Backend. * Merge the Wallet::Logger support written by Commerzbank AG: create a @@ -109,8 +106,6 @@ ACLs: * Pass a reference to the object for which the ACL is interpreted to the ACL API so that ACL APIs can make more complex decisions. - * A group-in-groups ACL schema. - * Provide an API for verifiers to syntax-check the values before an ACL is set and implement syntax checking for the krb5 and ldap-attr verifiers. @@ -122,10 +117,7 @@ ACLs: for whether a particular user is authorized to create host-based objects for a particular host. - * Add ldap-group ACL scheme. - - * Provide a root-instance version of the ldap-attr (and possibly the - ldap-group) ACL schemes. + * Add ldap-group ACL scheme (and possibly a root-only version). * Add a comment field to ACLs. @@ -170,14 +162,6 @@ Objects: * Support returning the checksum of a file object stored in wallet so that one can determine whether the version stored on disk is identical. - * Implement new password wallet object, which is like file except that it - generates a random, strong password when retrieved the first time - without being stored. - - * Support interrogating objects to find all host-based objects for a - particular host, allowing cleanup of all of those host's objects after - retiring the host. - * Support setting the disallow-svr flag on created principals. In general, support setting arbitrary principal flags. @@ -187,8 +171,6 @@ Reports: previous versions before ACL deletion was checked with database backends that don't do referential integrity. - * Add report for all objects that have never been stored. - * For objects tied to hostnames, report on objects referring to hosts which do not exist. For the initial pass, this is probably only keytab objects with names containing a slash where the part after the slash -- cgit v1.2.3 From 4c88bc4d11188499909fed9def7fad9c16afb84e Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 15:53:35 -0800 Subject: Document requirements for the password object Change-Id: Ibff0602d5ff8bf4c625f3970130cce4c8c02720e --- README | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README b/README index f21b9ad..d423983 100644 --- a/README +++ b/README @@ -111,6 +111,9 @@ REQUIREMENTS The Duo integration object support in the wallet server requires the Net::Duo Perl module. + The password object support in the wallet server requires the + Crypt::GeneratePassword Perl module. + To support the LDAP attribute ACL verifier, the Authen::SASL and Net::LDAP Perl modules must be installed on the server. This verifier only works with LDAP servers that support GSS-API binds. -- cgit v1.2.3 From e5a4c4a02d3e14c0d48fa82204c9bd44cb23a7f9 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 15:53:48 -0800 Subject: Enable Travis-CI integration testing Change-Id: I0248c2bd36c063526c64e22c4d30f39464f69028 --- .travis.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2ae23b7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: c +compiler: + - gcc + +before_install: + - sudo apt-get update -qq + - sudo apt-get install libauthen-sasl-perl libcrypt-generatepassword-perl libdatetime-perl libdatetime-format-sqlite-perl libdbd-sqlite3-perl libdbi-perl libdbix-class-perl libheimdal-kadm5-perl libkrb5-dev libnet-dns-perl libnet-duo-perl libnet-ldap-perl libremctl-dev libsql-translator-perl libtest-minimumversion-perl libtest-pod-perl libtest-strict-perl libtimedate-perl libwebauth-perl perl sqlite3 +env: AUTHOR_TESTING=1 +script: ./autogen && ./configure && make warnings && make check + +branches: + only: + - master -- cgit v1.2.3 From 6fa385f391d04e8439e74eb2668944e4b957e36a Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 16:46:06 -0800 Subject: No libnet-duo-perl in the version Travis-CI uses Change-Id: I7e49c687e892e012051056bc9324d7a8a5b36d07 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2ae23b7..2b65d9e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ compiler: before_install: - sudo apt-get update -qq - - sudo apt-get install libauthen-sasl-perl libcrypt-generatepassword-perl libdatetime-perl libdatetime-format-sqlite-perl libdbd-sqlite3-perl libdbi-perl libdbix-class-perl libheimdal-kadm5-perl libkrb5-dev libnet-dns-perl libnet-duo-perl libnet-ldap-perl libremctl-dev libsql-translator-perl libtest-minimumversion-perl libtest-pod-perl libtest-strict-perl libtimedate-perl libwebauth-perl perl sqlite3 + - sudo apt-get install libauthen-sasl-perl libcrypt-generatepassword-perl libdatetime-perl libdatetime-format-sqlite-perl libdbd-sqlite3-perl libdbi-perl libdbix-class-perl libheimdal-kadm5-perl libkrb5-dev libnet-dns-perl libnet-ldap-perl libremctl-dev libsql-translator-perl libtest-minimumversion-perl libtest-pod-perl libtest-strict-perl libtimedate-perl libwebauth-perl perl sqlite3 env: AUTHOR_TESTING=1 script: ./autogen && ./configure && make warnings && make check -- cgit v1.2.3 From 6d6718c3c19180adbb5b17985c539d2a4a693f8a Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 16:54:54 -0800 Subject: Add stopwords for some additional spelling issues Change-Id: If63ea5829252fda13b68d031fb9f48c93b71697a --- contrib/wallet-contacts | 2 +- server/wallet-report | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/wallet-contacts b/contrib/wallet-contacts index ce16ab1..c2f3bf1 100755 --- a/contrib/wallet-contacts +++ b/contrib/wallet-contacts @@ -174,7 +174,7 @@ if ($mail) { ############################################################################## =for stopwords -ACL NetDB SQL hostname lookup swhois whois Allbery +ACL NetDB SQL hostname lookup swhois whois Allbery -dryrun =head1 NAME diff --git a/server/wallet-report b/server/wallet-report index d598aeb..22637ca 100755 --- a/server/wallet-report +++ b/server/wallet-report @@ -135,7 +135,7 @@ wallet-report - Wallet server reporting interface =for stopwords metadata ACL hostname backend acl acls wildcard SQL Allbery remctl -MERCHANTABILITY NONINFRINGEMENT sublicense +MERCHANTABILITY NONINFRINGEMENT sublicense unstored =head1 SYNOPSIS -- cgit v1.2.3 From 7aab166369a2d8bd3b15715b4a5c1c18757f843d Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 16:56:57 -0800 Subject: Allow contrib/wallet-contacts to work on Perl 5.008 I'll probably bump this later, but for now that's the minimum supported Perl version for wallet. Change-Id: I97e36f850dcb3dcd3a78daf34d8a35bf597bdb43 --- contrib/wallet-contacts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/wallet-contacts b/contrib/wallet-contacts index c2f3bf1..0c72c9c 100755 --- a/contrib/wallet-contacts +++ b/contrib/wallet-contacts @@ -12,7 +12,7 @@ # Modules and declarations ############################################################################## -use 5.010; +use 5.008; use autodie; use strict; use warnings; -- cgit v1.2.3 From 3a1d021bff22b82a1d77b1ca2c0d467949481352 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 16:58:08 -0800 Subject: Debug Travis-CI failure, install Net::Remctl Change-Id: I3b97807548638865987861979e73ae341e06f681 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2b65d9e..7b18d7d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,9 @@ compiler: before_install: - sudo apt-get update -qq - - sudo apt-get install libauthen-sasl-perl libcrypt-generatepassword-perl libdatetime-perl libdatetime-format-sqlite-perl libdbd-sqlite3-perl libdbi-perl libdbix-class-perl libheimdal-kadm5-perl libkrb5-dev libnet-dns-perl libnet-ldap-perl libremctl-dev libsql-translator-perl libtest-minimumversion-perl libtest-pod-perl libtest-strict-perl libtimedate-perl libwebauth-perl perl sqlite3 + - sudo apt-get install libauthen-sasl-perl libcrypt-generatepassword-perl libdatetime-perl libdatetime-format-sqlite-perl libdbd-sqlite3-perl libdbi-perl libdbix-class-perl libheimdal-kadm5-perl libkrb5-dev libnet-dns-perl libnet-ldap-perl libnet-remctl-perl libremctl-dev libsql-translator-perl libtest-minimumversion-perl libtest-pod-perl libtest-strict-perl libtimedate-perl libwebauth-perl perl sqlite3 env: AUTHOR_TESTING=1 -script: ./autogen && ./configure && make warnings && make check +script: ./autogen && ./configure && make warnings && runtests -o tests/perl/strict && make check branches: only: -- cgit v1.2.3 From 98527101b865d053aae76245b3d2a2569048670e Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 17:12:10 -0800 Subject: Fix Travis-CI debugging (hopefully) Change-Id: I589c964895351c40e4b608925b055f97e6463d9a --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7b18d7d..9050cb8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ before_install: - sudo apt-get update -qq - sudo apt-get install libauthen-sasl-perl libcrypt-generatepassword-perl libdatetime-perl libdatetime-format-sqlite-perl libdbd-sqlite3-perl libdbi-perl libdbix-class-perl libheimdal-kadm5-perl libkrb5-dev libnet-dns-perl libnet-ldap-perl libnet-remctl-perl libremctl-dev libsql-translator-perl libtest-minimumversion-perl libtest-pod-perl libtest-strict-perl libtimedate-perl libwebauth-perl perl sqlite3 env: AUTHOR_TESTING=1 -script: ./autogen && ./configure && make warnings && runtests -o tests/perl/strict && make check +script: ./autogen && ./configure && make warnings && tests/runtests -o tests/perl/strict && make check branches: only: -- cgit v1.2.3 From 99c718eff041657704a50589486bde2f9e4391f7 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 17:50:12 -0800 Subject: Add libperl6-slurp-perl to the Travis CI build Change-Id: I7a69a5bc425e16fbcf0a294d5e3aaf941bb2a453 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9050cb8..ba0a6e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ compiler: before_install: - sudo apt-get update -qq - - sudo apt-get install libauthen-sasl-perl libcrypt-generatepassword-perl libdatetime-perl libdatetime-format-sqlite-perl libdbd-sqlite3-perl libdbi-perl libdbix-class-perl libheimdal-kadm5-perl libkrb5-dev libnet-dns-perl libnet-ldap-perl libnet-remctl-perl libremctl-dev libsql-translator-perl libtest-minimumversion-perl libtest-pod-perl libtest-strict-perl libtimedate-perl libwebauth-perl perl sqlite3 + - sudo apt-get install libauthen-sasl-perl libcrypt-generatepassword-perl libdatetime-perl libdatetime-format-sqlite-perl libdbd-sqlite3-perl libdbi-perl libdbix-class-perl libheimdal-kadm5-perl libkrb5-dev libnet-dns-perl libnet-ldap-perl libnet-remctl-perl libperl6-slurp-perl libremctl-dev libsql-translator-perl libtest-minimumversion-perl libtest-pod-perl libtest-strict-perl libtimedate-perl libwebauth-perl perl sqlite3 env: AUTHOR_TESTING=1 script: ./autogen && ./configure && make warnings && tests/runtests -o tests/perl/strict && make check -- 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 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 e142e127136d47a0f136859bf4d224a40935b96d Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 19:35:17 -0800 Subject: Do the Travis-CI build in trusty for WebAuth support Change-Id: I2bcee71d36782c08f858e78712e9d92605a69ba3 --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index ba0a6e7..6fff8be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,6 @@ +sudo: required +dist: trusty + language: c compiler: - gcc -- cgit v1.2.3 From 3881994c18929f5377a15b5921bc33c86492f606 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 19:56:48 -0800 Subject: Add stopword for Wallet::ACL::External documentation Change-Id: I3a8b13a8b255522cff92910f8d99ec94dc020e6f --- perl/lib/Wallet/ACL/External.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perl/lib/Wallet/ACL/External.pm b/perl/lib/Wallet/ACL/External.pm index da013aa..76d0891 100644 --- a/perl/lib/Wallet/ACL/External.pm +++ b/perl/lib/Wallet/ACL/External.pm @@ -89,7 +89,7 @@ __END__ ############################################################################## =for stopwords -ACL Allbery verifier +ACL Allbery verifier remctl =head1 NAME -- cgit v1.2.3 From a7b518bd54a73e0234d9dcb9bf9ef78272f73add Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 19:57:04 -0800 Subject: Fix Wallet::Object::Duo to pass strict.t test w/o Net::Duo Ubuntu precise and trusty don't have Net::Duo packages. Delay loading to the constructor so that the modules will still pass strictness tests. This also fixes Travis-CI testing. Change-Id: I23f1fe6dbdddaac2040f459410a74be4a13b6755 --- perl/lib/Wallet/Object/Duo.pm | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/perl/lib/Wallet/Object/Duo.pm b/perl/lib/Wallet/Object/Duo.pm index d0901de..378c123 100644 --- a/perl/lib/Wallet/Object/Duo.pm +++ b/perl/lib/Wallet/Object/Duo.pm @@ -1,7 +1,8 @@ # Wallet::Object::Duo -- Base Duo object implementation for the wallet # # Written by Russ Allbery -# Copyright 2014 +# Copyright 2016 Russ Allbery +# Copyright 2014, 2015 # The Board of Trustees of the Leland Stanford Junior University # # See LICENSE for licensing terms. @@ -18,8 +19,6 @@ use warnings; use vars qw(@ISA $VERSION); use JSON; -use Net::Duo::Admin; -use Net::Duo::Admin::Integration; use Perl6::Slurp qw(slurp); use Wallet::Config (); use Wallet::Object::Base; @@ -159,8 +158,20 @@ sub new { my $key_file = $Wallet::Config::DUO_KEY_FILE; my $agent = $Wallet::Config::DUO_AGENT; + # Check that we can load all of the required modules. + eval { + require Net::Duo; + require Net::Duo::Admin; + require Net::Duo::Admin::Integration; + }; + if ($@) { + my $error = $@; + chomp $error; + 1 while ($error =~ s/ at \S+ line \d+\.?\z//); + die "Duo object support not available: $error\n"; + } + # Construct the Net::Duo::Admin object. - require Net::Duo::Admin; my $duo = Net::Duo::Admin->new ( { key_file => $key_file, @@ -194,8 +205,20 @@ sub create { die "$type is not a valid duo integration\n"; } + # Check that we can load all of the required modules. + eval { + require Net::Duo; + require Net::Duo::Admin; + require Net::Duo::Admin::Integration; + }; + if ($@) { + my $error = $@; + chomp $error; + 1 while ($error =~ s/ at \S+ line \d+\.?\z//); + die "Duo object support not available: $error\n"; + } + # Construct the Net::Duo::Admin object. - require Net::Duo::Admin; my $duo = Net::Duo::Admin->new ( { key_file => $key_file, @@ -204,7 +227,6 @@ sub create { ); # Create the object in Duo. - require Net::Duo::Admin::Integration; my $duo_type = $DUO_TYPES{$type}{integration}; my %data = ( name => "$name ($duo_type)", -- cgit v1.2.3 From cfff765a7a453fdab46a217c2adc48c958fdf739 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 19:59:11 -0800 Subject: Document the Duo integration requires JSON and Perl6::Slurp Change-Id: I6249d2ea983959bc6c5ec03c2035a271228d4721 --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index d423983..75b1224 100644 --- a/README +++ b/README @@ -109,7 +109,7 @@ REQUIREMENTS WebAuth Perl module from WebAuth 4.4.0 or later. The Duo integration object support in the wallet server requires the - Net::Duo Perl module. + Net::Duo, JSON, and Perl6::Slurp Perl modules. The password object support in the wallet server requires the Crypt::GeneratePassword Perl module. -- cgit v1.2.3 From 7b59e2e854c9006b1cac8dd109f36e2ac68c6a00 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 20:19:31 -0800 Subject: Remove old Travis debugging, use apt-get -y Change-Id: Ibdd2494106324f8e1077daa084a2468c0a5fe4ea --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6fff8be..0f69179 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,9 @@ compiler: before_install: - sudo apt-get update -qq - - sudo apt-get install libauthen-sasl-perl libcrypt-generatepassword-perl libdatetime-perl libdatetime-format-sqlite-perl libdbd-sqlite3-perl libdbi-perl libdbix-class-perl libheimdal-kadm5-perl libkrb5-dev libnet-dns-perl libnet-ldap-perl libnet-remctl-perl libperl6-slurp-perl libremctl-dev libsql-translator-perl libtest-minimumversion-perl libtest-pod-perl libtest-strict-perl libtimedate-perl libwebauth-perl perl sqlite3 + - sudo apt-get install -y libauthen-sasl-perl libcrypt-generatepassword-perl libdatetime-perl libdatetime-format-sqlite-perl libdbd-sqlite3-perl libdbi-perl libdbix-class-perl libheimdal-kadm5-perl libkrb5-dev libnet-dns-perl libnet-ldap-perl libnet-remctl-perl libperl6-slurp-perl libremctl-dev libsql-translator-perl libtest-minimumversion-perl libtest-pod-perl libtest-strict-perl libtimedate-perl libwebauth-perl perl sqlite3 env: AUTHOR_TESTING=1 -script: ./autogen && ./configure && make warnings && tests/runtests -o tests/perl/strict && make check +script: ./autogen && ./configure && make warnings && make check branches: only: -- cgit v1.2.3 From 4eceb97d077503e1022b9bcdc703398cace78ff3 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 20:23:42 -0800 Subject: Add libjson-perl to Travis CI build --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0f69179..3f60b23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ compiler: before_install: - sudo apt-get update -qq - - sudo apt-get install -y libauthen-sasl-perl libcrypt-generatepassword-perl libdatetime-perl libdatetime-format-sqlite-perl libdbd-sqlite3-perl libdbi-perl libdbix-class-perl libheimdal-kadm5-perl libkrb5-dev libnet-dns-perl libnet-ldap-perl libnet-remctl-perl libperl6-slurp-perl libremctl-dev libsql-translator-perl libtest-minimumversion-perl libtest-pod-perl libtest-strict-perl libtimedate-perl libwebauth-perl perl sqlite3 + - sudo apt-get install -y libauthen-sasl-perl libcrypt-generatepassword-perl libdatetime-perl libdatetime-format-sqlite-perl libdbd-sqlite3-perl libdbi-perl libdbix-class-perl libheimdal-kadm5-perl libjson-perl libkrb5-dev libnet-dns-perl libnet-ldap-perl libnet-remctl-perl libperl6-slurp-perl libremctl-dev libsql-translator-perl libtest-minimumversion-perl libtest-pod-perl libtest-strict-perl libtimedate-perl libwebauth-perl perl sqlite3 env: AUTHOR_TESTING=1 script: ./autogen && ./configure && make warnings && make check -- cgit v1.2.3 From 423792510f017d36580eb6d96342f6d09433a078 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 21:25:40 -0800 Subject: Fix t/object/keytab.t MIT enctype recognition New versions of MIT now use the actual enctype in klist -ke output. Also add 128-bit AES. Also add some additional debugging that was useful when chasing another problem. --- perl/t/object/keytab.t | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/perl/t/object/keytab.t b/perl/t/object/keytab.t index 69db438..111b7d0 100755 --- a/perl/t/object/keytab.t +++ b/perl/t/object/keytab.t @@ -12,7 +12,7 @@ use strict; use warnings; use POSIX qw(strftime); -use Test::More tests => 141; +use Test::More tests => 142; BEGIN { $Wallet::Config::KEYTAB_TMP = '.' } @@ -25,15 +25,28 @@ use Wallet::Object::Keytab; use lib 't/lib'; use Util; -# Mapping of klist -ke encryption type names to the strings that Kerberos uses -# internally. It's very annoying to have to maintain this, and it probably -# breaks with Heimdal. +# Mapping of klist -ke output from old MIT Kerberos implementations to to the +# strings that Kerberos uses internally. It's very annoying to have to +# maintain this, and it probably breaks with Heimdal. +# +# Newer versions of MIT Kerberos just print out the canonical enctype names +# and don't need this logic, but the current test requires that they still +# have entries. That's why the second set where the key and value are the +# same. my %enctype = ('triple des cbc mode with hmac/sha1' => 'des3-cbc-sha1', 'des cbc mode with crc-32' => 'des-cbc-crc', 'des cbc mode with rsa-md5' => 'des-cbc-md5', + 'aes-128 cts mode with 96-bit sha-1 hmac' => 'aes128-cts-hmac-sha1-96', 'aes-256 cts mode with 96-bit sha-1 hmac' => 'aes256-cts-hmac-sha1-96', - 'arcfour with hmac/md5' => 'rc4-hmac'); + 'arcfour with hmac/md5' => 'rc4-hmac', + + 'des3-cbc-sha1' => 'des3-cbc-sha1', + 'des-cbc-crc' => 'des-cbc-crc', + 'des-cbc-md5' => 'des-cbc-md5', + 'aes128-cts-hmac-sha1-96' => 'aes128-cts-hmac-sha1-96', + 'aes256-cts-hmac-sha1-96' => 'aes256-cts-hmac-sha1-96', + 'rc4-hmac' => 'rc4-hmac'); # Some global defaults to use. my $user = 'admin@EXAMPLE.COM'; @@ -159,7 +172,7 @@ my $date = strftime ('%Y-%m-%d %H:%M:%S', localtime $trace[2]); # Basic keytab creation and manipulation tests. SKIP: { - skip 'no keytab configuration', 52 unless -f 't/data/test.keytab'; + skip 'no keytab configuration', 53 unless -f 't/data/test.keytab'; # Set up our configuration. $Wallet::Config::KEYTAB_FILE = 't/data/test.keytab'; @@ -296,6 +309,7 @@ EOO @trace) }; ok (defined ($object), 'Creating good principal succeeds'); + is ($@, '', ' with no error'); ok (created ('wallet/one'), ' and the principal was created'); SKIP: { skip 'no kadmin program test for Heimdal', 2 -- cgit v1.2.3 From 61ef051f79682742a30d1501b81cd86ed172fa3c Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 21:26:28 -0800 Subject: Use _exit when failing to fork external commands Failed kadmin commands were deleting the wallet database in the test suite due to an END block in the test programs. Use _exit to avoid this. --- perl/lib/Wallet/ACL/External.pm | 5 +++-- perl/lib/Wallet/Kadmin/MIT.pm | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/perl/lib/Wallet/ACL/External.pm b/perl/lib/Wallet/ACL/External.pm index 76d0891..77c2499 100644 --- a/perl/lib/Wallet/ACL/External.pm +++ b/perl/lib/Wallet/ACL/External.pm @@ -16,6 +16,7 @@ use strict; use warnings; use vars qw(@ISA $VERSION); +use POSIX qw(_exit); use Wallet::ACL::Base; use Wallet::Config; @@ -59,11 +60,11 @@ sub check { } elsif ($pid == 0) { unless (open (STDERR, '>&STDOUT')) { warn "wallet: cannot dup stdout: $!\n"; - exit 1; + _exit(1); } unless (exec ($Wallet::Config::EXTERNAL_COMMAND, @args)) { warn "wallet: cannot run $Wallet::Config::EXTERNAL_COMMAND: $!\n"; - exit 1; + _exit(1); } } local $_; diff --git a/perl/lib/Wallet/Kadmin/MIT.pm b/perl/lib/Wallet/Kadmin/MIT.pm index ac45265..c5dea23 100644 --- a/perl/lib/Wallet/Kadmin/MIT.pm +++ b/perl/lib/Wallet/Kadmin/MIT.pm @@ -18,6 +18,7 @@ use strict; use warnings; use vars qw(@ISA $VERSION); +use POSIX qw(_exit); use Wallet::Config (); use Wallet::Kadmin (); @@ -65,11 +66,11 @@ sub kadmin { $self->{fork_callback} () if $self->{fork_callback}; unless (open (STDERR, '>&STDOUT')) { warn "wallet: cannot dup stdout: $!\n"; - exit 1; + _exit(1); } unless (exec ($Wallet::Config::KEYTAB_KADMIN, @args)) { warn "wallet: cannot run $Wallet::Config::KEYTAB_KADMIN: $!\n"; - exit 1; + _exit(1); } } local $_; -- cgit v1.2.3 From 802e47e8d84530d191817b2d86978a0b09803186 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 3 Jan 2016 21:32:55 -0800 Subject: Clean up test-files directory after object/password test --- perl/t/object/password.t | 1 + 1 file changed, 1 insertion(+) diff --git a/perl/t/object/password.t b/perl/t/object/password.t index 4fe6b50..306d82b 100644 --- a/perl/t/object/password.t +++ b/perl/t/object/password.t @@ -120,5 +120,6 @@ like ($pwd, qr{^.{$Wallet::Config::PWD_LENGTH_MIN}$}, # Clean up. $admin->destroy; END { + system ('rm -r test-files') == 0 or die "cannot remove test-files\n"; unlink ('wallet-db'); } -- 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(-) 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 From 269b5a2cdb9b2f2c65423081f532db42a2ec55e4 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sat, 16 Jan 2016 16:13:03 -0800 Subject: Add documentation of the Active Directory support Also remove some configuration checks that aren't required, and unify handling of some configuration options. --- NEWS | 19 ++++++---- README | 23 ++++++++---- perl/lib/Wallet/Config.pm | 88 +++++++++++++++++++++++++++++++++++++++----- perl/lib/Wallet/Kadmin/AD.pm | 8 ++-- 4 files changed, 108 insertions(+), 30 deletions(-) diff --git a/NEWS b/NEWS index aa9cf47..9d5b1a6 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,18 @@ wallet 1.3 (unreleased) + This release adds initial, experimental support for using Active + Directory as the KDC for keytab creation. The interface to Active + Directory uses a combination of direct LDAP queries and the msktutil + utility. This version does not support the wallet unchanging flag. + Unchanging requires that a keytab be retrieved without changing the + password/kvno which is not supported by msktutil. Active Directory + can be selected by setting KEYTAB_KRBTYPE to AD in the wallet + configuration. Multiple other configuration options must also be set; + see Wallet::Config for more information and README for the additional + Perl modules required. Thanks to Bill MacAllister for the + implementation. + A new ACL type, nested (Wallet::ACL::Nested), is now supported. The identifier of this ACL names another ACL, and access is granted if that ACL would grant access. This lets one combine multiple other @@ -63,13 +75,6 @@ wallet 1.3 (unreleased) Displays of ACLs and ACL entries are now sorted correctly. - Initial support for using Active Directory as the KDC for keytab - creation. The interface to Active Directory uses a combination of - direct LDAP queries and the msktutil utility. This version does - not support the wallet unchanging flag. Unchanging requires that - a keytab be retrieved without changing the password/kvno which is - not supported by msktutil. - wallet 1.2 (2014-12-08) The duo object type has been split into several sub-types, each for a diff --git a/README b/README index 75b1224..200f0eb 100644 --- a/README +++ b/README @@ -91,12 +91,15 @@ REQUIREMENTS on CPAN for older versions. The keytab support in the wallet server supports either Heimdal or MIT - Kerberos KDCs. The Heimdal support requires the Heimdal::Kadm5 Perl - module. The MIT Kerberos support requires the MIT Kerberos kadmin - client program be installed. In either case, wallet also requires that - the wallet server have a keytab for a principal with appropriate access - to create, modify, and delete principals from the KDC (as configured in - kadm5.acl on an MIT Kerberos KDC). + Kerberos KDCs and has exeprimental support for Active Directory. The + Heimdal support requires the Heimdal::Kadm5 Perl module. The MIT + Kerberos support requires the MIT Kerberos kadmin client program be + installed. The Active Directory support requires the Net::LDAP, + Authen::SASL, and IPC::Run Perl modules and the msktutil client program. + In all cases, wallet also requires that the wallet server have a keytab + for a principal with appropriate access to create, modify, and delete + principals from the KDC (as configured in kadm5.acl on an MIT Kerberos + KDC). To support the unchanging flag on keytab objects with an MIT Kerberos KDC, the Net::Remctl Perl module (shipped with remctl) must be installed @@ -339,8 +342,12 @@ THANKS security models. To Jon Robertson for the refactoring of Wallet::Kadmin, Heimdal support, - many of the wallet server-side reports, and the initial wallet-rekey - implementation. + many of the wallet server-side reports, the initial wallet-rekey + implementation, and lots of work on object and ACL types including + nested ACLs. + + To Bill MacAllister for Wallet::Kadmin::AD and the implementation of + keytab object types backed by Active Directory. LICENSE diff --git a/perl/lib/Wallet/Config.pm b/perl/lib/Wallet/Config.pm index e8bc00c..f4ebc0f 100644 --- a/perl/lib/Wallet/Config.pm +++ b/perl/lib/Wallet/Config.pm @@ -319,7 +319,8 @@ modify, inspect, and delete any principals that should be managed by the wallet. (In MIT Kerberos F parlance, this is C privileges.) -KEYTAB_FILE must be set to use keytab objects. +KEYTAB_FILE must be set to use keytab objects with any backend other than +Active Directory. =cut @@ -336,16 +337,18 @@ is generally pointless and may interact poorly with the way C works when third-party add-ons for password strength checking are used.) +This option is ignored when using Active Directory. + =cut our $KEYTAB_FLAGS = '-clearpolicy'; =item KEYTAB_HOST -Specifies the host on which the kadmin service is running. This setting -overrides the C setting in the [realms] section of -F and any DNS SRV records and allows the wallet to run on a -system that doesn't have a Kerberos configuration for the wallet's realm. +Specifies the host on which the kadmin or Active Directory service is running. +This setting overrides the C setting in the [realms] section of +F and any DNS SRV records and allows the wallet to run on a system +that doesn't have a Kerberos configuration for the wallet's realm. =cut @@ -357,13 +360,15 @@ The path to the B command-line client. The default value is C, which will cause the wallet to search for B on its default PATH. +This option is ignored when using Active Directory. + =cut our $KEYTAB_KADMIN = 'kadmin'; =item KEYTAB_KRBTYPE -The Kerberos KDC implementation type, either C or C +The Kerberos KDC implementation type, chosen from C, C, or C (case-insensitive). KEYTAB_KRBTYPE must be set to use keytab objects. =cut @@ -375,9 +380,9 @@ our $KEYTAB_KRBTYPE; The principal whose key is stored in KEYTAB_FILE. The wallet will authenticate as this principal to the kadmin service. -KEYTAB_PRINCIPAL must be set to use keytab objects, at least until -B is smart enough to use the first principal found in the keytab -it's using for authentication. +KEYTAB_PRINCIPAL must be set to use keytab objects unless Active Directory is +the backend, at least until B is smart enough to use the first +principal found in the keytab it's using for authentication. =cut @@ -391,7 +396,7 @@ installation and the keytab object names are stored without realm. KEYTAB_REALM is added when talking to the KDC via B. KEYTAB_REALM must be set to use keytab objects. C doesn't always -default to the local realm. +default to the local realm and the Active Directory integration requires it. =cut @@ -414,6 +419,69 @@ our $KEYTAB_TMP; =back +The following parameters are specific to generating keytabs from Active +Directory (KEYTAB_KRBTYPE is set to C). + +=over 4 + +=item AD_CACHE + +Specifies the ticket cache to use when manipulating Active Directory objects. +The ticket cache must be for a principal able to bind to Active Directory and +run B. + +AD_CACHE must be set to use Active Directory support. + +=cut + +our $AD_CACHE; + +=item AD_COMPUTER_DN + +The LDAP base DN for computer objects inside Active Directory. All keytabs of +the form host/ will be mapped to objects with a C of +the portion under this DN. + +AD_COMPUTER_DN must be set if using Active Directory as the keytab backend. + +=cut + +our $AD_COMPUTER_DN; + +=item AD_DEBUG + +If set to true, asks for some additional debugging information, such as the +B command, to be logged to syslog. These debugging messages will be +logged to the C facility. + +=cut + +our $AD_DEBUG = 0; + +=item AD_MSKTUTIL + +The path to the B command-line client. The default value is +C, which will cause the wallet to search for B on its +default PATH. + +=cut + +our $AD_MSKTUTIL = 'msktutil'; + +=item AD_USER_DN + +The LDAP base DN for user objects inside Active Directory. All keytabs of the +form service/ will be mapped to objects with a C +matching the wallet object name under this DN. + +AD_USER_DN must be set if using Active Directory as the keytab backend. + +=cut + +our $AD_USER_DN; + +=back + =head2 Retrieving Existing Keytabs Heimdal provides the choice, over the network protocol, of either diff --git a/perl/lib/Wallet/Kadmin/AD.pm b/perl/lib/Wallet/Kadmin/AD.pm index 4efc643..97bf2bf 100644 --- a/perl/lib/Wallet/Kadmin/AD.pm +++ b/perl/lib/Wallet/Kadmin/AD.pm @@ -130,16 +130,14 @@ sub get_ad_keytab { # that error output. sub msktutil { my ($self, $args_ref) = @_; - unless (defined($Wallet::Config::KEYTAB_PRINCIPAL) - and defined($Wallet::Config::KEYTAB_FILE) + unless (defined($Wallet::Config::KEYTAB_HOST) and defined($Wallet::Config::KEYTAB_REALM)) { die "keytab object implementation not configured\n"; } - unless (defined($Wallet::Config::AD_SERVER) + unless (defined($Wallet::Config::AD_CACHE) and defined($Wallet::Config::AD_COMPUTER_DN) - and defined($Wallet::Config::AD_USER_DN) - and defined($Wallet::Config::AD_KEYTAB_BUCKET)) + and defined($Wallet::Config::AD_USER_DN)) { die "Active Directory support not configured\n"; } -- cgit v1.2.3 From 8c9c420553c4f9a1573d5c7ecabd41148f9e49e1 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sat, 16 Jan 2016 16:14:52 -0800 Subject: Fix spelling errors and add stopwords --- perl/lib/Wallet/Config.pm | 2 +- perl/lib/Wallet/Kadmin/AD.pm | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/perl/lib/Wallet/Config.pm b/perl/lib/Wallet/Config.pm index f4ebc0f..ac66676 100644 --- a/perl/lib/Wallet/Config.pm +++ b/perl/lib/Wallet/Config.pm @@ -30,7 +30,7 @@ Wallet::Config - Configuration handling for the wallet server DBI DSN SQLite subdirectories KEYTAB keytab kadmind KDC add-ons kadmin DNS SRV kadmin keytabs remctl backend lowercased NETDB ACL NetDB unscoped usernames rekey hostnames Allbery wallet-backend keytab-backend Heimdal -rekeys WebAuth WEBAUTH keyring LDAP DN GSS-API integrations +rekeys WebAuth WEBAUTH keyring LDAP DN GSS-API integrations msktutil =head1 SYNOPSIS diff --git a/perl/lib/Wallet/Kadmin/AD.pm b/perl/lib/Wallet/Kadmin/AD.pm index 97bf2bf..40e4376 100644 --- a/perl/lib/Wallet/Kadmin/AD.pm +++ b/perl/lib/Wallet/Kadmin/AD.pm @@ -413,7 +413,7 @@ __END__ =for stopwords rekeying rekeys remctl backend keytabs keytab kadmin KDC API Allbery -unlinked +unlinked MacAllister msktutil =head1 NAME @@ -435,7 +435,7 @@ Directory Kerberos, providing an interface to create and delete principals and create keytabs. It provides the API documented in L for an Active Directory Kerberos KDC. -AD erberos does not provide any method via msktuil to retrieve a +AD Kerberos does not provide any method via msktutil to retrieve a keytab for a principal without rekeying it, so the keytab() method (as opposed to keytab_rekey(), which rekeys the principal) is implemented using a local keytab cache. -- cgit v1.2.3 From 4feab8a987a345e38c44077d1042bf05ec03f0eb Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Sun, 17 Jan 2016 12:25:15 -0800 Subject: Standardize Perl module versions The versions of all of the wallet Perl modules now match the overall package version except for Wallet::Schema, which is used to version the database schema. Import the test from rra-c-util 5.10 and exclude Wallet::Schema from the tests. Go through all Perl modules and standardize the syntax for setting the version and indicating the required version of Perl. Fix a few other syntax issues while I'm in there. --- NEWS | 4 + perl/Build.PL | 2 +- perl/lib/Wallet/ACL.pm | 14 +- perl/lib/Wallet/ACL/Base.pm | 11 +- perl/lib/Wallet/ACL/External.pm | 12 +- perl/lib/Wallet/ACL/Krb5.pm | 14 +- perl/lib/Wallet/ACL/Krb5/Regex.pm | 12 +- perl/lib/Wallet/ACL/LDAP/Attribute.pm | 16 +- perl/lib/Wallet/ACL/LDAP/Attribute/Root.pm | 17 +- perl/lib/Wallet/ACL/Nested.pm | 12 +- perl/lib/Wallet/ACL/NetDB.pm | 14 +- perl/lib/Wallet/ACL/NetDB/Root.pm | 15 +- perl/lib/Wallet/Admin.pm | 11 +- perl/lib/Wallet/Config.pm | 12 +- perl/lib/Wallet/Database.pm | 16 +- perl/lib/Wallet/Kadmin.pm | 13 +- perl/lib/Wallet/Kadmin/AD.pm | 32 ++- perl/lib/Wallet/Kadmin/Heimdal.pm | 18 +- perl/lib/Wallet/Kadmin/MIT.pm | 18 +- perl/lib/Wallet/Object/Base.pm | 12 +- perl/lib/Wallet/Object/Duo.pm | 13 +- perl/lib/Wallet/Object/File.pm | 16 +- perl/lib/Wallet/Object/Keytab.pm | 18 +- perl/lib/Wallet/Object/Password.pm | 16 +- perl/lib/Wallet/Object/WAKeyring.pm | 16 +- perl/lib/Wallet/Policy/Stanford.pm | 7 +- perl/lib/Wallet/Report.pm | 11 +- perl/lib/Wallet/Schema.pm | 10 +- perl/lib/Wallet/Schema/Result/Acl.pm | 2 + perl/lib/Wallet/Schema/Result/AclEntry.pm | 2 + perl/lib/Wallet/Schema/Result/AclHistory.pm | 2 + perl/lib/Wallet/Schema/Result/AclScheme.pm | 3 + perl/lib/Wallet/Schema/Result/Duo.pm | 2 + perl/lib/Wallet/Schema/Result/Enctype.pm | 2 + perl/lib/Wallet/Schema/Result/Flag.pm | 2 + perl/lib/Wallet/Schema/Result/KeytabEnctype.pm | 2 + perl/lib/Wallet/Schema/Result/KeytabSync.pm | 2 + perl/lib/Wallet/Schema/Result/Object.pm | 2 + perl/lib/Wallet/Schema/Result/ObjectHistory.pm | 2 + perl/lib/Wallet/Schema/Result/SyncTarget.pm | 2 + perl/lib/Wallet/Schema/Result/Type.pm | 2 + perl/lib/Wallet/Server.pm | 11 +- tests/TESTS | 1 + tests/data/perl.conf | 5 +- tests/perl/module-version-t | 169 ++++++++++++++ tests/tap/perl/Test/RRA.pm | 104 ++++----- tests/tap/perl/Test/RRA/Automake.pm | 164 ++++++-------- tests/tap/perl/Test/RRA/Config.pm | 138 ++++++------ tests/tap/perl/Test/RRA/ModuleVersion.pm | 295 +++++++++++++++++++++++++ 49 files changed, 826 insertions(+), 470 deletions(-) create mode 100755 tests/perl/module-version-t create mode 100644 tests/tap/perl/Test/RRA/ModuleVersion.pm diff --git a/NEWS b/NEWS index 9d5b1a6..fe4429d 100644 --- a/NEWS +++ b/NEWS @@ -75,6 +75,10 @@ wallet 1.3 (unreleased) Displays of ACLs and ACL entries are now sorted correctly. + The versions of all of the wallet Perl modules now match the overall + package version except for Wallet::Schema, which is used to version + the database schema. + wallet 1.2 (2014-12-08) The duo object type has been split into several sub-types, each for a diff --git a/perl/Build.PL b/perl/Build.PL index 968ae37..05111dd 100644 --- a/perl/Build.PL +++ b/perl/Build.PL @@ -19,7 +19,7 @@ my $build = Module::Build->new( dist_abstract => 'Secure credential management system', dist_author => 'Russ Allbery ', dist_name => 'Wallet', - dist_version => '1.01', + dist_version => '1.03', license => 'mit', module_name => 'Wallet::Server', recursive_test_files => 1, diff --git a/perl/lib/Wallet/ACL.pm b/perl/lib/Wallet/ACL.pm index 69e6890..ad0eb2c 100644 --- a/perl/lib/Wallet/ACL.pm +++ b/perl/lib/Wallet/ACL.pm @@ -1,6 +1,7 @@ -# Wallet::ACL -- Implementation of ACLs in the wallet system. +# Wallet::ACL -- Implementation of ACLs in the wallet system # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2007, 2008, 2010, 2013, 2014, 2015 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,20 +12,15 @@ ############################################################################## package Wallet::ACL; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw($VERSION); -use Wallet::Object::Base; use DateTime; -use DBI; +use Wallet::Object::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.09'; +our $VERSION = '1.03'; ############################################################################## # Constructors diff --git a/perl/lib/Wallet/ACL/Base.pm b/perl/lib/Wallet/ACL/Base.pm index 3778c07..235a9cb 100644 --- a/perl/lib/Wallet/ACL/Base.pm +++ b/perl/lib/Wallet/ACL/Base.pm @@ -1,6 +1,7 @@ -# Wallet::ACL::Base -- Parent class for wallet ACL verifiers. +# Wallet::ACL::Base -- Parent class for wallet ACL verifiers # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2007, 2010, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,16 +12,12 @@ ############################################################################## package Wallet::ACL::Base; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw($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.03'; +our $VERSION = '1.03'; ############################################################################## # Interface diff --git a/perl/lib/Wallet/ACL/External.pm b/perl/lib/Wallet/ACL/External.pm index f1bd577..caed80e 100644 --- a/perl/lib/Wallet/ACL/External.pm +++ b/perl/lib/Wallet/ACL/External.pm @@ -1,6 +1,5 @@ # Wallet::ACL::External -- Wallet external ACL verifier # -# Written by Russ Allbery # Copyright 2016 Russ Allbery # # See LICENSE for licensing terms. @@ -10,22 +9,17 @@ ############################################################################## package Wallet::ACL::External; -require 5.008; +use 5.008; use strict; use warnings; -use vars qw(@ISA $VERSION); use POSIX qw(_exit); 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'; +our @ISA = qw(Wallet::ACL::Base); +our $VERSION = '1.03'; ############################################################################## # Interface diff --git a/perl/lib/Wallet/ACL/Krb5.pm b/perl/lib/Wallet/ACL/Krb5.pm index 80d32bd..e0e9a61 100644 --- a/perl/lib/Wallet/ACL/Krb5.pm +++ b/perl/lib/Wallet/ACL/Krb5.pm @@ -1,6 +1,7 @@ -# Wallet::ACL::Krb5 -- Wallet Kerberos v5 principal ACL verifier. +# Wallet::ACL::Krb5 -- Wallet Kerberos v5 principal ACL verifier # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2007, 2010, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,20 +12,15 @@ ############################################################################## package Wallet::ACL::Krb5; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw(@ISA $VERSION); use Wallet::ACL::Base; -@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.02'; +our @ISA = qw(Wallet::ACL::Base); +our $VERSION = '1.03'; ############################################################################## # Interface diff --git a/perl/lib/Wallet/ACL/Krb5/Regex.pm b/perl/lib/Wallet/ACL/Krb5/Regex.pm index 4934cfc..f3b9a06 100644 --- a/perl/lib/Wallet/ACL/Krb5/Regex.pm +++ b/perl/lib/Wallet/ACL/Krb5/Regex.pm @@ -1,6 +1,7 @@ # Wallet::ACL::Krb5::Regex -- Wallet Kerberos v5 principal regex ACL verifier # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2007, 2010, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,20 +12,15 @@ ############################################################################## package Wallet::ACL::Krb5::Regex; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw(@ISA $VERSION); use Wallet::ACL::Krb5; -@ISA = qw(Wallet::ACL::Krb5); - -# 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'; +our @ISA = qw(Wallet::ACL::Krb5); +our $VERSION = '1.03'; ############################################################################## # Interface diff --git a/perl/lib/Wallet/ACL/LDAP/Attribute.pm b/perl/lib/Wallet/ACL/LDAP/Attribute.pm index c27729e..fcb8447 100644 --- a/perl/lib/Wallet/ACL/LDAP/Attribute.pm +++ b/perl/lib/Wallet/ACL/LDAP/Attribute.pm @@ -1,6 +1,7 @@ -# Wallet::ACL::LDAP::Attribute -- Wallet LDAP attribute ACL verifier. +# Wallet::ACL::LDAP::Attribute -- Wallet LDAP attribute ACL verifier # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2012, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,23 +12,18 @@ ############################################################################## package Wallet::ACL::LDAP::Attribute; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw(@ISA $VERSION); -use Authen::SASL (); +use Authen::SASL; use Net::LDAP qw(LDAP_COMPARE_TRUE); 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'; +our @ISA = qw(Wallet::ACL::Base); +our $VERSION = '1.03'; ############################################################################## # Interface diff --git a/perl/lib/Wallet/ACL/LDAP/Attribute/Root.pm b/perl/lib/Wallet/ACL/LDAP/Attribute/Root.pm index eb30931..8451394 100644 --- a/perl/lib/Wallet/ACL/LDAP/Attribute/Root.pm +++ b/perl/lib/Wallet/ACL/LDAP/Attribute/Root.pm @@ -1,7 +1,8 @@ -# Wallet::ACL::LDAP::Attribute::Root -- Wallet LDAP ACL verifier (root instances). +# Wallet::ACL::LDAP::Attribute::Root -- Wallet root instance LDAP ACL verifier # # Written by Jon Robertson -# From Wallet::ACL::NetDB::Root by Russ Allbery +# Based on Wallet::ACL::NetDB::Root by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2015 # The Board of Trustees of the Leland Stanford Junior University # @@ -12,21 +13,15 @@ ############################################################################## package Wallet::ACL::LDAP::Attribute::Root; -require 5.006; +use 5.008; 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'; +our @ISA = qw(Wallet::ACL::LDAP::Attribute); +our $VERSION = '1.03'; ############################################################################## # Interface diff --git a/perl/lib/Wallet/ACL/Nested.pm b/perl/lib/Wallet/ACL/Nested.pm index 3b6c827..da42286 100644 --- a/perl/lib/Wallet/ACL/Nested.pm +++ b/perl/lib/Wallet/ACL/Nested.pm @@ -1,6 +1,7 @@ # Wallet::ACL::Nested - ACL class for nesting ACLs # # Written by Jon Robertson +# Copyright 2016 Russ Allbery # Copyright 2015 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,20 +12,15 @@ ############################################################################## package Wallet::ACL::Nested; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw($VERSION @ISA); use Wallet::ACL::Base; -@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'; +our @ISA = qw(Wallet::ACL::Base); +our $VERSION = '1.03'; ############################################################################## # Interface diff --git a/perl/lib/Wallet/ACL/NetDB.pm b/perl/lib/Wallet/ACL/NetDB.pm index ad2164b..a4c7fb0 100644 --- a/perl/lib/Wallet/ACL/NetDB.pm +++ b/perl/lib/Wallet/ACL/NetDB.pm @@ -1,6 +1,7 @@ -# Wallet::ACL::NetDB -- Wallet NetDB role ACL verifier. +# Wallet::ACL::NetDB -- Wallet NetDB role ACL verifier # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2007, 2010, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,21 +12,16 @@ ############################################################################## package Wallet::ACL::NetDB; -require 5.006; +use 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.05'; +our @ISA = qw(Wallet::ACL::Base); +our $VERSION = '1.03'; ############################################################################## # Interface diff --git a/perl/lib/Wallet/ACL/NetDB/Root.pm b/perl/lib/Wallet/ACL/NetDB/Root.pm index 34163e7..bfd13b4 100644 --- a/perl/lib/Wallet/ACL/NetDB/Root.pm +++ b/perl/lib/Wallet/ACL/NetDB/Root.pm @@ -1,6 +1,7 @@ -# Wallet::ACL::NetDB::Root -- Wallet NetDB role ACL verifier (root instances). +# Wallet::ACL::NetDB::Root -- Wallet NetDB role ACL verifier (root instances) # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2007, 2010, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,21 +12,15 @@ ############################################################################## package Wallet::ACL::NetDB::Root; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw(@ISA $VERSION); use Wallet::ACL::NetDB; -use Wallet::Config; -@ISA = qw(Wallet::ACL::NetDB); - -# 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.02'; +our @ISA = qw(Wallet::ACL::NetDB); +our $VERSION = '1.03'; ############################################################################## # Interface diff --git a/perl/lib/Wallet/Admin.pm b/perl/lib/Wallet/Admin.pm index b4246ba..9b63174 100644 --- a/perl/lib/Wallet/Admin.pm +++ b/perl/lib/Wallet/Admin.pm @@ -1,6 +1,7 @@ -# Wallet::Admin -- Wallet system administrative interface. +# Wallet::Admin -- Wallet system administrative interface # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2008, 2009, 2010, 2011, 2012, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,19 +12,15 @@ ############################################################################## package Wallet::Admin; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw($VERSION); use Wallet::ACL; use Wallet::Schema; -# 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.08'; +our $VERSION = '1.03'; # The last non-DBIx::Class version of Wallet::Schema. If a database has no # DBIx::Class versioning, we artificially install this version number before diff --git a/perl/lib/Wallet/Config.pm b/perl/lib/Wallet/Config.pm index ac66676..b8771c3 100644 --- a/perl/lib/Wallet/Config.pm +++ b/perl/lib/Wallet/Config.pm @@ -1,4 +1,4 @@ -# Wallet::Config -- Configuration handling for the wallet server. +# Wallet::Config -- Configuration handling for the wallet server # # Written by Russ Allbery # Copyright 2016 Russ Allbery @@ -8,19 +8,15 @@ # See LICENSE for licensing terms. package Wallet::Config; -require 5.006; +use 5.008; use strict; use warnings; -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.06'; +our $VERSION = '1.03'; # Path to the config file to load. -$PATH = $ENV{WALLET_CONFIG} || '/etc/wallet/wallet.conf'; +our $PATH = $ENV{WALLET_CONFIG} || '/etc/wallet/wallet.conf'; =head1 NAME diff --git a/perl/lib/Wallet/Database.pm b/perl/lib/Wallet/Database.pm index 3a4e130..23b059f 100644 --- a/perl/lib/Wallet/Database.pm +++ b/perl/lib/Wallet/Database.pm @@ -1,4 +1,4 @@ -# Wallet::Database -- Wallet system database connection management. +# Wallet::Database -- Wallet system database connection management # # This module is a thin wrapper around DBIx::Class to handle determination # of the database configuration settings automatically on connect. The @@ -6,6 +6,7 @@ # like DBIx::Class objects in the rest of the code. # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2008, 2009, 2010, 2012, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -16,21 +17,16 @@ ############################################################################## package Wallet::Database; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw(@ISA $VERSION); -use Wallet::Schema; use Wallet::Config; +use Wallet::Schema; -@ISA = qw(Wallet::Schema); - -# 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.04'; +our @ISA = qw(Wallet::Schema); +our $VERSION = '1.03'; ############################################################################## # Core overrides diff --git a/perl/lib/Wallet/Kadmin.pm b/perl/lib/Wallet/Kadmin.pm index cb3bd47..8851c7e 100644 --- a/perl/lib/Wallet/Kadmin.pm +++ b/perl/lib/Wallet/Kadmin.pm @@ -1,6 +1,7 @@ -# Wallet::Kadmin -- Kerberos administration API for wallet keytab backend. +# Wallet::Kadmin -- Kerberos administration API for wallet keytab backend # # Written by Jon Robertson +# Copyright 2016 Russ Allbery # Copyright 2009, 2010, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,18 +12,14 @@ ############################################################################## package Wallet::Kadmin; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw($VERSION); -use Wallet::Config (); +use Wallet::Config; -# 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.03'; +our $VERSION = '1.03'; ############################################################################## # Utility functions for child classes diff --git a/perl/lib/Wallet/Kadmin/AD.pm b/perl/lib/Wallet/Kadmin/AD.pm index 40e4376..5b71d41 100644 --- a/perl/lib/Wallet/Kadmin/AD.pm +++ b/perl/lib/Wallet/Kadmin/AD.pm @@ -1,10 +1,10 @@ -# Wallet::Kadmin::AD -- Wallet Kerberos administration API for AD. +# Wallet::Kadmin::AD -- Wallet Kerberos administration API for AD # # Written by Bill MacAllister -# Based on work by Russ Allbery and -# Jon Robertson -# Copyright 2015 -# Dropbox +# Copyright 2016 Russ Allbery +# Copyright 2015 Dropbox, Inc. +# Copyright 2007, 2008, 2009, 2010, 2014 +# The Board of Trustees of the Leland Stanford Junior University # # See LICENSE for licensing terms. @@ -13,26 +13,20 @@ ############################################################################## package Wallet::Kadmin::AD; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw(@ISA $VERSION); -use Wallet::Config (); -use Wallet::Kadmin (); - -use Authen::SASL (); +use Authen::SASL; use Net::LDAP; -use IPC::Run qw( run timeout ); -use Sys::Syslog qw( :standard :macros ); - -@ISA = qw(Wallet::Kadmin); +use IPC::Run qw(run timeout); +use Sys::Syslog qw(:standard :macros); +use Wallet::Config; +use Wallet::Kadmin; -# 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.02'; +our @ISA = qw(Wallet::Kadmin); +our $VERSION = '1.03'; ############################################################################## # kadmin Interaction diff --git a/perl/lib/Wallet/Kadmin/Heimdal.pm b/perl/lib/Wallet/Kadmin/Heimdal.pm index 1208801..22bdd59 100644 --- a/perl/lib/Wallet/Kadmin/Heimdal.pm +++ b/perl/lib/Wallet/Kadmin/Heimdal.pm @@ -1,6 +1,7 @@ -# Wallet::Kadmin::Heimdal -- Wallet Kerberos administration API for Heimdal. +# Wallet::Kadmin::Heimdal -- Wallet Kerberos administration API for Heimdal # # Written by Jon Robertson +# Copyright 2016 Russ Allbery # Copyright 2009, 2010, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,22 +12,17 @@ ############################################################################## package Wallet::Kadmin::Heimdal; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw(@ISA $VERSION); use Heimdal::Kadm5 qw(KRB5_KDB_DISALLOW_ALL_TIX); -use Wallet::Config (); -use Wallet::Kadmin (); +use Wallet::Config; +use Wallet::Kadmin; -@ISA = qw(Wallet::Kadmin); - -# 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.04'; +our @ISA = qw(Wallet::Kadmin); +our $VERSION = '1.03'; ############################################################################## # Utility functions diff --git a/perl/lib/Wallet/Kadmin/MIT.pm b/perl/lib/Wallet/Kadmin/MIT.pm index c5dea23..9f0f50f 100644 --- a/perl/lib/Wallet/Kadmin/MIT.pm +++ b/perl/lib/Wallet/Kadmin/MIT.pm @@ -1,7 +1,8 @@ -# Wallet::Kadmin::MIT -- Wallet Kerberos administration API for MIT. +# Wallet::Kadmin::MIT -- Wallet Kerberos administration API for MIT # # Written by Russ Allbery # Pulled into a module by Jon Robertson +# Copyright 2016 Russ Allbery # Copyright 2007, 2008, 2009, 2010, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -12,22 +13,17 @@ ############################################################################## package Wallet::Kadmin::MIT; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw(@ISA $VERSION); use POSIX qw(_exit); -use Wallet::Config (); -use Wallet::Kadmin (); +use Wallet::Config; +use Wallet::Kadmin; -@ISA = qw(Wallet::Kadmin); - -# 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.03'; +our @ISA = qw(Wallet::Kadmin); +our $VERSION = '1.03'; ############################################################################## # kadmin Interaction diff --git a/perl/lib/Wallet/Object/Base.pm b/perl/lib/Wallet/Object/Base.pm index 97e6127..221031f 100644 --- a/perl/lib/Wallet/Object/Base.pm +++ b/perl/lib/Wallet/Object/Base.pm @@ -1,6 +1,7 @@ -# Wallet::Object::Base -- Parent class for any object stored in the wallet. +# Wallet::Object::Base -- Parent class for any object stored in the wallet # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2007, 2008, 2010, 2011, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,22 +12,17 @@ ############################################################################## package Wallet::Object::Base; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw($VERSION); use DateTime; use Date::Parse qw(str2time); -use DBI; use Text::Wrap qw(wrap); use Wallet::ACL; -# 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.08'; +our $VERSION = '1.03'; ############################################################################## # Constructors diff --git a/perl/lib/Wallet/Object/Duo.pm b/perl/lib/Wallet/Object/Duo.pm index 378c123..1aca979 100644 --- a/perl/lib/Wallet/Object/Duo.pm +++ b/perl/lib/Wallet/Object/Duo.pm @@ -12,23 +12,18 @@ ############################################################################## package Wallet::Object::Duo; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw(@ISA $VERSION); use JSON; use Perl6::Slurp qw(slurp); -use Wallet::Config (); +use Wallet::Config; use Wallet::Object::Base; -@ISA = qw(Wallet::Object::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.03'; +our @ISA = qw(Wallet::Object::Base); +our $VERSION = '1.03'; # Mappings from our types into what Duo calls the integration types. our %DUO_TYPES = ( diff --git a/perl/lib/Wallet/Object/File.pm b/perl/lib/Wallet/Object/File.pm index 226e32c..9452ff4 100644 --- a/perl/lib/Wallet/Object/File.pm +++ b/perl/lib/Wallet/Object/File.pm @@ -1,6 +1,7 @@ -# Wallet::Object::File -- File object implementation for the wallet. +# Wallet::Object::File -- File object implementation for the wallet # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2008, 2010, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,23 +12,18 @@ ############################################################################## package Wallet::Object::File; -require 5.006; +use 5.006; use strict; use warnings; -use vars qw(@ISA $VERSION); use Digest::MD5 qw(md5_hex); use File::Copy qw(move); -use Wallet::Config (); +use Wallet::Config; use Wallet::Object::Base; -@ISA = qw(Wallet::Object::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.03'; +our @ISA = qw(Wallet::Object::Base); +our $VERSION = '1.03'; ############################################################################## # File naming diff --git a/perl/lib/Wallet/Object/Keytab.pm b/perl/lib/Wallet/Object/Keytab.pm index c625766..f276b3f 100644 --- a/perl/lib/Wallet/Object/Keytab.pm +++ b/perl/lib/Wallet/Object/Keytab.pm @@ -1,6 +1,7 @@ -# Wallet::Object::Keytab -- Keytab object implementation for the wallet. +# Wallet::Object::Keytab -- Keytab object implementation for the wallet # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2007, 2008, 2009, 2010, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,22 +12,17 @@ ############################################################################## package Wallet::Object::Keytab; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw(@ISA $VERSION); -use Wallet::Config (); -use Wallet::Object::Base; +use Wallet::Config; use Wallet::Kadmin; +use Wallet::Object::Base; -@ISA = qw(Wallet::Object::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.09'; +our @ISA = qw(Wallet::Object::Base); +our $VERSION = '1.03'; ############################################################################## # Shared methods diff --git a/perl/lib/Wallet/Object/Password.pm b/perl/lib/Wallet/Object/Password.pm index 3fd6ec8..1db53f3 100644 --- a/perl/lib/Wallet/Object/Password.pm +++ b/perl/lib/Wallet/Object/Password.pm @@ -1,6 +1,7 @@ -# Wallet::Object::Password -- Password object implementation for the wallet. +# Wallet::Object::Password -- Password object implementation for the wallet # # Written by Jon Robertson +# Copyright 2016 Russ Allbery # Copyright 2015 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,23 +12,18 @@ ############################################################################## package Wallet::Object::Password; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw(@ISA $VERSION); use Crypt::GeneratePassword qw(chars); use Digest::MD5 qw(md5_hex); -use Wallet::Config (); +use Wallet::Config; use Wallet::Object::File; -@ISA = qw(Wallet::Object::File); - -# 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'; +our @ISA = qw(Wallet::Object::File); +our $VERSION = '1.03'; ############################################################################## # File naming diff --git a/perl/lib/Wallet/Object/WAKeyring.pm b/perl/lib/Wallet/Object/WAKeyring.pm index 3e80300..3c99785 100644 --- a/perl/lib/Wallet/Object/WAKeyring.pm +++ b/perl/lib/Wallet/Object/WAKeyring.pm @@ -1,6 +1,7 @@ -# Wallet::Object::WAKeyring -- WebAuth keyring object implementation. +# Wallet::Object::WAKeyring -- WebAuth keyring object implementation # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2012, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,24 +12,19 @@ ############################################################################## package Wallet::Object::WAKeyring; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw(@ISA $VERSION); use Digest::MD5 qw(md5_hex); use Fcntl qw(LOCK_EX); -use Wallet::Config (); +use Wallet::Config; use Wallet::Object::Base; use WebAuth 3.06 qw(WA_KEY_AES WA_AES_128); -@ISA = qw(Wallet::Object::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'; +our @ISA = qw(Wallet::Object::Base); +our $VERSION = '1.03'; ############################################################################## # File naming diff --git a/perl/lib/Wallet/Policy/Stanford.pm b/perl/lib/Wallet/Policy/Stanford.pm index 86e204e..efb9d28 100644 --- a/perl/lib/Wallet/Policy/Stanford.pm +++ b/perl/lib/Wallet/Policy/Stanford.pm @@ -1,7 +1,8 @@ -# Wallet::Policy::Stanford -- Stanford's wallet naming and ownership policy. +# Wallet::Policy::Stanford -- Stanford's wallet naming and ownership policy # # Written by Russ Allbery -# Copyright 2013 +# Copyright 2016 Russ Allbery +# Copyright 2013, 2014, 2015 # The Board of Trustees of the Leland Stanford Junior University # # See LICENSE for licensing terms. @@ -25,7 +26,7 @@ our (@EXPORT_OK, $VERSION); # against circular module loading (not that we load any modules, but # consistency is good). BEGIN { - $VERSION = '1.01'; + $VERSION = '1.03'; @EXPORT_OK = qw(default_owner verify_name is_for_host); } diff --git a/perl/lib/Wallet/Report.pm b/perl/lib/Wallet/Report.pm index 353cd97..3d59bf8 100644 --- a/perl/lib/Wallet/Report.pm +++ b/perl/lib/Wallet/Report.pm @@ -1,6 +1,7 @@ -# Wallet::Report -- Wallet system reporting interface. +# Wallet::Report -- Wallet system reporting interface # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2008, 2009, 2010, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,19 +12,15 @@ ############################################################################## package Wallet::Report; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw($VERSION); use Wallet::ACL; use Wallet::Schema; -# 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.04'; +our $VERSION = '1.03'; ############################################################################## # Constructor, destructor, and accessors diff --git a/perl/lib/Wallet/Schema.pm b/perl/lib/Wallet/Schema.pm index 386801a..6b3de39 100644 --- a/perl/lib/Wallet/Schema.pm +++ b/perl/lib/Wallet/Schema.pm @@ -1,6 +1,7 @@ -# Database schema and connector for the wallet system. +# Wallet::Schema -- Database schema and connector for the wallet system # # Written by Jon Robertson +# Copyright 2016 Russ Allbery # Copyright 2012, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -8,6 +9,7 @@ package Wallet::Schema; +use 5.008; use strict; use warnings; @@ -15,9 +17,9 @@ use Wallet::Config; use base 'DBIx::Class::Schema'; -# 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. +# Unlike all of the other wallet modules, this module's version is tied to the +# version of the schema in the database. It should only be changed on schema +# changes, at least until better handling of upgrades is available. our $VERSION = '0.10'; __PACKAGE__->load_namespaces; diff --git a/perl/lib/Wallet/Schema/Result/Acl.pm b/perl/lib/Wallet/Schema/Result/Acl.pm index 226738a..59a628a 100644 --- a/perl/lib/Wallet/Schema/Result/Acl.pm +++ b/perl/lib/Wallet/Schema/Result/Acl.pm @@ -13,6 +13,8 @@ use warnings; use base 'DBIx::Class::Core'; +our $VERSION = '1.03'; + =for stopwords ACL diff --git a/perl/lib/Wallet/Schema/Result/AclEntry.pm b/perl/lib/Wallet/Schema/Result/AclEntry.pm index a33a98c..ea531bd 100644 --- a/perl/lib/Wallet/Schema/Result/AclEntry.pm +++ b/perl/lib/Wallet/Schema/Result/AclEntry.pm @@ -13,6 +13,8 @@ use warnings; use base 'DBIx::Class::Core'; +our $VERSION = '1.03'; + =for stopwords ACL diff --git a/perl/lib/Wallet/Schema/Result/AclHistory.pm b/perl/lib/Wallet/Schema/Result/AclHistory.pm index 82e18a9..dc6bed7 100644 --- a/perl/lib/Wallet/Schema/Result/AclHistory.pm +++ b/perl/lib/Wallet/Schema/Result/AclHistory.pm @@ -13,6 +13,8 @@ use warnings; use base 'DBIx::Class::Core'; +our $VERSION = '1.03'; + __PACKAGE__->load_components("InflateColumn::DateTime"); =for stopwords diff --git a/perl/lib/Wallet/Schema/Result/AclScheme.pm b/perl/lib/Wallet/Schema/Result/AclScheme.pm index be4ec09..004e5d2 100644 --- a/perl/lib/Wallet/Schema/Result/AclScheme.pm +++ b/perl/lib/Wallet/Schema/Result/AclScheme.pm @@ -12,6 +12,9 @@ use strict; use warnings; use base 'DBIx::Class::Core'; + +our $VERSION = '1.03'; + __PACKAGE__->load_components (qw//); =for stopwords diff --git a/perl/lib/Wallet/Schema/Result/Duo.pm b/perl/lib/Wallet/Schema/Result/Duo.pm index 6ad61e9..b5328bb 100644 --- a/perl/lib/Wallet/Schema/Result/Duo.pm +++ b/perl/lib/Wallet/Schema/Result/Duo.pm @@ -13,6 +13,8 @@ use warnings; use base 'DBIx::Class::Core'; +our $VERSION = '1.03'; + =for stopwords keytab enctype diff --git a/perl/lib/Wallet/Schema/Result/Enctype.pm b/perl/lib/Wallet/Schema/Result/Enctype.pm index 5733669..f1f42a9 100644 --- a/perl/lib/Wallet/Schema/Result/Enctype.pm +++ b/perl/lib/Wallet/Schema/Result/Enctype.pm @@ -13,6 +13,8 @@ use warnings; use base 'DBIx::Class::Core'; +our $VERSION = '1.03'; + =for stopwords Kerberos diff --git a/perl/lib/Wallet/Schema/Result/Flag.pm b/perl/lib/Wallet/Schema/Result/Flag.pm index e223ff8..84e3ee3 100644 --- a/perl/lib/Wallet/Schema/Result/Flag.pm +++ b/perl/lib/Wallet/Schema/Result/Flag.pm @@ -13,6 +13,8 @@ use warnings; use base 'DBIx::Class::Core'; +our $VERSION = '1.03'; + =head1 NAME Wallet::Schema::Result::Flag - Wallet schema for object flags diff --git a/perl/lib/Wallet/Schema/Result/KeytabEnctype.pm b/perl/lib/Wallet/Schema/Result/KeytabEnctype.pm index daea724..2a16af8 100644 --- a/perl/lib/Wallet/Schema/Result/KeytabEnctype.pm +++ b/perl/lib/Wallet/Schema/Result/KeytabEnctype.pm @@ -13,6 +13,8 @@ use warnings; use base 'DBIx::Class::Core'; +our $VERSION = '1.03'; + =for stopwords keytab enctype diff --git a/perl/lib/Wallet/Schema/Result/KeytabSync.pm b/perl/lib/Wallet/Schema/Result/KeytabSync.pm index ca84277..bd57310 100644 --- a/perl/lib/Wallet/Schema/Result/KeytabSync.pm +++ b/perl/lib/Wallet/Schema/Result/KeytabSync.pm @@ -13,6 +13,8 @@ use warnings; use base 'DBIx::Class::Core'; +our $VERSION = '1.03'; + =for stopwords keytab diff --git a/perl/lib/Wallet/Schema/Result/Object.pm b/perl/lib/Wallet/Schema/Result/Object.pm index fd64e1b..fdec3b8 100644 --- a/perl/lib/Wallet/Schema/Result/Object.pm +++ b/perl/lib/Wallet/Schema/Result/Object.pm @@ -13,6 +13,8 @@ use warnings; use base 'DBIx::Class::Core'; +our $VERSION = '1.03'; + __PACKAGE__->load_components("InflateColumn::DateTime"); =head1 NAME diff --git a/perl/lib/Wallet/Schema/Result/ObjectHistory.pm b/perl/lib/Wallet/Schema/Result/ObjectHistory.pm index 5e9c8bd..2fe687e 100644 --- a/perl/lib/Wallet/Schema/Result/ObjectHistory.pm +++ b/perl/lib/Wallet/Schema/Result/ObjectHistory.pm @@ -13,6 +13,8 @@ use warnings; use base 'DBIx::Class::Core'; +our $VERSION = '1.03'; + __PACKAGE__->load_components("InflateColumn::DateTime"); =head1 NAME diff --git a/perl/lib/Wallet/Schema/Result/SyncTarget.pm b/perl/lib/Wallet/Schema/Result/SyncTarget.pm index 4300a54..ab8ea47 100644 --- a/perl/lib/Wallet/Schema/Result/SyncTarget.pm +++ b/perl/lib/Wallet/Schema/Result/SyncTarget.pm @@ -13,6 +13,8 @@ use warnings; use base 'DBIx::Class::Core'; +our $VERSION = '1.03'; + =head1 NAME Wallet::Schema::Result::SyncTarget - Wallet schema for synchronization targets diff --git a/perl/lib/Wallet/Schema/Result/Type.pm b/perl/lib/Wallet/Schema/Result/Type.pm index 748a8a8..abc7017 100644 --- a/perl/lib/Wallet/Schema/Result/Type.pm +++ b/perl/lib/Wallet/Schema/Result/Type.pm @@ -13,6 +13,8 @@ use warnings; use base 'DBIx::Class::Core'; +our $VERSION = '1.03'; + =for stopwords APIs diff --git a/perl/lib/Wallet/Server.pm b/perl/lib/Wallet/Server.pm index 946ba10..552ba9d 100644 --- a/perl/lib/Wallet/Server.pm +++ b/perl/lib/Wallet/Server.pm @@ -1,6 +1,7 @@ -# Wallet::Server -- Wallet system server implementation. +# Wallet::Server -- Wallet system server implementation # # Written by Russ Allbery +# Copyright 2016 Russ Allbery # Copyright 2007, 2008, 2010, 2011, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # @@ -11,20 +12,16 @@ ############################################################################## package Wallet::Server; -require 5.006; +use 5.008; use strict; use warnings; -use vars qw(%MAPPING $VERSION); use Wallet::ACL; use Wallet::Config; use Wallet::Schema; -# 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.11'; +our $VERSION = '1.03'; ############################################################################## # Utility methods diff --git a/tests/TESTS b/tests/TESTS index d947e97..f78a477 100644 --- a/tests/TESTS +++ b/tests/TESTS @@ -5,6 +5,7 @@ client/rekey docs/pod docs/pod-spelling perl/minimum-version +perl/module-version perl/strict portable/asprintf portable/mkstemp diff --git a/tests/data/perl.conf b/tests/data/perl.conf index eaf7443..0c1e34e 100644 --- a/tests/data/perl.conf +++ b/tests/data/perl.conf @@ -1,6 +1,9 @@ # Configuration for Perl tests. -*- perl -*- -# No special configuration yet. +# Wallet::Schema's version number is used to version the database schema and +# requires upgrade SQL files for each version bump. Until this is replaced +# with some better system, exclude it from version checking. +@MODULE_VERSION_IGNORE = qw(perl/lib/Wallet/Schema.pm); # File must end with this line. 1; diff --git a/tests/perl/module-version-t b/tests/perl/module-version-t new file mode 100755 index 0000000..a9ebf3b --- /dev/null +++ b/tests/perl/module-version-t @@ -0,0 +1,169 @@ +#!/usr/bin/perl +# +# Check or update the version of embedded Perl modules. +# +# Examines all module files (*.pm) under the perl/lib directory, if it exists, +# and verifies that their $VERSION is set to the same value as the current +# version number as determined by the NEWS file at the top level of the source +# tree (or the current directory if not being run as a test). +# +# When given the --update option, instead fixes all of the Perl modules found +# to have the correct version. + +use 5.006; +use strict; +use warnings; + +# SOURCE may not be set if we're running this script manually to update +# version numbers. If it isn't, assume we're being run from the top of the +# tree. +BEGIN { + if ($ENV{SOURCE}) { + unshift(@INC, "$ENV{SOURCE}/tap/perl"); + } else { + unshift(@INC, 'tests/tap/perl'); + } +} + +use Getopt::Long qw(GetOptions); +use Test::RRA qw(skip_unless_automated); +use Test::RRA::Automake qw(automake_setup); +use Test::RRA::ModuleVersion qw(test_module_versions update_module_versions); + +# Return the current version and, optionally, the package name from the NEWS +# file. Munges the version to be appropriate for Perl if necessary. +# +# Returns: Scalar: The version number of the latest version in NEWS +# List: The version number and the package name +# Throws: Text exception if NEWS is not found or doesn't contain a version +sub news_version { + my ($package, $version); + open(my $news, q{<}, 'NEWS') or die "$0: cannot open NEWS: $!\n"; + SCAN: + while (defined(my $line = <$news>)) { + ## no critic (RegularExpressions::ProhibitEscapedMetacharacters) + if ($line =~ m{ \A ([\w\s-]+) \s ([\d.]+) \s \( }xms) { + ($package, $version) = ($1, $2); + last SCAN; + } + ## use critic + } + close($news) or die "$0: error reading from NEWS: $!\n"; + if (!defined($version)) { + die "$0: cannot find version number in NEWS\n"; + } + + # Munge the version for Perl purposes by ensuring that each component + # has two digits and by dropping the second period. + $version =~ s{ [.] (\d) (?= [.] | \z ) }{.0$1}xmsg; + $version =~ s{ ([.] \d+) [.] (\d+) }{$1$2}xms; + + # Return the appropriate value based on context. + return wantarray ? ($version, $package) : $version; +} + +# Get the package name and version. +my ($version, $package) = news_version(); + +# rra-c-util itself checks the versions of the testing support modules instead +# of an embedded tree of Perl modules. +my $root = ($package eq 'rra-c-util') ? 'tests/tap/perl' : 'perl/lib'; + +# Main routine. We run as either a test suite or as a script to update all of +# the module versions, selecting based on whether we got the -u / --update +# command-line option. +my $update; +Getopt::Long::config('bundling', 'no_ignore_case'); +GetOptions('update|u' => \$update) or exit 1; +if ($update) { + update_module_versions($root, $version); +} else { + skip_unless_automated('Module version tests'); + automake_setup(); + test_module_versions($root, $version); +} +exit 0; +__END__ + +=for stopwords +Allbery sublicense MERCHANTABILITY NONINFRINGEMENT rra-c-util + +=head1 NAME + +module-version-t - Check or update versions of embedded Perl modules + +=head1 SYNOPSIS + +B [B<--update>] + +=head1 REQUIREMENTS + +Perl 5.6.2 or later. + +=head1 DESCRIPTION + +This script has a dual purpose as either a test script or a utility script. +The intent is to assist with maintaining consistent versions between a larger +primarily C project and any embedded Perl modules, supporting both the package +keyword syntax introduced in Perl 5.12 or the older explicit setting of a +$VERSION variable. + +As a test, it reads the current version of a package from the F file and +then looks for any Perl modules in F. (As a special exception, if +the package name as determined from the F file is C, it +looks for Perl modules in F instead.) If it finds any, it +checks that the version number of the Perl module matches the version number +of the package from the F file. These test results are reported with +Test::More, suitable for any TAP harness. + +As a utility script, when run with the B<--update> option, it similarly finds +all Perl modules in F (or F per above) and then +rewrites their version setting to match the version of the package as +determined from the F file. + +=head1 OPTIONS + +=over 4 + +=item B<-u>, B<--update> + +Rather than test the Perl modules for the correct version, update all Perl +modules found in the tree under F to the current version from the +NEWS file. + +=back + +=head1 AUTHOR + +Russ Allbery + +=head1 COPYRIGHT AND LICENSE + +Copyright 2014, 2016 Russ Allbery + +Copyright 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. + +=head1 SEE ALSO + +This module is maintained in the rra-c-util package. The current version is +available from L. + +=cut diff --git a/tests/tap/perl/Test/RRA.pm b/tests/tap/perl/Test/RRA.pm index bb7de7d..8608e31 100644 --- a/tests/tap/perl/Test/RRA.pm +++ b/tests/tap/perl/Test/RRA.pm @@ -5,31 +5,6 @@ # by both C packages with Automake and by stand-alone Perl modules. See # Test::RRA::Automake for additional functions specifically for C Automake # distributions. -# -# The canonical version of this file is maintained in the rra-c-util package, -# which can be found at . -# -# Written by Russ Allbery -# Copyright 2013, 2014 -# 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. package Test::RRA; @@ -56,7 +31,7 @@ BEGIN { # This version should match the corresponding rra-c-util release, but with # two digits for the minor version, including a leading zero if necessary, # so that it will sort properly. - $VERSION = '5.05'; + $VERSION = '5.10'; } # Skip this test unless author tests are requested. Takes a short description @@ -153,7 +128,7 @@ __END__ =for stopwords Allbery Allbery's DESC bareword sublicense MERCHANTABILITY NONINFRINGEMENT -rra-c-util +rra-c-util CPAN =head1 NAME @@ -176,46 +151,45 @@ Test::RRA - Support functions for Perl tests =head1 DESCRIPTION -This module collects utility functions that are useful for Perl test -scripts. It assumes Russ Allbery's Perl module layout and test -conventions and will only be useful for other people if they use the -same conventions. +This module collects utility functions that are useful for Perl test scripts. +It assumes Russ Allbery's Perl module layout and test conventions and will +only be useful for other people if they use the same conventions. =head1 FUNCTIONS -None of these functions are imported by default. The ones used by a -script should be explicitly imported. +None of these functions are imported by default. The ones used by a script +should be explicitly imported. =over 4 =item skip_unless_author(DESC) -Checks whether AUTHOR_TESTING is set in the environment and skips the -whole test (by calling C from Test::More) if it is not. -DESC is a description of the tests being skipped. A space and C will be appended to it and used as the skip reason. +Checks whether AUTHOR_TESTING is set in the environment and skips the whole +test (by calling C from Test::More) if it is not. DESC is a +description of the tests being skipped. A space and C +will be appended to it and used as the skip reason. =item skip_unless_automated(DESC) -Checks whether AUTHOR_TESTING, AUTOMATED_TESTING, or RELEASE_TESTING are -set in the environment and skips the whole test (by calling C from Test::More) if they are not. This should be used by tests -that should not run during end-user installs of the module, but which -should run as part of CPAN smoke testing and release testing. +Checks whether AUTHOR_TESTING, AUTOMATED_TESTING, or RELEASE_TESTING are set +in the environment and skips the whole test (by calling C from +Test::More) if they are not. This should be used by tests that should not run +during end-user installs of the module, but which should run as part of CPAN +smoke testing and release testing. DESC is a description of the tests being skipped. A space and C will be appended to it and used as the skip reason. =item use_prereq(MODULE[, VERSION][, IMPORT ...]) -Attempts to load MODULE with the given VERSION and import arguments. If -this fails for any reason, the test will be skipped (by calling C from Test::More) with a skip reason saying that MODULE is -required for the test. +Attempts to load MODULE with the given VERSION and import arguments. If this +fails for any reason, the test will be skipped (by calling C +from Test::More) with a skip reason saying that MODULE is required for the +test. VERSION will be passed to C as a version bareword if it looks like a -version number. The remaining IMPORT arguments will be passed as the -value of an array. +version number. The remaining IMPORT arguments will be passed as the value of +an array. =back @@ -228,33 +202,33 @@ Russ Allbery Copyright 2013, 2014 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: +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 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. +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. =head1 SEE ALSO Test::More(3), Test::RRA::Automake(3), Test::RRA::Config(3) -This module is maintained in the rra-c-util package. The current version -is available from L. +This module is maintained in the rra-c-util package. The current version is +available from L. -The functions to control when tests are run use environment variables -defined by the L. =cut diff --git a/tests/tap/perl/Test/RRA/Automake.pm b/tests/tap/perl/Test/RRA/Automake.pm index a064ed9..79e825c 100644 --- a/tests/tap/perl/Test/RRA/Automake.pm +++ b/tests/tap/perl/Test/RRA/Automake.pm @@ -9,31 +9,6 @@ # # All the functions here assume that BUILD and SOURCE are set in the # environment. This is normally done via the C TAP Harness runtests wrapper. -# -# The canonical version of this file is maintained in the rra-c-util package, -# which can be found at . -# -# Written by Russ Allbery -# Copyright 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. package Test::RRA::Automake; @@ -87,7 +62,7 @@ BEGIN { # This version should match the corresponding rra-c-util release, but with # two digits for the minor version, including a leading zero if necessary, # so that it will sort properly. - $VERSION = '5.05'; + $VERSION = '5.10'; } # Perl directories to skip globally for perl_dirs. We ignore the perl @@ -126,7 +101,15 @@ sub automake_setup { my ($vol, $dirs) = File::Spec->splitpath($start, 1); my @dirs = File::Spec->splitdir($dirs); pop(@dirs); - if ($dirs[-1] eq File::Spec->updir) { + + # Simplify relative paths at the end of the directory. + my $ups = 0; + my $i = $#dirs; + while ($i > 2 && $dirs[$i] eq File::Spec->updir) { + $ups++; + $i--; + } + for (1 .. $ups) { pop(@dirs); pop(@dirs); } @@ -196,7 +179,7 @@ sub perl_dirs { # Build the list of top-level directories to test. opendir(my $rootdir, q{.}) or BAIL_OUT("cannot open .: $!"); - my @dirs = grep { -d $_ && !$skip{$_} } readdir($rootdir); + my @dirs = grep { -d && !$skip{$_} } readdir($rootdir); closedir($rootdir); @dirs = File::Spec->no_upwards(@dirs); @@ -288,8 +271,8 @@ END { __END__ =for stopwords -Allbery Automake Automake-aware Automake-based rra-c-util ARGS -subdirectories sublicense MERCHANTABILITY NONINFRINGEMENT umask +Allbery Automake Automake-aware Automake-based rra-c-util ARGS subdirectories +sublicense MERCHANTABILITY NONINFRINGEMENT umask =head1 NAME @@ -309,75 +292,71 @@ Test::RRA::Automake - Automake-aware support functions for Perl tests =head1 DESCRIPTION This module collects utility functions that are useful for test scripts -written in Perl and included in a C Automake-based package. They assume -the layout of a package that uses rra-c-util and C TAP Harness for the -test structure. +written in Perl and included in a C Automake-based package. They assume the +layout of a package that uses rra-c-util and C TAP Harness for the test +structure. Loading this module will also add the directories C and -C to the Perl library search path, relative to BUILD if -that environment variable is set. This is harmless for C Automake -projects that don't contain an embedded Perl module, and for those -projects that do, this will allow subsequent C calls to find modules -that are built as part of the package build process. +C to the Perl library search path, relative to BUILD if that +environment variable is set. This is harmless for C Automake projects that +don't contain an embedded Perl module, and for those projects that do, this +will allow subsequent C calls to find modules that are built as part of +the package build process. The automake_setup() function should be called before calling any other functions provided by this module. =head1 FUNCTIONS -None of these functions are imported by default. The ones used by a -script should be explicitly imported. On failure, all of these functions -call BAIL_OUT (from Test::More). +None of these functions are imported by default. The ones used by a script +should be explicitly imported. On failure, all of these functions call +BAIL_OUT (from Test::More). =over 4 =item automake_setup([ARGS]) -Verifies that the BUILD and SOURCE environment variables are set and -then changes directory to the top of the source tree (which is one -directory up from the SOURCE path, since SOURCE points to the top of -the tests directory). +Verifies that the BUILD and SOURCE environment variables are set and then +changes directory to the top of the source tree (which is one directory up +from the SOURCE path, since SOURCE points to the top of the tests directory). -If ARGS is given, it should be a reference to a hash of configuration -options. Only one option is supported: C. If it is set -to a true value, automake_setup() changes directories to the top of -the build tree instead. +If ARGS is given, it should be a reference to a hash of configuration options. +Only one option is supported: C. If it is set to a true value, +automake_setup() changes directories to the top of the build tree instead. =item perl_dirs([ARGS]) Returns a list of directories that may contain Perl scripts that should be -tested by test scripts that test all Perl in the source tree (such as -syntax or coding style checks). The paths will be simple directory names -relative to the current directory or two-part directory names under the -F directory. (Directories under F are broken out separately -since it's common to want to apply different policies to different -subdirectories of F.) - -If ARGS is given, it should be a reference to a hash of configuration -options. Only one option is supported: C, whose value should be a -reference to an array of additional top-level directories or directories -starting with C that should be skipped. +tested by test scripts that test all Perl in the source tree (such as syntax +or coding style checks). The paths will be simple directory names relative to +the current directory or two-part directory names under the F +directory. (Directories under F are broken out separately since it's +common to want to apply different policies to different subdirectories of +F.) + +If ARGS is given, it should be a reference to a hash of configuration options. +Only one option is supported: C, whose value should be a reference to an +array of additional top-level directories or directories starting with +C that should be skipped. =item test_file_path(FILE) -Given FILE, which should be a relative path, locates that file relative to -the test directory in either the source or build tree. FILE will be -checked for relative to the environment variable BUILD first, and then -relative to SOURCE. test_file_path() returns the full path to FILE or -calls BAIL_OUT if FILE could not be found. +Given FILE, which should be a relative path, locates that file relative to the +test directory in either the source or build tree. FILE will be checked for +relative to the environment variable BUILD first, and then relative to SOURCE. +test_file_path() returns the full path to FILE or calls BAIL_OUT if FILE could +not be found. =item test_tmpdir() -Create a temporary directory for tests to use for transient files and -return the path to that directory. The directory is created relative to -the BUILD environment variable, which must be set. Permissions on the -directory are set using the current umask. test_tmpdir() returns the full -path to the temporary directory or calls BAIL_OUT if it could not be -created. +Create a temporary directory for tests to use for transient files and return +the path to that directory. The directory is created relative to the BUILD +environment variable, which must be set. Permissions on the directory are set +using the current umask. test_tmpdir() returns the full path to the temporary +directory or calls BAIL_OUT if it could not be created. -The directory is automatically removed if possible on program exit. -Failure to remove the directory on exit is reported with diag() and -otherwise ignored. +The directory is automatically removed if possible on program exit. Failure +to remove the directory on exit is reported with diag() and otherwise ignored. =back @@ -387,35 +366,36 @@ Russ Allbery =head1 COPYRIGHT AND LICENSE -Copyright 2013 The Board of Trustees of the Leland Stanford Junior -University +Copyright 2014, 2015 Russ Allbery + +Copyright 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: +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 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. +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. =head1 SEE ALSO Test::More(3), Test::RRA(3), Test::RRA::Config(3) +This module is maintained in the rra-c-util package. The current version is +available from L. + The C TAP Harness test driver and libraries for TAP-based C testing are available from L. -This module is maintained in the rra-c-util package. The current version -is available from L. - =cut diff --git a/tests/tap/perl/Test/RRA/Config.pm b/tests/tap/perl/Test/RRA/Config.pm index 3e77650..a5b0d0d 100644 --- a/tests/tap/perl/Test/RRA/Config.pm +++ b/tests/tap/perl/Test/RRA/Config.pm @@ -4,9 +4,6 @@ # configuration file to store some package-specific data. This module loads # that configuration and provides the namespace for the configuration # settings. -# -# The canonical version of this file is maintained in the rra-c-util package, -# which can be found at . package Test::RRA::Config; @@ -30,22 +27,23 @@ BEGIN { @ISA = qw(Exporter); @EXPORT_OK = qw( $COVERAGE_LEVEL @COVERAGE_SKIP_TESTS @CRITIC_IGNORE $LIBRARY_PATH - $MINIMUM_VERSION %MINIMUM_VERSION @POD_COVERAGE_EXCLUDE @STRICT_IGNORE - @STRICT_PREREQ + $MINIMUM_VERSION %MINIMUM_VERSION @MODULE_VERSION_IGNORE + @POD_COVERAGE_EXCLUDE @STRICT_IGNORE @STRICT_PREREQ ); # This version should match the corresponding rra-c-util release, but with # two digits for the minor version, including a leading zero if necessary, # so that it will sort properly. - $VERSION = '5.05'; + $VERSION = '5.10'; } # If BUILD or SOURCE are set in the environment, look for data/perl.conf under # those paths for a C Automake package. Otherwise, look in t/data/perl.conf -# for a standalone Perl module. Don't use Test::RRA::Automake since it may -# not exist. +# for a standalone Perl module or tests/data/perl.conf for Perl tests embedded +# in a larger distribution. Don't use Test::RRA::Automake since it may not +# exist. our $PATH; -for my $base ($ENV{BUILD}, $ENV{SOURCE}, 't') { +for my $base ($ENV{BUILD}, $ENV{SOURCE}, 't', 'tests') { next if !defined($base); my $path = "$base/data/perl.conf"; if (-r $path) { @@ -64,6 +62,7 @@ our @CRITIC_IGNORE; our $LIBRARY_PATH; our $MINIMUM_VERSION = '5.008'; our %MINIMUM_VERSION; +our @MODULE_VERSION_IGNORE; our @POD_COVERAGE_EXCLUDE; our @STRICT_IGNORE; our @STRICT_PREREQ; @@ -78,8 +77,8 @@ if (!do($PATH)) { __END__ =for stopwords -Allbery rra-c-util Automake perlcritic .libs namespace subdirectory -sublicense MERCHANTABILITY NONINFRINGEMENT +Allbery rra-c-util Automake perlcritic .libs namespace subdirectory sublicense +MERCHANTABILITY NONINFRINGEMENT regexes =head1 NAME @@ -92,19 +91,17 @@ Test::RRA::Config - Perl test configuration =head1 DESCRIPTION -Test::RRA::Config encapsulates per-package configuration for generic Perl -test programs that are shared between multiple packages using the -rra-c-util infrastructure. It handles locating and loading the test -configuration file for both C Automake packages and stand-alone Perl -modules. +Test::RRA::Config encapsulates per-package configuration for generic Perl test +programs that are shared between multiple packages using the rra-c-util +infrastructure. It handles locating and loading the test configuration file +for both C Automake packages and stand-alone Perl modules. Test::RRA::Config looks for a file named F relative to the -root of the test directory. That root is taken from the environment -variables BUILD or SOURCE (in that order) if set, which will be the case -for C Automake packages using C TAP Harness. If neither is set, it -expects the root of the test directory to be a directory named F -relative to the current directory, which will be the case for stand-alone -Perl modules. +root of the test directory. That root is taken from the environment variables +BUILD or SOURCE (in that order) if set, which will be the case for C Automake +packages using C TAP Harness. If neither is set, it expects the root of the +test directory to be a directory named F relative to the current directory, +which will be the case for stand-alone Perl modules. The following variables are supported: @@ -112,70 +109,75 @@ The following variables are supported: =item $COVERAGE_LEVEL -The coverage level achieved by the test suite for Perl test coverage -testing using Test::Strict, as a percentage. The test will fail if test -coverage less than this percentage is achieved. If not given, defaults -to 100. +The coverage level achieved by the test suite for Perl test coverage testing +using Test::Strict, as a percentage. The test will fail if test coverage less +than this percentage is achieved. If not given, defaults to 100. =item @COVERAGE_SKIP_TESTS Directories under F whose tests should be skipped when doing coverage -testing. This can be tests that won't contribute to coverage or tests -that don't run properly under Devel::Cover for some reason (such as ones -that use taint checking). F and F