summaryrefslogtreecommitdiff
path: root/examples/monkeysphere-monitor-keys
blob: f6328dfc40ee805fad3e15e5bbbc3c721e912ab7 (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 $debug = 0;
  42. if (defined($ENV{MONKEYSPHERE_LOG_LEVEL}) &&
  43. $ENV{MONKEYSPHERE_LOG_LEVEL} =~ /^debug/i) {
  44. $debug = 1;
  45. }
  46. sub debug {
  47. printf STDERR @_
  48. if ($debug eq 1);
  49. }
  50. sub get_watch_files() {
  51. my @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. push(@watch_files,$file);
  59. }
  60. }
  61. endpwent();
  62. push(@watch_files,$user_update_file);
  63. return @watch_files;
  64. }
  65. sub get_key_file_locations {
  66. # set defaults
  67. my %key_file_locations;
  68. $key_file_locations{ 'authorized_user_ids' } = '%h/.monkeysphere/authorized_user_ids';
  69. $key_file_locations{ 'authorized_keys' } = '%h/.ssh/authorized_keys';
  70. # check monkeysphere-authentication configuration
  71. my $config_file = '/etc/monkeysphere/monkeysphere-authentication.conf';
  72. if (-f $config_file) {
  73. if (-r $config_file) {
  74. my %config;
  75. %config = Config::General::ParseConfig($config_file);
  76. if (exists $config{'AUTHORIZED_USER_IDS'}) {
  77. $key_file_locations{'authorized_user_ids'} = $config{'AUTHORIZED_USER_IDS'};
  78. }
  79. if (exists $config{'RAW_AUTHORIZED_KEYS'}) {
  80. $key_file_locations{'authorized_keys'} = $config{'RAW_AUTHORIZED_KEYS'};
  81. }
  82. }
  83. }
  84. return %key_file_locations;
  85. }
  86. sub get_watcher {
  87. my @filters;
  88. my @dirs;
  89. my(@files) = get_watch_files();
  90. for my $file (@files) {
  91. my $dir = dirname($file);
  92. if ( -d $dir && !grep $_ eq $dir, @dirs ) {
  93. debug("Watching dir: %s\n", $dir);
  94. push(@dirs,$dir);
  95. my $file = basename($file);
  96. if ( !grep $_ eq $file, @filters ) {
  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. # FIXME: what if the elements of @filters have some regex characters
  104. # in them? this seems like it could match all kinds of crazy stuff
  105. my $filter = '^(' . join("|",@filters) . ')$';
  106. # return a watcher object
  107. return my $watcher =
  108. File::ChangeNotify->instantiate_watcher
  109. ( directories => [ @dirs ],
  110. filter => qr/$filter/,
  111. );
  112. }
  113. sub watch {
  114. my $watcher = get_watcher();
  115. while ( my @events = $watcher->wait_for_events() ) {
  116. my @users;
  117. for my $event (@events) {
  118. if($event->path eq "$user_update_file") {
  119. debug("Reloading user list\n");
  120. $watcher = get_watcher();
  121. } else {
  122. # if user deleted, file might not exist
  123. if( -f $event->path) {
  124. # FIXME: how is this choosing a username? What if the
  125. # sysadmin controls these files instead of the user?
  126. # (e.g. /etc/monkeysphere/authorized_user_ids/%u) then won't
  127. # the owner be the superuser each time?
  128. # Is there some more clever way that we can get back to the
  129. # user from the path itself? maybe we store a lookup table
  130. # when we're generating the path list and refer back to it?
  131. my $username = getpwuid((stat($event->path))[4]);
  132. # FIXME: this seems like it is trying to treat an array as a
  133. # set. Maybe it'd be better to use the keys of a hash (or
  134. # hashref) instead?
  135. if ( !grep $_ eq $username, @users ) {
  136. push(@users,$username);
  137. }
  138. }
  139. }
  140. }
  141. for my $user (@users) {
  142. debug("Updating user: %s\n", $user);
  143. # FIXME: this call blocks until m-a u finishes running, i think.
  144. # what happens if other changes occur in the meantime? Can we
  145. # rate-limit this? Could we instead spawn child processes that
  146. # run this command directly?
  147. system('monkeysphere-authentication', 'update-users', $user);
  148. }
  149. }
  150. }
  151. watch();