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(-) (limited to 'perl/lib/Wallet/Policy') 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 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(-) (limited to 'perl/lib/Wallet/Policy') 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 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(-) (limited to 'perl/lib/Wallet/Policy') 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 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(+) (limited to 'perl/lib/Wallet/Policy') 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 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(-) (limited to 'perl/lib/Wallet/Policy') 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