#!/usr/bin/perl # This script automatically runs: # # monkeysphere-authentication update-users # # every time it detects a change in an authorized_keys or authorized_user_ids # file. The update-users command operates on the username that owns the file # that was updated. # # The list of files to monitor is generated from the AUTHORIZED_USER_IDS and # RAW_AUTHORIZED_KEYS variables found in # /etc/monkeysphere/monkeysphere-authentication.conf and expanded using a list # of users on the system. # # Additionally, the /var/lib/monkeysphere/user-update/lastchange file is # monitored. If a change is made to that file, the list of files to monitor is # re-generated based on a fresh listing of users. If you run a hook on user # creation and deletion that generates a file in this directory, you can ensure # that the list of files to monitor is always up-to-date. # # On debian system you can install required perl modules with: aptitude install # libfile-changenotify-perl libfile-spec-perl libconfig-general-perl # # This script is designed to run at system start and should be run with root # privileges. # # File::ChangeNotify is cross platform - it will choose a sub class for # monitoring file system changes appropriate to your operating system (if you # are running Linux, liblinux-inotify2-perl is recommended). # FIXME: does this handle revocations and re-keying? if a sysadmin # switches over to this arrangement, how will the system check for # revocations? Scheduling a simple gpg --refresh should handle # revocations. I'm not sure how to best handle re-keyings. use strict; use warnings; use File::ChangeNotify; use File::Basename; use File::Spec; use Config::General; my $user_update_file = '/var/lib/monkeysphere/user-update/lastchange'; my %watch_files; my $debug = 0; if (defined($ENV{MONKEYSPHERE_LOG_LEVEL}) && $ENV{MONKEYSPHERE_LOG_LEVEL} =~ /^debug/i) { $debug = 1; } sub debug { printf STDERR @_ if ($debug eq 1); } sub set_watch_files() { my %key_file_locations = get_key_file_locations(); # get list of users on the system while(my ($name, $passwd, $uid, $gid, $gcos, $dir, $shell, $home) = getpwent()) { while (my ($key, $file) = each (%key_file_locations)) { $file =~ s/%h/$home/; $file =~ s/%u/$name/; $watch_files{ $file } = $name; } } endpwent(); $watch_files{ $user_update_file } = ''; } sub get_key_file_locations { # set defaults my %key_file_locations; $key_file_locations{ 'authorized_user_ids' } = '%h/.monkeysphere/authorized_user_ids'; $key_file_locations{ 'authorized_keys' } = '%h/.ssh/authorized_keys'; # check monkeysphere-authentication configuration my $config_file = '/etc/monkeysphere/monkeysphere-authentication.conf'; if (-f $config_file) { if (-r $config_file) { my %config; %config = Config::General::ParseConfig($config_file); if (exists $config{'AUTHORIZED_USER_IDS'}) { $key_file_locations{'authorized_user_ids'} = $config{'AUTHORIZED_USER_IDS'}; } if (exists $config{'RAW_AUTHORIZED_KEYS'}) { $key_file_locations{'authorized_keys'} = $config{'RAW_AUTHORIZED_KEYS'}; } } } return %key_file_locations; } sub get_watcher { my @filters; my @dirs; set_watch_files(); for my $file (%watch_files) { my $dir = dirname($file); if ( -d $dir && !grep $_ eq $dir, @dirs ) { debug("Watching dir: %s\n", $dir); push(@dirs,$dir); my $file = basename($file); if ( !grep $_ eq $file, @filters ) { $file = quotemeta($file); debug("Adding file filter: %s\n", $file); push(@filters,$file); } } } # create combined file filters to limit our monitor my $filter = '^(' . join("|",@filters) . ')$'; # return a watcher object return my $watcher = File::ChangeNotify->instantiate_watcher ( directories => [ @dirs ], filter => qr/$filter/, ); } sub watch { my $watcher = get_watcher(); while ( my @events = $watcher->wait_for_events() ) { my %users; for my $event (@events) { if($event->path eq "$user_update_file") { debug("Reloading user list\n"); $watcher = get_watcher(); } else { # if user deleted, file might not exist # FIXME - m-a u returns an error if the username # doesn't exist. It should silently ensure that # the generated authorized_keys file is deleted. # Once it's fixed, we should execute even if the # file is gone. if( -f $event->path) { my $username = $watch_files { $event->path }; $users{ $username } = 1; } } } for ((my $username) = each(%users)) { debug("Updating user: %s\n", $username); # FIXME: this call blocks until m-a u finishes running, i think. # what happens if other changes occur in the meantime? Can we # rate-limit this? Could we instead spawn child processes that # run this command directly? system('monkeysphere-authentication', 'update-users', $username); } } } watch();