From 6f82bad6f93cd9c94a3912e94b80243e2ef00da1 Mon Sep 17 00:00:00 2001 From: mm Date: Mon, 14 Sep 2026 11:21:00 +0200 Subject: [PATCH] =?UTF-8?q?fix(acl):=20Glob-Wurzeln=20brauchen=20r-x=20?= =?UTF-8?q?=E2=80=94=20Ursache=20der=20stummen=20Datenluecke=20auf=20x14?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit x14 sendete 105 Metriken, aber KEINE apache-Metriken und keine Logs. Der Installer meldete Erfolg. Telegraf selbst nannte den Grund: W! [inputs.tail] Directory "/home/users" is not readable but is followed by wildcards, make sure you set read permissions or globbing will not work! Drei Ursachen, alle behoben: 1. `setfacl -m u:telegraf:x` auf der Glob-Wurzel ist FALSCH. Eine Wildcard wird per readdir expandiert — dafuer braucht das Verzeichnis `r-x`, nicht nur `x`. Neue Logik: Muster werden mit `set -f` gelesen, dann je Treffer die Datei mit `r` und JEDE Ebene zwischen Wurzel und Datei mit `r-x` belegt. Gemessen: alte ACL -> 1 Warnung, 1 Punkt (nur /var/log/apache2); neue ACL -> 0 Warnungen, 4 Punkte (alle 3 Dateien inkl. User-Homes). 2. Der Gegentest war blind: er las jede gefundene Datei per `head -c 1` als telegraf. Das konnte nicht fehlschlagen, weil die Datei vom ROOT-Shell expandiert wurde — die Glob-Wurzel blieb unlesbar, Telegraf bekam 0 Treffer, die Meldung sagte trotzdem "alle N Logdateien lesen". Jetzt wird die GLOB-AUFLOESUNG gemessen: dasselbe Muster im Dienstkontext expandieren und die Trefferzahl vergleichen. 0 von 0 zaehlt als Fehler, nicht als Erfolg. 3. `for p in $_pfadquelle` ohne `set -f` expandiert die Muster im Root-Shell. _pattern war danach ein konkreter Dateiname, der Wildcard-Zweig lief nie, und die ACL landete nur auf den Dateien. Real passiert: "ACL auf 3 Datei(en)", "kein Glob-Muster gefunden", 0 Treffer im Dienst. Verifiziert mit echtem Telegraf 1.40.0 und REALEN KeyHelp-Rechten (/home/users/ 0700, logs 0750, Dateien 0640). --- VERSION | 2 +- install.sh | 189 +++++++++++++++++++++++++++-------------------------- 2 files changed, 98 insertions(+), 93 deletions(-) diff --git a/VERSION b/VERSION index 27f9cd3..f8e233b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.0 +1.9.0 diff --git a/install.sh b/install.sh index 7b4a64b..49023d9 100755 --- a/install.sh +++ b/install.sh @@ -625,112 +625,117 @@ grant_apache_log_access() { return 0 fi - # 1) Verzeichnisse aus der aktiven Conf ermitteln. + # 1) Glob-Muster aus der aktiven Conf ermitteln. # - # REIHENFOLGE-BUG (real): diese Funktion liest - # /etc/telegraf/telegraf.d/apache.conf - # die erst SPAETER im Ablauf geschrieben wird. Beim ERSTEN Install ist sie - # nicht da -> _roots bleibt leer -> der Fallback unten greift auf die groben - # Panel-Wurzeln (/home/users, /var/www/vhosts) zu, also auf zehntausende - # Dateien inklusive Mails und Webinhalten. Beim zweiten Lauf (x22) existierte - # die Conf und lieferte die praezisen Domain-Ordner (83 Wu­rzeln). - # Deshalb: fehlt die Conf, wird sie hier aus den erkannten Pfaden erzeugt, - # BEVOR die ACL gesetzt wird. - # QUELLE DER PFADE, richtig herum: - # 1. die aktive apache.conf (Folge-Laeufe — dort stehen bereits die Muster) - # 2. sonst APACHE_LOG (in DIESEM Lauf erkannt; beim Erstinstall ist die Conf - # noch nicht geschrieben, und genau dann griff vorher der grobe Fallback) + # QUELLE, richtig herum: + # 1. die aktive apache.conf (Folge-Laeufe — dort stehen die Muster) + # 2. sonst APACHE_LOG (in DIESEM Lauf erkannt; beim Erstinstall existiert die + # Conf noch nicht — vorher griff hier ein grober Fallback auf /home/users + # und /var/www/vhosts, also auf Mails und Webinhalte) _pfadquelle="" if [ -f /etc/telegraf/telegraf.d/apache.conf ]; then _pfadquelle="$(sed -n 's/^[[:space:]]*files = \[\(.*\)\].*/\1/p' \ /etc/telegraf/telegraf.d/apache.conf 2>/dev/null | tr -d '"' | tr ',' ' ')" fi [ -n "$_pfadquelle" ] || _pfadquelle="${APACHE_LOG:-}" + [ -n "$_pfadquelle" ] || return 0 - _roots="" - for _f in $_pfadquelle; do - case "$_f" in - */\**) _f="${_f%%/\**}" ;; # /a/b/** -> /a/b - */*) _f="${_f%/*}" ;; # /a/b/*/x -> /a/b/* - esac - case "$_f" in - */\*|*/\*/*) _f="${_f%%\**}" ;; # Glob-Rest abschneiden - esac - _f="${_f%/}" - [ -d "$_f" ] && _roots="$_roots $_f" - done - # Panel-typische Wurzeln direkt mitnehmen (auch wenn die Conf sie globt) - for _d in /home/users /home/*/logs /var/www/vhosts /usr/local/apache/domlogs; do - [ -d "$_d" ] && _roots="$_roots $_d" - done - [ -n "$_roots" ] || return 0 + acl_set() { timeout 30 setfacl -m "u:$1:$2" "$3" 2>/dev/null || true; } - _n=0 - # Timeout pro Wurzel: setfacl laeuft rekursiv. Auf einem Panel-Host kann eine - # Wurzel sehr gross sein; ohne Grenze sieht ein langsamer Lauf wie ein Haenger - # aus. 120 s pro Wurzel sind grosszuegig und begrenzen die Gesamtdauer. - for _r in $_roots; do - # Nur die LOG-Verzeichnisse, nicht den ganzen User-Baum: /home/users - # enthaelt auch Mails, Webinhalte und Backups — ein rekursives Recht darauf - # waere wirkungslos fuer die Logs, aber teuer und semantisch falsch. - _targets="" - case "$_r" in - /home/*|/var/www*) - # Untergeordnete logs/-Verzeichnisse einsammeln (eine Ebene tief genug) - _targets="$(find "$_r" -maxdepth 3 -type d -name 'logs' 2>/dev/null | head -500 | tr '\n' ' ')" - [ -n "$_targets" ] || _targets="$_r" ;; - *) _targets="$_r" ;; - esac - for _t in $_targets; do - [ -d "$_t" ] || continue - # DURCHGANGSRECHT auf allen ELTERNVERZEICHNISSEN bis zur Wurzel. - # Realer Fehler: /home/users/ ist oft chmod 700. Ein setfacl -R auf - # ...//logs setzt nichts auf selbst — telegraf kommt dann - # nicht bis zur Datei, obwohl die Meldung "ACL gesetzt" erscheint. - _p="$(dirname "$_t")" - while [ -n "$_p" ] && [ "$_p" != "/" ] && [ "$_p" != "." ]; do - timeout 30 setfacl -m "u:$_u:x" "$_p" 2>/dev/null || true - # Default-ACL auf Zwischenverzeichnissen, damit neue Ordner erben - case "$_p" in - /home|/home/users|/var|/var/www|/usr|/usr/local) ;; - *) timeout 30 setfacl -d -m "u:$_u:x" "$_p" 2>/dev/null || true ;; - esac - _p="$(dirname "$_p")" - done - # Leserecht (r) auf die Dateien, Durchgang (x) auf die Ordner: - # rX vergibt x nur fuer Verzeichnisse. - timeout 120 setfacl -R -m "u:$_u:rX" "$_t" 2>/dev/null && _n=$((_n+1)) - # Neu angelegte Dateien/Ordner erben das Recht (default-ACL nur fuer dirs). - timeout 120 setfacl -R -d -m "u:$_u:rX" "$_t" 2>/dev/null || true - done - done - [ "$_n" -gt 0 ] && log "Log-Leserechte gesetzt (ACL fuer '$_u') auf $_n Wurzel(n)." - - # 2) Gegentest: JEDEN Logpfad als Dienst-Benutzer pruefen — nicht nur den ersten. + # 1a) Rechte aus den MUSTERN ableiten. # - # Realer Fehler: der Test brach beim ersten Treffer ab. Auf einem KeyHelp-Host - # war das /var/log/apache2/keyhelp/access.log (root:adm, immer lesbar) — die - # Meldung lautete "Gegentest OK", waehrend ALLE Domain-Logs in den User-Homes - # unlesbar blieben. Ein Test, der beim ersten Erfolg abbricht, verdeckt genau - # die Pfade, fuer die er gedacht ist. - _tested=0; _fail=0; _firstfail="" - for _f in $_pfadquelle; do - # shellcheck disable=SC2086 - for _cand in $(ls $_f 2>/dev/null); do - [ -s "$_cand" ] || continue - _tested=$((_tested+1)) - if ! su -s /bin/sh "$_u" -c "head -c 1 \"$_cand\" >/dev/null 2>&1"; then - _fail=$((_fail+1)) - [ -n "$_firstfail" ] || _firstfail="$_cand" - fi - done + # Eine Wildcard wird per readdir expandiert. Dafuer muss JEDES Verzeichnis auf + # dem Weg zum Treffer AUFLISTBAR sein — also `r-x`, nicht nur `x`. `x` laesst + # Telegraf ein Verzeichnis betreten, aber nicht auflisten. + # + # REALER, STILLER FEHLER (x14): die vorige Fassung gab /home/users nur `--x`. + # Telegraf meldete + # W! [inputs.tail] Directory "/home/users" is not readable but is followed + # by wildcards, make sure you set read permissions or globbing will not + # work! + # und lieferte 0 von 56 Logdateien: Confs geladen, kein Fehler, Gegentest gruen, + # aber es floss nichts — genau die stille Datenluecke, die nicht passieren darf. + # + # `set -f` ist hier PFLICHT. Ohne expandiert der Root-Shell jedes Muster, BEVOR + # die Schleife es sieht; _pattern waere ein konkreter Dateiname, der Wildcard- + # Zweig liefe nie, und die ACL landete nur auf den Dateien — die Glob-Wurzeln + # blieben unlesbar. Real passiert: "ACL auf 2 Datei(en)", 0 Treffer im Dienst. + set -f + for _pattern in $_pfadquelle; do + case "$_pattern" in + *'*'*) + # Wurzel des Musters = alles vor dem ERSTEN Stern. + # ${_pattern%%**} waere leer (Muster "**" = beliebiger String), deshalb + # die Zeichenklasse: ${_pattern%%[*]*} schneidet ab dem ersten Stern. + _pre="${_pattern%%[*]*}" + _root="${_pre%/*}" + [ -n "$_root" ] || _root="/" + # Hier wird expandiert -> die Wurzel muss auflistbar sein. + [ -d "$_root" ] && acl_set "$_u" r-x "$_root" + # Konkrete Treffer: jetzt mit Expansion. + set +f + for _hit in $_pattern; do + [ -f "$_hit" ] || continue + acl_set "$_u" r "$_hit" + _touched="$_touched $_hit" + # Jede Ebene zwischen Wurzel und Datei: auflistbar UND betretbar. + _d="$(dirname "$_hit")" + while [ -n "$_d" ] && [ "$_d" != "/" ] && [ "$_d" != "$_root" ]; do + acl_set "$_u" r-x "$_d" + _d="$(dirname "$_d")" + done + done + set -f + ;; + *) + if [ -f "$_pattern" ]; then acl_set "$_u" r "$_pattern"; _touched="$_touched $_pattern"; fi + ;; + esac done + + # 2) Gegentest: die GLOB-AUFLOESUNG pruefen, nicht die Datei-Lesbarkeit. + # + # REALER FEHLER: der alte Test las jede gefundene Datei per `head -c 1` als + # telegraf. Das KONNTE nicht fehlschlagen — die Datei war ja vom Root-Shell + # expandiert worden; eine ACL lag auf der Datei, nur die Glob-Wurzel blieb + # unlesbar. Telegraf bekam 0 Treffer, die Meldung sagte "alle N lesen". + # Hier wird deshalb GENAU das gemessen, was Telegraf tut: dasselbe Muster im + # Dienstkontext expandieren und die Trefferzahl MIT der Wurzelzahl vergleichen. + _tested=0; _fail=0; _firstfail="" + for _pattern in $_pfadquelle; do + case "$_pattern" in *'*'*) : ;; *) continue ;; esac + # Trefferzahl als root: dafür MUSS expandiert werden. + set +f + _root_cnt=0 + for _c in $_pattern; do [ -f "$_c" ] && _root_cnt=$((_root_cnt+1)); done + set -f + # Trefferzahl als Dienstbenutzer: das Muster unquoted uebergeben, damit die + # Ziel-Shell selbst globbt. + _cnt="$(su -s /bin/sh "$_u" -c ' + n=0 + for _c in '"$_pattern"'; do [ -f "$_c" ] && n=$((n+1)); done + printf "%s" "$n"' 2>/dev/null)" + _cnt="${_cnt:-0}" + _tested=$((_tested+1)) + # Blind muss ausgeschlossen sein: 0 von 0 waere kein Beweis. + if [ "$_root_cnt" -eq 0 ]; then + _fail=$((_fail+1)) + [ -n "$_firstfail" ] || _firstfail="$_pattern (kein Treffer als root — Muster pruefen)" + elif [ "$_cnt" -lt "$_root_cnt" ]; then + _fail=$((_fail+1)) + [ -n "$_firstfail" ] || _firstfail="$_pattern (Dienst sieht $_cnt von $_root_cnt)" + fi + done + set +f + _n=0 + [ -n "$_touched" ] && _n=$(printf '%s\n' $_touched | grep -c .) + [ "$_n" -gt 0 ] && log "Log-Leserechte gesetzt (ACL fuer '$_u') auf $_n Datei(en)." if [ "$_tested" -eq 0 ]; then - log "HINWEIS: keine gefuellte Logdatei gefunden — Gegentest nicht moeglich." + log "HINWEIS: kein Glob-Muster gefunden — Gegentest nicht moeglich." elif [ "$_fail" -eq 0 ]; then - log "Gegentest: '$_u' kann alle $_tested Logdateien lesen." + log "Gegentest: '$_u' loest alle $_tested Log-Muster vollstaendig auf." else - log "WARNUNG: $_fail von $_tested Logdateien sind fuer '$_u' NICHT lesbar." + log "WARNUNG: $_fail von $_tested Log-Mustern sind fuer '$_u' NICHT aufloesbar." log " Beispiel: $_firstfail" log " Pruefen mit: sh install.sh --diagnose" fi