Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/package-standard.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<paket>/<sunucu>/` 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/<ad>/` 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/<paket>/<sunucu>/logs/<YYYYmmdd-HHMMSS>-<eylem>.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).
Expand Down
87 changes: 85 additions & 2 deletions sistem/Helpers/ComponentsExec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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[@]}"
Expand Down Expand Up @@ -292,15 +294,96 @@ comp_exec_last_result() {
head -n 40 "$f"
}

# _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/(([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'
}

# _comp_exec_fmt <epoch> <biçim> [-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 <durum-klasörü> <paket> <sürüm> <eylem> <sunucu> <çıkış-kodu> <başlangıç-epoch>
# last-run.log'u kalıcı arşive kopyalar: <durum>/logs/<YYYYmmdd-HHMMSS>-<eylem>.log (dizin 0700, dosya 0600); başlık + maskelenmiş gövde + sonuç.
# Yolu COMP_EXEC_LOGFILE'a koyar; başarısızlıkta <durum>/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 <bilgi> <paket> <sürüm> <eylem> → 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
Expand Down
39 changes: 39 additions & 0 deletions sistem/Helpers/ComponentsMenu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 <dosya> → 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 <durum-klasörü> → 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}"

Expand Down
4 changes: 4 additions & 0 deletions sistem/Helpers/ComponentsRunCLI.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
108 changes: 108 additions & 0 deletions test/components_exec.bats
Original file line number Diff line number Diff line change
Expand Up @@ -354,3 +354,111 @@ 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 </dev/null
[ "$status" -eq 0 ]
local d="$T/state/demo/web1/logs" f
[ "$(modeof "$d")" = "700" ]
f=$(ls "$d"/*-preview.log)
[[ "$f" =~ /[0-9]{8}-[0-9]{6}-preview\.log$ ]] || false
[ "$(modeof "$f")" = "600" ]
grep -q '^# paket: demo 1.0.0$' "$f"
grep -q '^# eylem: preview$' "$f"
grep -q '^# sunucu: web1$' "$f"
grep -q '^# zaman: .* (yerel) / .*Z (UTC)$' "$f"
grep -q '^# komut: .*harden.yml' "$f"
grep -q '^# süre: [0-9]*s$' "$f"
grep -q '^merhaba çıktı$' "$f"
grep -q '^# çıkış kodu: 0$' "$f"
grep -q '^# sonuç: ok$' "$f"
[ -f "$T/state/demo/web1/last-run.log" ]
[ ! -e "$T/state/demo/web1/last-fail-log" ]
grep -q '|1.0.0|preview|ok$' "$T/state/demo/web1/results" # results biçimi değişmedi
}

@test "günlük: başarısızlıkta sonuç fail, çıkış kodu yazılır, last-fail-log yolu gösterir; sonraki başarı onu siler; önceki günlük silinmez" {
FAKE_RC=4 FAKE_OUT='asıl hata burada' run comp_exec_action "$INFO" demo 1.0.0 preview </dev/null
[ "$status" -eq 4 ]
local s="$T/state/demo/web1" f
f=$(cat "$s/last-fail-log")
[ -f "$f" ]
grep -q '^asıl hata burada$' "$f"
grep -q '^# çıkış kodu: 4$' "$f"
grep -q '^# sonuç: fail$' "$f"
sleep 1
run comp_exec_action "$INFO" demo 1.0.0 preview </dev/null
[ "$status" -eq 0 ]
[ ! -e "$s/last-fail-log" ]
[ -f "$f" ] # başarısız günlük ezilmedi
[ "$(ls "$s/logs" | wc -l | tr -d ' ')" = "2" ]
run grep -q 'asıl hata' "$s/last-run.log" # canlı günlük ezilir, arşiv kalır
[ "$status" -ne 0 ]
}

@test "günlük: parola, token ve özel anahtar izleri arşivde maskelenir" {
local kt="OPENSSH PRIVATE" # düz yazılırsa depo-hijyen testi sahte anahtar sanır
FAKE_OUT=$'BECOME password: gizli123\nsudo_password=abc999\napi_token: ghp_ABCDEFGH12345678\n-----BEGIN '"$kt"$' KEY-----\nAAAAB3NzaC1\n-----END '"$kt"$' KEY-----\nnormal satır' \
run comp_exec_action "$INFO" demo 1.0.0 preview </dev/null
[ "$status" -eq 0 ]
local f
f=$(ls "$T/state/demo/web1/logs"/*-preview.log)
run grep -q 'gizli123\|abc999\|ABCDEFGH12345678\|AAAAB3NzaC1\|BEGIN OPENSSH' "$f"
[ "$status" -ne 0 ]
grep -q 'özel anahtar bloğu gizlendi' "$f"
grep -q '^normal satır$' "$f"
grep -q '\*\*\*\*' "$f"
}

@test "günlük: COMP_LOG_KEEP kadar günlük kalır, en eskiler silinir" {
local d="$T/state/demo/web1/logs" i
mkdir -p "$d"; chmod 700 "$d"
for i in 1 2 3 4 5; do : > "$d/2020010$i-000000-preview.log"; done
COMP_LOG_KEEP=3 run comp_exec_action "$INFO" demo 1.0.0 preview </dev/null
[ "$status" -eq 0 ]
[ "$(ls "$d" | wc -l | tr -d ' ')" = "3" ]
[ ! -e "$d/20200101-000000-preview.log" ]
[ ! -e "$d/20200103-000000-preview.log" ]
[ -e "$d/20200105-000000-preview.log" ]
ls "$d" | grep -q "^20[2-9][0-9][01][0-9][0-3][0-9]-[0-9]*-preview.log$"
}

@test "günlük: reddedilen (çalışmayan) eylem günlük bırakmaz" {
comp_gate_requires_vars() { return 1; }
run comp_exec_action "$INFO" demo 1.0.0 preview </dev/null
[ ! -d "$T/state/demo/web1/logs" ]
}

@test "günlük: Ctrl-C/TERM ile kesilen eylem (alt kabuk 130) 'kesildi (sinyal)' olarak arşivlenir" {
FAKE_RC=130 FAKE_OUT='yarım çıktı' run comp_exec_action "$INFO" demo 1.0.0 preview </dev/null
[ "$status" -eq 130 ]
local f
f=$(cat "$T/state/demo/web1/last-fail-log")
grep -q '^yarım çıktı$' "$f"
grep -q '^# sonuç: fail — kesildi (sinyal 2)$' "$f"
}

@test "günlük: üst kabuğa HUP gelince (terminal kapandı) günlük arşivlenir, kayıt fail olur" {
COMP_EXEC_SIG_CTX="demo|1.0.0|preview|web1|$(date +%s)"
mkdir -p "$T/state/demo/web1"; printf 'canlı satır\n' > "$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"
}

@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 </dev/null
[ "$status" -eq 0 ]
local f
f=$(ls "$T/state/demo/web1/logs"/*-preview.log)
run grep -E 'abc123|redis777|tok888|sec999|par111|par222' "$f"
[ "$status" -ne 0 ]
grep -q '^MARIADB_ROOT_PASSWORD=\*\*\*\*$' "$f"
grep -q '^çalışan değişken=1$' "$f"
}
18 changes: 18 additions & 0 deletions test/components_menu.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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" </dev/null
[ "$status" -eq 0 ]
[ -z "$output" ]
}
Loading