- #!/usr/bin/perl
- # This script automatically runs:
- #
- # monkeysphere-authentication update-users <user>
- #
- # 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 ) {
- debug("Adding file filter: %s\n", $file);
- push(@filters,$file);
- }
- }
- }
- # create combined file filters to limit our monitor
- # FIXME: what if the elements of @filters have some regex characters
- # in them? this seems like it could match all kinds of crazy stuff
- 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();
|