diff options
author | Russ Allbery <rra@stanford.edu> | 2008-03-12 20:56:34 +0000 |
---|---|---|
committer | Russ Allbery <rra@stanford.edu> | 2008-03-12 20:56:34 +0000 |
commit | c2c0c26a6f4c0c2b47c9a79d4055dcc38c09a79f (patch) | |
tree | b061095dee9cbd82f22b07e45911b37efd73f60a /contrib | |
parent | f243bc74eb5012687f6fdf892d0063aa382497c8 (diff) |
Another sample script from Stanford's wallet migration.
Diffstat (limited to 'contrib')
-rwxr-xr-x | contrib/convert-srvtab-db | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/contrib/convert-srvtab-db b/contrib/convert-srvtab-db new file mode 100755 index 0000000..74b19a7 --- /dev/null +++ b/contrib/convert-srvtab-db @@ -0,0 +1,355 @@ +#!/usr/bin/perl -w +our $ID = q$Id$; +# +# convert-srvtab-db -- Converts a leland_srvtab database to wallet +# +# Written by Russ Allbery <rra@stanford.edu> +# Copyright 2008 Board of Trustees, Leland Stanford Jr. University +# +# See LICENSE for licensing terms. + +############################################################################## +# Modules and site configuration +############################################################################## + +require 5.006; +use strict; + +use Getopt::Long qw(GetOptions); +use Wallet::ACL; +use Wallet::Server; + +# The identity of the user who will be creating the wallet database entries, +# for logging purposes. +our $IDENTITY = 'rra/root@stanford.edu'; + +# The path to the mappings from CGI principals to home directories, used for +# identifying user- and group-based CGI principals that are no longer being +# used. +our $CGI = '/afs/ir/service/etc/passwd.cgi.lsdb'; +our %CGI; + +# The path to the allow-extract file for the Kerberos KDCs, listing what +# principals can be cached. This is used to check for principals marked +# cached in the srvtab database that aren't configured to be cachable on the +# KDC side. +our $CACHED = '/home/eagle/work/puppet/services/s_kdc/files/prod' + . '/etc/krb5kdc/allow-extract'; +our @CACHED; + +# The list of principal types that should be treated as host-based. +our %HOST_BASED = map { $_ => 1 } + qw(HTTP afpserver cifs ftp ident imap ldap lpr nfs pop rcmd sieve smtp + uniengd webauth); + +# Additional allowable principal types. Anything not on this list, not in +# %HOST_BASED, and not a CGI principal will not be converted. +our %ALLOWED = map { $_ => 1 } qw(service); + +# The domain to add to form host-based Kerberos v5 principals from Kerberos v4 +# principals. +our $DOMAIN = 'stanford.edu'; + +# The realm to add when checking against the KDC whitelist. +our $REALM = 'stanford.edu'; + +# ACL mappings. By default, ACL groups in the srvtab database are mapped to +# ACL groups in the wallet with the same name, but the following groups are +# exceptions. +our %ACL_MAPPING = + ('group/cgi' => 'service/www', + 'group/leland' => 'ADMIN', + 'group/netstaff-sysad' => 'ADMIN', + 'group/oss' => 'ADMIN', + 'group/ti-ops' => 'ADMIN', + 'group/tss-cs' => 'ADMIN'); + +# Whether to write the output into the wallet database. Set to 1 if -w was +# given on the command line. +our $WRITE = 0; + +############################################################################## +# Load data files +############################################################################## + +# Load the CGI password file into the %CGI hash. The keys are CGI principals +# and the values are home directory paths. +sub load_cgi { + open (CGI, '<', $CGI) or die "$0: cannot open $CGI: $!\n"; + local $_; + while (<CGI>) { + my ($user, $home) = (split ':')[0,5]; + $CGI{$user} = $home; + } + close CGI; +} + +# Load the regexes permitting extraction of existing keys, used to check +# cached keytabs. +sub load_cached { + open (CACHED, '<', $CACHED) or die "$0: cannot open $CACHED: $!"; + local $_; + while (<CACHED>) { + next if /^\s*\#/; + next if /^\s*$/; + s/^\s+//; + s/\s+$//; + s/\s*\#.*//; + push (@CACHED, qr/$_/); + } + close CACHED; +} + +############################################################################## +# Principal and ACL conversion +############################################################################## + +# Convert a Kerberos v4 principal to the corresponding Kerberos v5 principal +# name. This is somewhat special-cased for the types of principals that we +# had in the srvtab database at Stanford. +sub convert_principal { + my ($principal) = @_; + my ($type, $instance) = split (/\./, $principal, 2); + $type = 'host' if $type eq 'rcmd'; + if (!$instance) { + $principal = $type; + } elsif ($HOST_BASED{$type}) { + $principal = "$type/$instance.$DOMAIN"; + } else { + $principal = "$type/$instance"; + } + return $principal; +} + +############################################################################## +# Database dump parsing +############################################################################## + +# Given a reference to a hash and the file name of a srvtab database dump, +# load that dump into the hash. The keys will be Kerberos v4 principal names +# and the values will be hashes of key/value pairs from the database. +sub load_dump { + my ($db, $dump) = @_; + open (DUMP, '<', $dump) or die "$0: cannot open $dump: $!\n"; + local $_; + my $last = ''; + while (<DUMP>) { + if (/^(\S+)$/) { + $last = $1; + } elsif (/^\s*(\S+): (\S+)$/) { + $db->{$last}{$1} = $2; + } + } + close DUMP; +} + +# Given the hash containing the srvtab database, delete all entries that have +# never been downloaded and were created more than a week ago. These can be +# re-requested if they're really needed. Also delete all entries that are +# hopelessly misnamed and cannot be moved to Kerberos v5, and all entries for +# host-based principals, since we're letting the wallet autocreation handle +# those. +sub clean_database { + my ($db) = @_; + my @delete; + for my $principal (keys %$db) { + my $entry = $db->{$principal}; + + # We were only caching the mail-related keytabs and LDAP keytabs so + # that users didn't see Kerberos problems when the kvno changed. + # wallet now deals correctly with those, so don't treat any of those + # as cached. + my ($type, $instance) = split (/\./, $principal, 2); + if ($type =~ /^(imap|ldap|pop|sieve|smtp)$/) { + delete $entry->{cached}; + } + + # Now check for principals we don't care about. + unless ( exists $entry->{'srvkeytab-generated-by'} + or exists $entry->{'srvtab-generated-by'} + or exists $entry->{'cached'} + or ($entry->{'created-on'} > time - (7 * 24 * 3600))) { + push (@delete, $principal); + next; + } + next if ($instance eq 'cgi' and $type ne 'rcmd'); + if (!$instance) { + push (@delete, $principal); + } elsif ($HOST_BASED{$type} and not exists $entry->{'cached'}) { + push (@delete, $principal); + } elsif (not $HOST_BASED{$type} and not $ALLOWED{$type}) { + push (@delete, $principal); + } + } + delete @$db{@delete}; +} + +############################################################################## +# Consistency checking +############################################################################## + +# Scan the provided database for anomolies. Report all of the srvtab database +# objects with anomolies to standard output. +sub check_database { + my ($db) = @_; + load_cgi; + load_cached; + for my $principal (sort keys %$db) { + my ($type, $instance) = split (/\./, $principal, 2); + if ($instance eq 'cgi' and $type ne 'rcmd') { + if (not $CGI{$type}) { + print "$principal does not have CGI service\n"; + } + } + my $entry = $db->{$principal}; + my @user_keys = grep { /^srvtab-user-/ } keys %$entry; + my @group_keys = grep { /^srvtab-acl-/ } keys %$entry; + my @users = map { $entry->{$_} } @user_keys; + my @groups = map { $entry->{$_} } @group_keys; + if (@users and @groups) { + print "$principal has both users and groups\n"; + } + if (@groups > 1) { + print "$principal has multiple groups\n"; + } + if ($instance eq 'cgi' and (@users || "@groups" ne 'group/cgi')) { + print "$principal is CGI principal with weird ACLs\n"; + } + if ($entry->{cached}) { + my $k5 = convert_principal ($principal) . '@' . $REALM; + my $okay; + for my $regex (@CACHED) { + if ($k5 =~ /$regex/) { + $okay = 1; + last; + } + } + print "$principal is cached but not in the KDC config\n" + unless $okay; + } + } +} + +############################################################################## +# Database conversion +############################################################################## + +# Iterate through the database and convert every entry that doesn't already +# exist in the wallet. +sub convert_database { + my ($db) = @_; + my %acls; + my $server = Wallet::Server->new ($IDENTITY, 'localhost'); + for my $principal (sort keys %$db) { + my $entry = $db->{$principal}; + my $k5 = convert_principal ($principal); + if ($server->check ('keytab', $k5)) { + print "skipping already created principal $k5\n"; + next; + } + my @user_keys = grep { /^srvtab-user-/ } keys %$entry; + my @group_keys = grep { /^srvtab-acl-/ } keys %$entry; + my @users = sort map { $entry->{$_} } @user_keys; + my @groups = sort map { $entry->{$_} } @group_keys; + for my $user (@users) { + $user =~ s/\.?\@.*//; + $user =~ s,\.,/,; + } + my ($owner, $group); + if (@groups) { + $owner = $ACL_MAPPING{$groups[0]} || $groups[0]; + } elsif (@users) { + if ($acls{"@users"}) { + $owner = $acls{"@users"}; + } elsif (@users == 1) { + $group = $users[0]; + $group =~ s,/.*,,; + $group = "user/$group"; + } else { + $group = $principal; + $group =~ s/^[^.]+\.//; + $group = "group/$group"; + } + } + if ($group) { + my $create = 1; + my $acl = eval { Wallet::ACL->new ($group, $server->dbh) }; + if (defined $acl) { + my @entries = $acl->list; + if (grep { $_->[0] ne 'krb5' } @entries) { + die "ACL $group exists with unknown types\n"; + } + @entries = map { $_->[1] } @entries; + for (@entries) { s/\@\Q$DOMAIN// } + unless ("@entries" eq "@users") { + die "ACL $group exists with different entries\n"; + } + $create = 0; + } elsif ($@ !~ /^ACL \S+ not found/) { + die "unknown ACL error on $group: $@\n"; + } + $owner = $group; + $acls{"@users"} = $group; + if ($WRITE && $create) { + $server->acl_create ($group) or die $server->error, "\n"; + for my $user (@users) { + $server->acl_add ($group, 'krb5', "$user\@$REALM") + or die $server->error, "\n"; + } + } elsif ($create) { + print "wallet create acl $group\n"; + for my $user (@users) { + print "wallet add acl $group krb5 $user\@$REALM\n"; + } + } + } + if ($WRITE) { + $server->create ('keytab', $k5) or die $server->error, "\n"; + $server->owner ('keytab', $k5, $owner) + or die $server->error, "\n"; + if ($entry->{cached}) { + $server->flag_set ('keytab', $k5, 'unchanging') + or die $server->error, "\n"; + } + } else { + print "wallet create keytab $k5\n"; + print "wallet owner keytab $k5 $owner\n"; + print "wallet flag set keytab $k5 unchanging\n" + if $entry->{cached}; + } + } +} + +############################################################################## +# Main routine +############################################################################## + +# Read in command-line options. +my ($audit, $help); +Getopt::Long::config ('no_ignore_case', 'bundling'); +GetOptions ('a|audit' => \$audit, + 'h|help' => \$help, + 'w|write' => \$WRITE) or exit 1; +if ($help) { + print "Feeding myself to perldoc, please wait....\n"; + exec ('perldoc', '-t', $0); +} + +# Clean up $0 for error reporting. +$0 =~ s%.*/%%; + +# Get the dump file. +die "$0: no srvtab database dump file specified" unless @ARGV; +die "$0: too many arguments" if @ARGV > 1; +my ($dump) = @ARGV; +my %db; +load_dump (\%db, $dump); +clean_database (\%db); +print 'Saw ', scalar (keys %db), " total principals\n"; + +# Perform the requested operation. +if ($audit) { + check_database (\%db); +} else { + convert_database (\%db); +} |