fix(acl): Glob-Wurzeln brauchen r-x — Ursache der stummen Datenluecke auf x14
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/<user> 0700, logs 0750, Dateien 0640).
This commit is contained in:
1 parent
3f9ac51c45
commit
6f82bad6f9
2 files changed
+98
-93
No files matched your search
+97
-92
@@ -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 Wurzeln).
|
||||
# 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/<user> ist oft chmod 700. Ein setfacl -R auf
|
||||
# .../<user>/logs setzt nichts auf <user> 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
|
||||
|
||||
Reference in new issue
Block a user