Bindfs

From A-Eskwiki
Jump to: navigation, search

Op een aantal plekken op het A-Eskwadraat systeem wordt BindFS gebruikt om permissies van commissies e.d. goed te houden. Eerst werd dit gedaan door de umask op 0002 te zetten, waardoor bestanden en directories altijd group writeable zijn. Dit gaat echter nog altijd fout in een aantal scenario's.


  • Sommige programma's zoals google-chrome downloaden bestanden altijd met rechten 640 als user:primary-group, ongeacht umask en of de directory de set group-id bit heeft. Bij het downloaden van bestanden direct naar een comissiemap gaat dit dan fout.
  • Een mv is in feite een rename en daarbij worden de rechten e.d. niet aangepast. Als iemand een file mv'ed van zijn homedir naar het commissieaccount heeft hij dus nog de oude rechten en kan de commissie er niet bij.
  • Bij een cp wordt er wel gekeken naar zaken als setgid bits, maar worden de rechten wel meegekopieerd, wat een probleem kan zijn als het origineel niet group-writeable is.

De enige manier om bovenstaande problemen structureel op te lossen is om deze permissies hard op file system niveau af te dwingen. Hiervoor wordt BindFS gebruikt. Dit is een Fuse file system waarmee een directory op een andere plek gemount kan worden, net zoals bij mount --bind, maar waarbij de permissies ook nog aangepast kunnen worden. Om echter te zorgen dat de permissies van bestanden dynamisch aangepast worden afhankelijk van de locatie was het echter nodig om BindFS te patchen.

Gebruikte opties van BindFS

Als we kijken naar de /etc/fstab van een systeem dat BindFS gebruikt zien we de volgende opties.

  • chmod-filter=g+rwD: Zorgt ervoor dat als er een chmod plaats vind op een file altijd de bits g+rw voor files en g+rwx voor directories geset worden. Een chmod 600 wordt dan bijvoorbeeld uitgevoerd als chmod 660 op de achtergrond.
  • create-with-perms=g+rwD: Permissies worden aangemaakt met g+rwD rechten.
  • perms=g+rwD: Bestaande bestanden worden gepresenteerd alsof ze g+rwD rechten hebben, ook al hoeft dat op de originele locatie niet zo te zijn.
  • chgrp-ignore: Negeer chgrp commando's. Dit is nodig omdat programma's als mv of google-chrome anders handmatig de group verkeerd zetten. Dit heeft wel als side effect dat handmatig rechten goedzetten dus ook niet meer kan.
  • nonempty: Zorgt ervoor dat Fuse de directory in een niet lege directory kan mounten.
  • noauto: Eerst moeten de home directories in /mnt/home gemount worden, voordat BindFS deze weer in /home kan mounten. Dit wordt gedaan door het script /usr/local/sbin/mount-dirs dat via een service gerund wordt. Deze service start pas nadat netwerk-file systems gemount zijn.

Custom BindFS patch

Met BindFS is het mogelijk om een enkele user en group te forcen, maar in ons geval willen we dit dynamisch doen, afhankelijk van de locatie van het bestand. Er is dus al code om een group-id te force'n, dus het enige wat gedaan moet worden is dit dynamisch te maken. Allereerst wordt bij het opstarten gekeken of de destination van de mount /home/iba, /home/cies, /home/bestuur of /home/extern is en alleen dan gebruiken we permission enforcement (settings.use_permission_enforcement). Er is gekozen om 4 aparte mountpoints te maken, zodat mv's nu cross-file systems zijn en dus geen rename meer zijn. Verder willen we ook geen permissies enforcen op de /home. Daarna wordt in de chown_new_file functie gekeken of de settings.use_permission_enforcement bit geset is en zoja, dan kijken we naar de directory van de file met behulp van een regex en zoeken we de bijbehorende group-id. Hierbij wordt als vuistregel gebruikt dat de naam van de top directory ook de group-name is. Dit levert problemen op als de naam van de homedir van een cie anders is dan de group-name.

@@ -63,6 +63,8 @@
 #ifdef HAVE_SETXATTR
 #include <sys/xattr.h>
 #endif
+#include <linux/limits.h>
+#include <regex.h>
 
 #include <fuse.h>
 #include <fuse_opt.h>
@@ -141,6 +143,9 @@ static struct Settings {
     int ctime_from_mtime;
     int hide_hard_links;
 
+    //Custom made for sysop
+    int use_permission_enforcement;
+
 } settings;
 
 
@@ -306,6 +311,41 @@ static int getattr_common(const char *pr
     return 0;
 }
 
+int get_gid(const char *path) {
+
+  regex_t regex;
+  int reti;
+
+  regmatch_t matches[2];
+
+//  reti = regcomp(&regex,"^/home/(cies|extern|iba|bestuur)/([^/]+)", REG_EXTENDED);
+  reti = regcomp(&regex,"^([^/]+)", REG_EXTENDED);
+  if (reti) {
+    fprintf(stderr, "Could not compile regex\n");
+    regfree(&regex);
+    return -1;
+  }
+  reti = regexec(&regex, path, 2, matches, 0);
+  int return_value = -1;
+  if (!reti) {
+    int match_size = matches[1].rm_eo - matches[1].rm_so;
+    char *matchgroup = malloc(sizeof(char) * (1+match_size));
+    matchgroup[match_size] = '\0';
+    memcpy(matchgroup,&path[matches[1].rm_so],match_size);
+    struct group *group_info = getgrnam(matchgroup);
+    if (group_info == NULL) {
+      return_value = -1;
+    } else {
+      return_value = group_info->gr_gid;
+    }
+
+    free(matchgroup);
+  }
+  regfree(&regex);
+  return return_value;
+}
+
+
 /* FIXME: another thread may race to see the old owner before the chown is done.
           Is there a scenario where this compromises security? Or application correctness? */
 static void chown_new_file(const char *path, struct fuse_context *fc, int (*chown_func)(const char*, uid_t, gid_t))
@@ -339,6 +379,14 @@ static void chown_new_file(const char *p
     if (settings.create_for_gid != -1)
         file_group = settings.create_for_gid;
 
+    //printf("Old file permission: %d %d\n",settings.use_permission_enforcement,file_group);
+    if (settings.use_permission_enforcement) {
+        
+        int new_gid = get_gid(path);
+        //printf("New file permssion: %d\n",new_gid);
+        file_group = new_gid;
+    }
+
     if ((file_owner != -1) || (file_group != -1)) {
         if (chown_func(path, file_owner, file_group) == -1) {
             DPRINTF("Failed to chown new file or directory (%d)", errno);
@@ -1670,6 +1718,13 @@ int main(int argc, char *argv[])
         bindfs_oper.listxattr = NULL;
         bindfs_oper.removexattr = NULL;
     }
+    if (strcmp(settings.mntdest,"/home/iba") || 
+        strcmp(settings.mntdest,"/home/cies") ||
+        strcmp(settings.mntdest,"/home/bestuur") ||
+        strcmp(settings.mntdest,"/home/extern")) {
+        settings.use_permission_enforcement = 1;
+    }
+    
 
     /* fuse_main will daemonize by fork()'ing. The signal handler will persist. */
     setup_signal_handling();

De code is de vinden in /root/bindfs-1.12.6 op elke machine waar BindFS gebruikt wordt. De patch is te vinden in /root/bindfs.patch.

Compilatie, debuggen, deployment

De code bevat een Makefile, dus kan gewoon gecompileerd worden met de standaard ./configure en make. De binary is een executable met de naam "bindfs". Als deze neergezet wordt in een locatie in het systeempath zoals /usr/bin/ wordt deze automatisch herkend en gebruikt als je als vstype fuse.bindfs opgeeft met mount.
mount -t fuse.bindfs
Voor compilatie zijn een standaard c compiler (zowel gcc als clang werkt) nodig en de package fuse-devel. Indien gecompileerd wordt op een CentOS 7 machine dan is deze executable niet te gebruiken op CentOS 6 vanwege een verouderde libc versie, andersom werkt wel. De executable kan ook eenvoudig gedebugd worden door een handmatige mount te doen met de -D optie.
/usr/bin/bindfs -d -o chmod-filter=g+rwD,create-with-perms=g+rwD,perms=g+rwD,chgrp-ignore,nonempty /mnt/home/ /home

In salt is de config te vinden in /srv/salt/centos7/homedirs. Dit is een losse state die het mounten van de homedirs via BindFS mogelijk maakt.