summaryrefslogtreecommitdiff
path: root/examples/monkeysphere-monitor-keys
blob: c5d0a0f8cd97b1f0b91f814a97145f96a51100db (plain)
  1. #!/usr/bin/perl
  2. # This script automatically runs:
  3. #
  4. # monkeysphere-authentication update-users <user>
  5. #
  6. # every time it detects a change in an authorized_keys or authorized_user_ids
  7. # file. The update-users command operates on the username that owns the file
  8. # that was updated.
  9. #
  10. # The list of files to monitor is generated from the AUTHORIZED_USER_IDS and
  11. # RAW_AUTHORIZED_KEYS variables found in
  12. # /etc/monkeysphere/monkeysphere-authentication.conf and expanded using a list
  13. # of users on the system.
  14. #
  15. # Additionally, the /var/lib/monkeysphere/user-update/lastchange file is
  16. # monitored. If a change is made to that file, the list of files to monitor is
  17. # re-generated based on a fresh listing of users. If you run a hook on user
  18. # creation and deletion that generates a file in this directory, you can ensure
  19. # that the list of files to monitor is always up-to-date.
  20. #
  21. # On debian system you can install required perl modules with: aptitude install
  22. # libfile-changenotify-perl libfile-spec-perl libconfig-general-perl
  23. #
  24. # This script is designed to run at system start and should be run with root
  25. # privileges.
  26. #
  27. # File::ChangeNotify is cross platform - it will choose a sub class for
  28. # monitoring file system changes appropriate to your operating system (if you
  29. # are running Linux, liblinux-inotify2-perl is recommended).
  30. # FIXME: does this handle revocations and re-keying? if a sysadmin
  31. # switches over to this arrangement, how will the system check for
  32. # revocations? Scheduling a simple gpg --refresh should handle
  33. # revocations. I'm not sure how to best handle re-keyings.
  34. use strict;
  35. use warnings;
  36. use File::ChangeNotify;
  37. use File::Basename;
  38. use File::Spec;
  39. use Config::General;
  40. my $user_update_file = '/var/lib/monkeysphere/user-update/lastchange';
  41. my %watch_files;
  42. my $debug = 0;
  43. if (defined($ENV{MONKEYSPHERE_LOG_LEVEL}) &&
  44. $ENV{MONKEYSPHERE_LOG_LEVEL} =~ /^debug/i) {
  45. $debug = 1;
  46. }
  47. sub debug {
  48. printf STDERR @_
  49. if ($debug eq 1);
  50. }
  51. sub set_watch_files() {
  52. my %key_file_locations = get_key_file_locations();
  53. # get list of users on the system
  54. while(my ($name, $passwd, $uid, $gid, $gcos, $dir, $shell, $home) = getpwent()) {
  55. while (my ($key, $file) = each (%key_file_locations)) {
  56. $file =~ s/%h/$home/;
  57. $file =~ s/%u/$name/;
  58. $watch_files{ $file } = $name;
  59. }
  60. }
  61. endpwent();
  62. $watch_files{ $user_update_file } = '';
  63. }
  64. sub get_key_file_locations {
  65. # set defaults
  66. my %key_file_locations;
  67. $key_file_locations{ 'authorized_user_ids' } = '%h/.monkeysphere/authorized_user_ids';
  68. $key_file_locations{ 'authorized_keys' } = '%h/.ssh/authorized_keys';
  69. # check monkeysphere-authentication configuration
  70. my $config_file = '/etc/monkeysphere/monkeysphere-authentication.conf';
  71. if (-f $config_file) {
  72. if (-r $config_file) {
  73. my %config;
  74. %config = Config::General::ParseConfig($config_file);
  75. if (exists $config{'AUTHORIZED_USER_IDS'}) {
  76. $key_file_locations{'authorized_user_ids'} = $config{'AUTHORIZED_USER_IDS'};
  77. }
  78. if (exists $config{'RAW_AUTHORIZED_KEYS'}) {
  79. $key_file_locations{'authorized_keys'} = $config{'RAW_AUTHORIZED_KEYS'};
  80. }
  81. }
  82. }
  83. return %key_file_locations;
  84. }
  85. sub get_watcher {
  86. my @filters;
  87. my @dirs;
  88. set_watch_files();
  89. for my $file (%watch_files) {
  90. my $dir = dirname($file);
  91. if ( -d $dir && !grep $_ eq $dir, @dirs ) {
  92. debug("Watching dir: %s\n", $dir);
  93. push(@dirs,$dir);
  94. my $file = basename($file);
  95. if ( !grep $_ eq $file, @filters ) {
  96. $file = quotemeta($file);
  97. debug("Adding file filter: %s\n", $file);
  98. push(@filters,$file);
  99. }
  100. }
  101. }
  102. # create combined file filters to limit our monitor
  103. my $filter = '^(' . join("|",@filters) . ')$';
  104. # return a watcher object
  105. return my $watcher =
  106. File::ChangeNotify->instantiate_watcher
  107. ( directories => [ @dirs ],
  108. filter => qr/$filter/,
  109. );
  110. }
  111. sub watch {
  112. my $watcher = get_watcher();
  113. while ( my @events = $watcher->wait_for_events() ) {
  114. my %users;
  115. for my $event (@events) {
  116. if($event->path eq "$user_update_file") {
  117. debug("Reloading user list\n");
  118. $watcher = get_watcher();
  119. } else {
  120. # if user deleted, file might not exist
  121. # FIXME - m-a u returns an error if the username
  122. # doesn't exist. It should silently ensure that
  123. # the generated authorized_keys file is deleted.
  124. # Once it's fixed, we should execute even if the
  125. # file is gone.
  126. if( -f $event->path) {
  127. my $username = $watch_files { $event->path };
  128. $users{ $username } = 1;
  129. }
  130. }
  131. }
  132. for ((my $username) = each(%users)) {
  133. debug("Updating user: %s\n", $username);
  134. # FIXME: this call blocks until m-a u finishes running, i think.
  135. # what happens if other changes occur in the meantime? Can we
  136. # rate-limit this? Could we instead spawn child processes that
  137. # run this command directly?
  138. system('monkeysphere-authentication', 'update-users', $username);
  139. }
  140. }
  141. }
  142. watch();