From cd716ecf122da19000f06450d517b7fdddd46c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ziya=20Ye=C5=9Filbah=C3=A7e?= Date: Sat, 7 Nov 2026 11:43:41 +0300 Subject: [PATCH 1/2] =?UTF-8?q?Eylem=20g=C3=BCnl=C3=BCkleri=20kal=C4=B1c?= =?UTF-8?q?=C4=B1:=20logs/-.log,=20maskeleme,=20temizlik,=20?= =?UTF-8?q?men=C3=BCde=20gezgin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- docs/package-standard.md | 3 + sistem/Helpers/ComponentsExec.sh | 87 ++++++++++++++++++++++++++- sistem/Helpers/ComponentsMenu.sh | 39 ++++++++++++ sistem/Helpers/ComponentsRunCLI.sh | 4 ++ test/components_exec.bats | 96 ++++++++++++++++++++++++++++++ test/components_menu.bats | 18 ++++++ 6 files changed, 245 insertions(+), 2 deletions(-) diff --git a/docs/package-standard.md b/docs/package-standard.md index 8e6729e..85d28e2 100644 --- a/docs/package-standard.md +++ b/docs/package-standard.md @@ -200,6 +200,9 @@ Bir denetim eylemi (Lynis, OpenSCAP, …) çıktı klasöründe `summary.json` y Geriye dönük uyum: `score` yoksa `hardening_index` okunur (linux-hardening). Dosyalar `~/.local/share/sshpocket///` altında aranır (derinlik ≤ 5); sunucudan gelen veriye güvenilmez, bozuk dosyalar sessizce atlanır. Başka bir araç eklemek için sshpocket'te kod değişikliği gerekmez, yalnızca bu biçimde `summary.json` yazmak yeter. - Her uygulamanın kaydı (tarih, sunucu, paket+sürüm, sonuç) `servers//` altında, **gizli bilgi içermeden** tutulur. +### 8.x Eylem günlükleri (kalıcı) +Çalışan her eylem (kapılar geçip gerçekten başladıysa) `~/.local/share/sshpocket///logs/-.log` olarak arşivlenir (dizin 0700, dosya 0600). Başlık: zaman (yerel+UTC), paket/sürüm, eylem, sunucu, komut satırı, süre; sonda çıkış kodu ve `ok`/`fail`. Gövde, parola/token/özel anahtar izleri maskelenerek yazılır (`****`); paket yine de gizli değeri çıktıya yazmamalıdır (maskeleme son savunmadır). `last-run.log` canlı günlük olarak kalır (her eylemde ezilir); `results` satır biçimi değişmedi. Başarısız eylemin günlük yolu `last-fail-log` dosyasında tutulur ve eylem sonunda ekrana yazılır (sonraki başarılı eylem bu işareti siler, günlük silinmez). Sunucu/paket başına son 50 günlük saklanır (`COMP_LOG_KEEP`). Menüde "📜 Geçmiş ve son sonuçlar" günlükleri listeler ve seçileni tam açar (`less`). + ## 9. Güvenlik kapıları (paketten bağımsız, sshpocket zorunlu tutar) 1. Kilit dosyasında olmayan paket **çalıştırılmaz** (üçüncü taraf paket yok). diff --git a/sistem/Helpers/ComponentsExec.sh b/sistem/Helpers/ComponentsExec.sh index c853892..2387643 100644 --- a/sistem/Helpers/ComponentsExec.sh +++ b/sistem/Helpers/ComponentsExec.sh @@ -117,6 +117,7 @@ _comp_exec_script() { -o PreferredAuthentications=publickey -o PasswordAuthentication=no -p "$COMP_CTX_PORT" -i "$COMP_CTX_KEY") log="$sdir/last-run.log" ( umask 077; : > "$log" ) || return "$refused" + printf 'komut: ssh -p %s %s@%s bash -s --%s (betik: %s)\n' "$COMP_CTX_PORT" "$COMP_CTX_USER" "$COMP_CTX_HOST" "$rargs" "${words[0]}" >> "$log" # shellcheck disable=SC2064 trap "rm -r -- $(printf '%q' "$work") 2>/dev/null; _comp_exec_remote_rm" EXIT @@ -255,6 +256,7 @@ _comp_exec_body() { # ---- çalıştır (günlük kullanıcı klasöründe, izin 600; kullanıcı terminali çıktıyı olduğu gibi görür) ---- log="$sdir/last-run.log" ( umask 077; : > "$log" ) || return "$refused" + printf 'komut: %s\n' "$(printf '%q ' "${COMP_RUN_ARGV[@]}")" >> "$log" for kv in ${applied[@]+"${applied[@]}"} ${runsets[@]+"${runsets[@]}"}; do printf 'bu koşuya özel: %s\n' "$kv" >> "$log"; done { cd "$pkg" && env PYTHONDONTWRITEBYTECODE=1 ANSIBLE_CONFIG="$pkg/ansible.cfg" ANSIBLE_HOST_KEY_CHECKING=True ANSIBLE_TIMEOUT="${ANSIBLE_TIMEOUT:-30}" PATH="$bindir:$PATH" "${COMP_RUN_ARGV[@]}" @@ -292,15 +294,96 @@ comp_exec_last_result() { head -n 40 "$f" } +# _comp_exec_mask → stdin'deki günlükten parola/anahtar/token izlerini siler (ansible/ssh çıktısına sızarsa kalıcı günlüğe geçmesin) +_comp_exec_mask() { + LC_ALL=C tr -d '\000' | LC_ALL=C sed -E \ + -e '/-----BEGIN [A-Z ]*PRIVATE KEY-----/,/-----END [A-Z ]*PRIVATE KEY-----/c\ +[özel anahtar bloğu gizlendi]' \ + -e 's/((BECOME|SUDO|[Pp]assword|[Pp]asswd|[Tt]oken|[Ss]ecret|[Aa]pi[_-]?[Kk]ey)[A-Za-z_ ]{0,20}[:=][ ]*)[^ ].*/\1****/' \ + -e 's/(ghp_|github_pat_|glpat-|xox[bp]-)[A-Za-z0-9_-]{8,}/\1****/g' +} + +# _comp_exec_fmt [-u] → BSD (macOS) ve GNU date ile aynı çıktı +_comp_exec_fmt() { + local u=""; [ "${3:-}" = "-u" ] && u="-u" + # shellcheck disable=SC2086 + date $u -r "$1" "$2" 2>/dev/null || date $u -d "@$1" "$2" +} + +# comp_exec_archive_log <çıkış-kodu> +# last-run.log'u kalıcı arşive kopyalar: /logs/-.log (dizin 0700, dosya 0600); başlık + maskelenmiş gövde + sonuç. +# Yolu COMP_EXEC_LOGFILE'a koyar; başarısızlıkta /last-fail-log'a yazar (başarıda siler). results biçimi DEĞİŞMEZ. +# Eski günlükler: sunucu/paket başına son COMP_LOG_KEEP (varsayılan 50) kalır. +comp_exec_archive_log() { + local sdir="$1" id="$2" ver="$3" action="$4" server="$5" rc="$6" t0="$7" ldir name f n t1 keep="${COMP_LOG_KEEP:-50}" res cmdline + COMP_EXEC_LOGFILE="" + [ -f "$sdir/last-run.log" ] || return 0 + [[ "$action" =~ ^[a-z][a-z0-9_]{0,63}$ ]] || return 1 + [[ "$keep" =~ ^[0-9]+$ ]] && [ "$keep" -ge 1 ] || keep=50 + ldir="$sdir/logs" + ( umask 077; mkdir -p "$ldir" ) && chmod 700 "$ldir" || return 1 + t1=$(date +%s) + name="$(_comp_exec_fmt "$t0" +%Y%m%d-%H%M%S)-$action" + f="$ldir/$name.log"; n=1 + while [ -e "$f" ]; do n=$((n + 1)); f="$ldir/$name-$n.log"; done + res=$([ "$rc" -eq 0 ] && echo ok || echo fail) + # 129/130/143 = HUP/INT/TERM ile kesildi (terminal kapandı, Ctrl-C, kill) + case "$rc" in 129|130|143) res="fail — kesildi (sinyal $((rc - 128)))" ;; esac + cmdline=$(sed -n 's/^komut: //p' "$sdir/last-run.log" | head -n 1 | _comp_exec_mask) + { + echo "# sshpocket eylem günlüğü" + printf '# zaman: %s (yerel) / %s (UTC)\n' "$(_comp_exec_fmt "$t0" '+%Y-%m-%d %H:%M:%S %z')" "$(_comp_exec_fmt "$t0" '+%Y-%m-%dT%H:%M:%SZ' -u)" + printf '# paket: %s %s\n# eylem: %s\n# sunucu: %s\n' "$id" "$ver" "$action" "$server" + printf '# komut: %s\n# süre: %ss\n' "${cmdline:-?}" "$((t1 - t0))" + echo "# ----" + grep -v '^komut: ' "$sdir/last-run.log" | _comp_exec_mask + echo "# ----" + printf '# çıkış kodu: %s\n# sonuç: %s\n' "$rc" "$res" + } > "$f" 2>/dev/null || { rm -f -- "$f"; return 1; } + chmod 600 "$f" + COMP_EXEC_LOGFILE="$f" + if [ "$rc" -ne 0 ]; then ( umask 077; printf '%s\n' "$f" > "$sdir/last-fail-log" ) || true + else rm -f -- "$sdir/last-fail-log"; fi + # temizlik: ad zaman damgasıyla başladığı için sözlük sırası = zaman sırası + n=$(find "$ldir" -maxdepth 1 -type f -name '*.log' | wc -l | tr -d ' ') + if [ "$n" -gt "$keep" ]; then + find "$ldir" -maxdepth 1 -type f -name '*.log' | sort | head -n "$((n - keep))" | while IFS= read -r f; do rm -f -- "$f"; done + fi + return 0 +} + +# _comp_exec_on_signal <çıkış-kodu> → HUP/TERM'de (alt kabuk zaten sonlandırılmış olabilir) günlüğü arşivleyip çıkar +_comp_exec_on_signal() { + local rc="$1" id ver action server t0 sdir + IFS='|' read -r id ver action server t0 <<< "${COMP_EXEC_SIG_CTX:-}" + if [ -n "$id" ] && sdir=$(comp_gate_state_ensure "$id" "$server" 2>/dev/null); then + comp_exec_archive_log "$sdir" "$id" "$ver" "$action" "$server" "$rc" "$t0" || true + comp_gate_record "$id" "$server" "$ver" "$action" fail || true + fi + exit "$rc" +} + # comp_exec_action → kapılar, çalıştırma, sonuç ayrıştırma ve kayıt comp_exec_action() { - local info="$1" id="$2" ver="$3" action="$4" rc sdir - COMP_EXEC_RESULT="" + local info="$1" id="$2" ver="$3" action="$4" rc sdir t0 + COMP_EXEC_RESULT=""; COMP_EXEC_LOGFILE="" + t0=$(date +%s) + # terminal paneli kapanırsa (HUP) ya da kill (TERM) gelirse üst kabuk da düşer: günlük yine arşivlensin + local old_hup old_term + old_hup=$(trap -p HUP); old_term=$(trap -p TERM) + COMP_EXEC_SIG_CTX="$id|$ver|$action|$COMP_CTX_SERVER|$t0" + trap '_comp_exec_on_signal 129' HUP + trap '_comp_exec_on_signal 143' TERM ( _comp_exec_body "$info" "$id" "$ver" "$action" ) rc=$? + trap - HUP TERM + [ -z "$old_hup" ] || eval "$old_hup" + [ -z "$old_term" ] || eval "$old_term" + COMP_EXEC_SIG_CTX="" [ "$rc" -eq "$COMP_EXEC_REFUSED" ] && return "$rc" # hiçbir şey çalışmadı: kayıt yok sdir=$(comp_gate_state_ensure "$id" "$COMP_CTX_SERVER") || return "$rc" comp_exec_parse_result "$info" "$action" "$sdir/last-run.log" || true + comp_exec_archive_log "$sdir" "$id" "$ver" "$action" "$COMP_CTX_SERVER" "$rc" "$t0" || true comp_exec_save_result "$sdir" "$action" || true if [ "$rc" -eq 0 ]; then comp_gate_record "$id" "$COMP_CTX_SERVER" "$ver" "$action" ok || true else comp_gate_record "$id" "$COMP_CTX_SERVER" "$ver" "$action" fail || true; fi diff --git a/sistem/Helpers/ComponentsMenu.sh b/sistem/Helpers/ComponentsMenu.sh index 5669b71..bf3cde9 100644 --- a/sistem/Helpers/ComponentsMenu.sh +++ b/sistem/Helpers/ComponentsMenu.sh @@ -740,10 +740,49 @@ comp_menu_history() { IFS= read -r -p "Son 30 satırı gösterilsin mi? [e/H]: " ans || return 1 case "$ans" in e|E|evet|Evet) echo ""; tail -n 30 "$sdir/last-run.log" | LC_ALL=C tr -d '\000-\010\013-\037\177' ;; esac fi + _comp_menu_log_browser "$sdir" || return 1 echo "" read -r -p "Devam için Enter..." _ || return 1 } +# _comp_menu_log_pager → günlüğü tamamen gösterir (etkileşimli terminalde less, değilse düz çıktı); denetim karakterleri atılır +_comp_menu_log_pager() { + if [ -t 1 ] && command -v less >/dev/null 2>&1; then + LC_ALL=C tr -d '\000-\010\013-\037\177' < "$1" | less -S + else + LC_ALL=C tr -d '\000-\010\013-\037\177' < "$1" + fi +} + +# _comp_menu_log_browser → kalıcı eylem günlüklerini (en yeni üstte) listeler; seçilen tam açılır +_comp_menu_log_browser() { + local sdir="$1" ldir f n i ans res base + local -a files + ldir="$sdir/logs" + [ -d "$ldir" ] || return 0 + files=() + while IFS= read -r f; do files+=("$f"); done < <(find "$ldir" -maxdepth 1 -type f -name '*.log' | sort -r | head -n 15) + [ "${#files[@]}" -gt 0 ] || return 0 + echo "" + echo "Eylem günlükleri ($ldir; son ${#files[@]}, en yeni üstte)" + i=0 + for f in "${files[@]}"; do + i=$((i + 1)); base="${f##*/}" + res=$(sed -n 's/^# sonuç: //p' "$f" | tail -n 1) + printf ' %2d) %s %s\n' "$i" "$([ "$res" = ok ] && echo '✅' || echo '❌')" "${base%.log}" + done + while true; do + IFS= read -r -p "Tam açmak için numara (Enter: geç): " ans || return 1 + [ -n "$ans" ] || return 0 + if [[ "$ans" =~ ^[0-9]+$ ]] && [ "$ans" -ge 1 ] && [ "$ans" -le "${#files[@]}" ]; then + n="${files[$((ans - 1))]}" + _comp_menu_log_pager "$n" + else + echo "Geçersiz numara." + fi + done +} + # Büyük paketlerde (çok değişken) değişkenler ekranının başına arama/süzgeç satırları eklenir; küçük paketlerde numaralandırma eskisi gibidir. COMP_MENU_VARS_FILTER_MIN="${COMP_MENU_VARS_FILTER_MIN:-25}" diff --git a/sistem/Helpers/ComponentsRunCLI.sh b/sistem/Helpers/ComponentsRunCLI.sh index d416d8a..6d6e084 100644 --- a/sistem/Helpers/ComponentsRunCLI.sh +++ b/sistem/Helpers/ComponentsRunCLI.sh @@ -189,5 +189,9 @@ comp_cli_run() { echo "Sonuç:" >&2 while IFS= read -r ans; do echo " $ans" >&2; done <<< "$COMP_EXEC_RESULT" fi + if [ -n "${COMP_EXEC_LOGFILE:-}" ]; then + if [ "$rc" -ne 0 ]; then echo "❌ Eylem başarısız. Tam günlük: $COMP_EXEC_LOGFILE" >&2 + else echo "Günlük: $COMP_EXEC_LOGFILE" >&2; fi + fi return "$rc" } diff --git a/test/components_exec.bats b/test/components_exec.bats index b1ecb9f..2fdfff0 100644 --- a/test/components_exec.bats +++ b/test/components_exec.bats @@ -354,3 +354,99 @@ LOG comp_exec_parse_result "$INFO" status "$T/log" [ "$COMP_EXEC_RESULT" = "armed=yes" ] } + +# ---- kalıcı eylem günlüğü ---- +@test "günlük: her eylem logs/ altına tarih-saatli, 0600 dosya yazar; başlık ve sonuç satırları var, last-run.log kalır" { + FAKE_OUT='merhaba çıktı' run comp_exec_action "$INFO" demo 1.0.0 preview "$d/2020010$i-000000-preview.log"; done + COMP_LOG_KEEP=3 run comp_exec_action "$INFO" demo 1.0.0 preview "$T/state/demo/web1/last-run.log" + run _comp_exec_on_signal 129 + [ "$status" -eq 129 ] + local f + f=$(ls "$T/state/demo/web1/logs"/*-preview.log) + grep -q '^canlı satır$' "$f" + grep -q 'kesildi (sinyal 1)' "$f" + grep -q '|1.0.0|preview|fail$' "$T/state/demo/web1/results" +} diff --git a/test/components_menu.bats b/test/components_menu.bats index 4d5c5c9..66e1c59 100644 --- a/test/components_menu.bats +++ b/test/components_menu.bats @@ -1286,3 +1286,21 @@ JSON run comp_menu_actions demo web1 <<<$'1\n1\n0' [ "$(cat "$T/panel")" = "port|Nginx › Ayarlar" ] } + +@test "günlük gezgini: arşivlenmiş günlükler en yeni üstte listelenir; numara seçilince tam içerik gösterilir; Enter geçer" { + local sd="$T/sd"; mkdir -p "$sd/logs" + printf '# sonuç: ok\neski içerik\n' > "$sd/logs/20260101-000000-preview.log" + printf '# sonuç: fail\nasıl hata satırı\nikinci\n' > "$sd/logs/20260102-000000-apply.log" + run _comp_menu_log_browser "$sd" <<< $'1\n' + [ "$status" -eq 0 ] + [[ "$output" == *"20260102-000000-apply"* ]] || false + [[ "$output" == *"❌ 20260102-000000-apply"* ]] || false + [[ "$output" == *"✅ 20260101-000000-preview"* ]] || false + [[ "$output" == *"asıl hata satırı"* ]] || false + [[ "${output%%20260101*}" == *"20260102"* ]] || false # en yeni önce + run _comp_menu_log_browser "$sd" <<< $'9\n\n' + [[ "$output" == *"Geçersiz numara"* ]] || false + run _comp_menu_log_browser "$T/yok" Date: Sat, 7 Nov 2026 11:45:16 +0300 Subject: [PATCH 2/2] =?UTF-8?q?G=C3=BCnl=C3=BCk=20maskeleme:=20b=C3=BCy?= =?UTF-8?q?=C3=BCk=20harfli=20ve=20T=C3=BCrk=C3=A7e=20anahtar=20adlar?= =?UTF-8?q?=C4=B1=20(PASSWORD,=20TOKEN,=20SECRET,=20API=5FKEY,=20PAROLA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- sistem/Helpers/ComponentsExec.sh | 4 ++-- test/components_exec.bats | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/sistem/Helpers/ComponentsExec.sh b/sistem/Helpers/ComponentsExec.sh index 2387643..49233d6 100644 --- a/sistem/Helpers/ComponentsExec.sh +++ b/sistem/Helpers/ComponentsExec.sh @@ -294,12 +294,12 @@ comp_exec_last_result() { head -n 40 "$f" } -# _comp_exec_mask → stdin'deki günlükten parola/anahtar/token izlerini siler (ansible/ssh çıktısına sızarsa kalıcı günlüğe geçmesin) +# _comp_exec_mask (BSD sed'de s///I yok: büyük/küçük harf köşeli ayraçla) → stdin'deki günlükten parola/anahtar/token izlerini siler (ansible/ssh çıktısına sızarsa kalıcı günlüğe geçmesin) _comp_exec_mask() { LC_ALL=C tr -d '\000' | LC_ALL=C sed -E \ -e '/-----BEGIN [A-Z ]*PRIVATE KEY-----/,/-----END [A-Z ]*PRIVATE KEY-----/c\ [özel anahtar bloğu gizlendi]' \ - -e 's/((BECOME|SUDO|[Pp]assword|[Pp]asswd|[Tt]oken|[Ss]ecret|[Aa]pi[_-]?[Kk]ey)[A-Za-z_ ]{0,20}[:=][ ]*)[^ ].*/\1****/' \ + -e 's/(([Bb][Ee][Cc][Oo][Mm][Ee]|[Ss][Uu][Dd][Oo]|[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd]|[Pp][Aa][Ss][Ss][Ww][Dd]|[Pp][Aa][Rr][Oo][Ll][Aa]|[Tt][Oo][Kk][Ee][Nn]|[Ss][Ee][Cc][Rr][Ee][Tt]|[Aa][Pp][Ii][_-]?[Kk][Ee][Yy])[A-Za-z_ ]{0,20}[:=][ ]*)[^ ].*/\1****/' \ -e 's/(ghp_|github_pat_|glpat-|xox[bp]-)[A-Za-z0-9_-]{8,}/\1****/g' } diff --git a/test/components_exec.bats b/test/components_exec.bats index 2fdfff0..a22667d 100644 --- a/test/components_exec.bats +++ b/test/components_exec.bats @@ -450,3 +450,15 @@ LOG grep -q 'kesildi (sinyal 1)' "$f" grep -q '|1.0.0|preview|fail$' "$T/state/demo/web1/results" } + +@test "günlük: büyük harfli ve Türkçe anahtar adları da maskelenir (MARIADB_ROOT_PASSWORD, API_TOKEN, SECRET_KEY, PAROLA)" { + FAKE_OUT=$'MARIADB_ROOT_PASSWORD=abc123\nREDIS_PASSWORD=redis777\nAPI_TOKEN=tok888\nSECRET_KEY=sec999\nPAROLA=par111\nparola: par222\nçalışan değişken=1' \ + run comp_exec_action "$INFO" demo 1.0.0 preview