Policykit

von Simon Wilper am Mittwoch, 16. September 2020

Policykit - sudo on Steroids

Wir kennen alle sudo oder doas -- Programme, die es erlauben, Befehle als ein anderer Benutzer auf einem System, meist als root, auszuführen. Auf einem Einzelplatzsystem ist man auch oft auch gerne geneigt, seinen Benutzer in die Gruppe "wheels" zu packen und in der /etc/sudoers der Gruppe das Attribut :NOPASSWD zu verpassen und man kann durch einfaches Hinzufügen von sudo alle Befehle ausführen. "sudo make me a sandwich" halt.

Jetzt ist es vielleicht notwendig, bestimmte Befehle nur mit bestimmten Kommandozeilenargumenten zu für bestimmte Benutzer zu erlauben. Da kann man mit sudo und doas bei komplexeren Setups schon an seine Grenzen kommen.

Ein erstes einfaches Beispiel

Die Regeln ("rules") werden in /etc/polkit-1/rules.d in *.rules-Dateien gespeichert. Die Regeln werden dort als JavaScript-Programme -- ja, wirklich! -- abgespeichert.

Für den Benutzer "john" wollen wir nun erlauben, alle Befehle als Benutzer "root" auszuführen. Wir editieren die Datei /etc/pokit-1/rules.d/10-example.rules und fügen ein:

polkit.addRule( function(action, subject) {
  if ( action.id == "org.freedesktop.policykit.exec" ) {

    if (subject.user == "john") {
      return polkit.Result.YES;
    }

  }
});

Nun speichern wir und der PolicyKit-Dienst wird die Änderungen sofort detektieren und sollte sie sofort aktivieren.

In diesem Beispiel passiert nun Folgendes:

Probieren wir das Ganze einmal aus -- erst ohne die Einträge in der rules-Datei. Wir versuchen, als User "john" den Inhalt des Verzeichnisses /root aufzulisten:

> pkexec ls /root
==== AUTHENTICATING FOR org.freedesktop.policykit.exec ====
Authentication is needed to run `/usr/bin/ls' as the super user
Authenticating as: John Connor (john)
Password:

Wir werden nach einem Passwort gefragt. Fügen wir die obigen Zeilen einer rules-Datei in /etc/polkit-1/rules.de hinzu:

> pkexec ls /root
dir1  dir2

Das funktioniert schonmal.

Ein etwas komplexeres Beispiel

Jetzt möchten wir gerne die Befehle für den Butzer john auf einige wenige einschränken. Z.B. auf ls, cat und less. Dazu kommt das action-Object mit einer Methode lookup. Je nachdem was für eine action-Instanz wir hier vorliegen haben, ist das interne Dictionary mit den entsprechenden Werten befüllt. Hier, bei action.id org.freedesktop.policykit.exec hält der Key command_line die Zeichenkette des Befehls, die unmittelbar auf pkexec folgt. Somit können wir die erlaubten Befehle mit einem Zweizeiler eingrenzen:

polkit.addRule( function(action, subject) {
  if ( action.id == "org.freedesktop.policykit.exec" ) {
    let allowed_programs = ["/usr/bin/ls", "/usr/bin/cat", "/usr/bin/less"];
    let program = action.lookup("command_line").split(" ")[0]

    if (subject.user == "sxw" && allowed_programs.includes(program)) {
      return polkit.Result.YES;
    } else {
      return polkit.Result.NO;
    }       
  }
});

Hier sehen wir zudem noch, dass wir im else-Zweig ein explizites polkit.Result.NO zurückgeben. Das verhindert, dass polkit den Passwort-Prompt für Root-Authentifizierung anzeigt, so dass der Benutzer den Befehl bei Eingabe des Passwortes dennoch ausführen könnte.

Jetzt können wir das ganze auch nur zwischen 12 und 18 Uhr zulassen. Dank JavaScript haben wir Zugriff auf die ganze Class-Library:

let hours = new Date().getHours();
if ( hours < 12 || hours >= 19 ) {
  return polkit.Result.NO;
}

Das Ganze kann man dann auch je nach Programm nach Belieben anpassen.

Beim Entwickeln kann es ganz hilfreich sein, Meldungen in den Log zu schreiben. Das kann man mit polkit.log("...") tun. Die Meldungen landen dann im Journal und können in einer anderen Shell dann mit journalctl -f -u polkit beobachtet werden.

Fazit

Ich weiß, die meisten werden mich jetzt lynchen für einen Artikel im systemd-Umfeld und dann kommt auch noch JavaScript drin vor. Aber ich kann mir schon den ein oder anderen Fall vorstellen -- gerade auf Systemen, die von einer Vielzahl an Benutzern mit unterschiedlichen Admin-Aufgaben frequentiert werden.