awk ile Kullanıcı Aktivite Loglarından Oturum Süresi Hesaplama ve Kullanıcı Bazlı Zaman Analizi

Sistem loglarına bakıp “bu kullanıcı bugün kaç saat çalıştı?” sorusunu kendine sorduğunda ve cevabı bulmak için beş farklı araç deneyip sonunda awk’a döndüğünü fark ettiğinde, bir şeylerin tık diye oturduğunu hissediyorsun. Ben de tam olarak bunu yaşadım. Büyük bir kurumsal ortamda kullanıcı aktivite raporlaması yapması gerektiğinde, önce Python yazmayı düşündüm, sonra perl’e baktım, ama sonunda masaya oturdum ve awk ile 20 satırda istediğimi elde ettim. Bu yazıda o süreci, öğrendiklerimi ve production’da gerçekten kullandığım scriptleri paylaşacağım.

Önce Log Formatını Anlayalım

Her şey log formatını doğru okumakla başlar. Çoğu Linux sisteminde kullanıcı oturumları /var/log/auth.log (Debian/Ubuntu tabanlı) veya /var/log/secure (RHEL/CentOS tabanlı) dosyasında tutulur. wtmp ve utmp dosyaları da var ama bunlar binary format olduğundan last komutuyla okunur. Biz metin tabanlı log işleyeceğiz.

Tipik bir auth.log satırı şöyle görünür:

Jan 15 09:23:41 sunucu sshd[12345]: Accepted password for ahmet from 192.168.1.100 port 54321 ssh2
Jan 15 17:45:12 sunucu sshd[12345]: Disconnected from user ahmet 192.168.1.100 port 54321

Bu iki satır arasındaki fark, kullanıcının oturum süresidir. Basit görünüyor, değil mi? Ama işin içine birden fazla eş zamanlı oturum, farklı günlere yayılan bağlantılar ve kesilen oturumlar girince iş karmaşıklaşıyor.

Önce basit bir örnekle başlayalım. last komutunun çıktısı daha düzenli bir format sunduğu için ona da bakacağız:

last -F | head -20

Bu çıktı şuna benzer:

ahmet    pts/0        192.168.1.100    Mon Jan 15 09:23:41 2024 - Mon Jan 15 17:45:12 2024  (08:21)
mehmet   pts/1        192.168.1.101    Mon Jan 15 10:00:00 2024 - Mon Jan 15 14:30:00 2024  (04:30)

İşte bu format awk için mükemmel. Ama önce auth.log üzerinden gidelim, çünkü gerçek dünyada daha fazla kontrol ihtiyacı duyarsınız.

Temel Awk Yapısı ve Zaman Hesaplama

Awk’ın en güçlü yanlarından biri mktime ve systime fonksiyonlarıdır. Ancak bunlar GNU awk (gawk) ile gelir. Sisteminizdeki awk sürümünü kontrol edin:

awk --version 2>/dev/null || awk -W version 2>/dev/null

Eğer gawk kullanıyorsanız, tarih/saat işlemleri çok kolaylaşır. Şimdi ilk gerçek örneğimize geçelim. last -F çıktısından kullanıcı bazlı toplam süreyi hesaplayan basit bir awk scripti:

last -F | grep -v "^$|wtmp|reboot|still" | awk '
{
    # Son alan saat formatında (08:21) ise onu parse et
    n = split($NF, t, ":")
    if (n == 2) {
        # Parantezleri temizle
        gsub(/[()]/, "", t[1])
        gsub(/[()]/, "", t[2])
        saat = t[1] + 0
        dakika = t[2] + 0
        toplam_dakika = saat * 60 + dakika
        kullanici_sure[$1] += toplam_dakika
        kullanici_giris[$1]++
    }
}
END {
    print "KullanicittToplam Sure (dk)tGiris Sayisi"
    print "--------tt----------------t------------"
    for (k in kullanici_sure) {
        printf "%-20st%d dk (%d saat)tt%dn", k, kullanici_sure[k], int(kullanici_sure[k]/60), kullanici_giris[k]
    }
}
'

Bu script, her kullanıcının toplam bağlantı süresini ve kaç kez bağlandığını verir. Güzel, ama ham veri. Daha iyisini yapabiliriz.

Auth.log Üzerinden Gerçek Zaman Hesaplama

last komutu kullanışlı ama bazen doğrudan auth.log okumak zorundasınız, özellikle belirli bir zaman aralığını filtrelemek istediğinizde. Şimdi daha sofistike bir yaklaşım:

grep "sshd" /var/log/auth.log | grep -E "Accepted|Disconnected" | awk '
BEGIN {
    # Ay isimleri -> numara mapping
    ay["Jan"] = 1; ay["Feb"] = 2; ay["Mar"] = 3; ay["Apr"] = 4
    ay["May"] = 5; ay["Jun"] = 6; ay["Jul"] = 7; ay["Aug"] = 8
    ay["Sep"] = 9; ay["Oct"] = 10; ay["Nov"] = 11; ay["Dec"] = 12
}

/Accepted/ {
    # Format: Jan 15 09:23:41
    kullanici = $9
    split($3, zaman, ":")
    # Basit epoch benzeri hesaplama (gün içi dakika)
    giris_dk = zaman[1] * 60 + zaman[2]
    baslangic[kullanici] = giris_dk
    baslangic_saat[kullanici] = $3
}

/Disconnected from user/ {
    kullanici = $9
    split($3, zaman, ":")
    cikis_dk = zaman[1] * 60 + zaman[2]
    
    if (kullanici in baslangic) {
        sure = cikis_dk - baslangic[kullanici]
        if (sure > 0) {
            toplam[kullanici] += sure
            giris_sayisi[kullanici]++
        }
        delete baslangic[kullanici]
    }
}

END {
    for (k in toplam) {
        saat = int(toplam[k] / 60)
        dk = toplam[k] % 60
        printf "Kullanici: %-15s | Toplam: %02d:%02d | Giris: %dn", k, saat, dk, giris_sayisi[k]
    }
}
'

Bu örnekte dikkat edilmesi gereken bir nokta: gece yarısını geçen oturumlar negatif değer üretebilir. Production’da bunu handle etmek için mktime kullanmak daha sağlıklı.

GNU Awk ile Doğru Epoch Hesaplama

İşte burada gawk’ın gücü devreye giriyor. mktime fonksiyonu ile gerçek epoch saniyelerini hesaplayabilir ve herhangi bir süreyi doğru şekilde bulabilirsiniz:

grep "sshd" /var/log/auth.log | grep -E "Accepted password for|Disconnected from user" | gawk '
BEGIN {
    yil = strftime("%Y", systime())
    ay_num["Jan"] = "01"; ay_num["Feb"] = "02"; ay_num["Mar"] = "03"
    ay_num["Apr"] = "04"; ay_num["May"] = "05"; ay_num["Jun"] = "06"
    ay_num["Jul"] = "07"; ay_num["Aug"] = "08"; ay_num["Sep"] = "09"
    ay_num["Oct"] = "10"; ay_num["Nov"] = "11"; ay_num["Dec"] = "12"
}

function log_epoch(ay_str, gun, zaman_str,    parcalar, mktime_str) {
    split(zaman_str, parcalar, ":")
    mktime_str = yil " " ay_num[ay_str]+0 " " gun+0 " " parcalar[1]+0 " " parcalar[2]+0 " " parcalar[3]+0
    return mktime(mktime_str)
}

/Accepted password for/ {
    kullanici = $9
    epoch = log_epoch($1, $2, $3)
    # Birden fazla aktif oturum varsa stack kullan
    oturum_no[kullanici]++
    baslangic[kullanici, oturum_no[kullanici]] = epoch
}

/Disconnected from user/ {
    kullanici = $9
    epoch = log_epoch($1, $2, $3)
    
    # En son aktif oturumu bul
    if (oturum_no[kullanici] > 0) {
        sure = epoch - baslangic[kullanici, oturum_no[kullanici]]
        if (sure > 0) {
            toplam_sure[kullanici] += sure
            giris_sayisi[kullanici]++
        }
        delete baslangic[kullanici, oturum_no[kullanici]]
        oturum_no[kullanici]--
    }
}

END {
    printf "%-20s %-15s %-15s %-10sn", "KULLANICI", "TOPLAM SURE", "ORT. OTURUM", "GIRIS"
    printf "%sn", "------------------------------------------------------------"
    for (k in toplam_sure) {
        toplam_sn = toplam_sure[k]
        saat = int(toplam_sn / 3600)
        dk = int((toplam_sn % 3600) / 60)
        ort = int(toplam_sn / giris_sayisi[k] / 60)
        printf "%-20s %02d saat %02d dk   %-12d dk   %dn", k, saat, dk, ort, giris_sayisi[k]
    }
}
'

Bu versiyon, birden fazla eş zamanlı oturumu da destekliyor. Production’da bu şart, çünkü bir kullanıcı iki farklı terminalden bağlanmış olabilir.

Belirli Bir Tarih Aralığını Filtreleme

Güzel raporlar yapmak için belirli tarih aralıklarını filtreleyebilmek gerekiyor. Awk ile bunu da halledebiliriz:

# Sadece bu haftanın verilerini analiz et
BASLANGIC="Jan 13"
BITIS="Jan 19"

grep "sshd.*Accepted|sshd.*Disconnected" /var/log/auth.log | gawk -v baslangic="$BASLANGIC" -v bitis="$BITIS" '
BEGIN {
    ay_num["Jan"] = 1; ay_num["Feb"] = 2; ay_num["Mar"] = 3
    ay_num["Apr"] = 4; ay_num["May"] = 5; ay_num["Jun"] = 6
    ay_num["Jul"] = 7; ay_num["Aug"] = 8; ay_num["Sep"] = 9
    ay_num["Oct"] = 10; ay_num["Nov"] = 11; ay_num["Dec"] = 12
    
    split(baslangic, b, " ")
    split(bitis, bt, " ")
    bas_ay = ay_num[b[1]]; bas_gun = b[2]+0
    bit_ay = ay_num[bt[1]]; bit_gun = bt[2]+0
}

{
    log_ay = ay_num[$1]
    log_gun = $2 + 0
    
    # Tarih aralığı kontrolü
    if ((log_ay > bas_ay || (log_ay == bas_ay && log_gun >= bas_gun)) &&
        (log_ay < bit_ay || (log_ay == bit_ay && log_gun <= bit_gun))) {
        
        if (/Accepted password for/) {
            kullanici = $9
            split($3, z, ":")
            giris_dk[kullanici] = log_ay * 43200 + log_gun * 1440 + z[1] * 60 + z[2]
        }
        
        if (/Disconnected from user/) {
            kullanici = $9
            split($3, z, ":")
            cikis_dk = log_ay * 43200 + log_gun * 1440 + z[1] * 60 + z[2]
            
            if (kullanici in giris_dk && cikis_dk > giris_dk[kullanici]) {
                sure_dk[kullanici] += cikis_dk - giris_dk[kullanici]
                sayac[kullanici]++
                delete giris_dk[kullanici]
            }
        }
    }
}

END {
    for (k in sure_dk) {
        printf "%s: %d saat %d dakika (%d oturum)n", k, int(sure_dk[k]/60), sure_dk[k]%60, sayac[k]
    }
}
'

Günlük Aktivite Raporu ve Peak Saat Analizi

Sadece “kim ne kadar bağlı kaldı” değil, “kimler hangi saatlerde sisteme bağlanıyor” da önemli bir analiz. Bu bilgi hem güvenlik hem de kapasite planlaması açısından değerli:

grep "Accepted password" /var/log/auth.log | gawk '
{
    kullanici = $9
    split($3, zaman, ":")
    saat = zaman[1] + 0
    
    # Saat bazlı bağlantı sayısı
    saat_bazli[kullanici, saat]++
    genel_saat[saat]++
    toplam_giris[kullanici]++
}

END {
    print "n=== GENEL SISTEM YOGUNLUGU (SAAT BAZLI) ==="
    for (s = 0; s < 24; s++) {
        if (genel_saat[s] > 0) {
            bar = ""
            for (i = 0; i < genel_saat[s]; i++) bar = bar "#"
            printf "%02d:00  [%-30s] %d girisn", s, bar, genel_saat[s]
        }
    }
    
    print "n=== KULLANICI BAZLI EN AKTIF SAATLER ==="
    for (anahtar in saat_bazli) {
        split(anahtar, parca, SUBSEP)
        kullanici = parca[1]
        saat = parca[2]
        if (saat_bazli[anahtar] > en_aktif[kullanici]) {
            en_aktif[kullanici] = saat_bazli[anahtar]
            en_aktif_saat[kullanici] = saat
        }
    }
    
    for (k in en_aktif) {
        printf "%-20s En aktif: %02d:00 (%d giris) | Toplam giris: %dn",
            k, en_aktif_saat[k], en_aktif[k], toplam_giris[k]
    }
}
'

Bu scriptin çıktısı size sistemin hangi saatlerde yoğun kullanıldığını ASCII bar grafiği olarak gösterir. Müşteriye ya da üst yönetime rapor götürürken görsel açıdan da anlamlı.

Anormal Oturum Sürelerini Tespit Etme

Güvenlik perspektifinden bakıldığında, olağandışı kısa ya da olağandışı uzun oturumlar dikkat çekmeli. Çok kısa oturumlar brute-force denemelerinin işareti olabilir; çok uzun oturumlar ise unutulmuş ya da ele geçirilmiş bir bağlantıyı işaret edebilir:

grep "sshd" /var/log/auth.log | grep -E "Accepted|Disconnected" | gawk '
BEGIN {
    CIKTI_ESIK_KISA = 2    # 2 dakikadan kisa oturumlar suphelidir
    CIKTI_ESIK_UZUN = 480  # 8 saatten uzun oturumlar dikkat cekici
}

/Accepted password for/ {
    kullanici = $9
    ip = $11
    split($3, z, ":")
    sure_dk = z[1] * 60 + z[2]
    aktif_giris[kullanici] = sure_dk
    aktif_ip[kullanici] = ip
}

/Disconnected from user/ {
    kullanici = $9
    split($3, z, ":")
    cikis_dk = z[1] * 60 + z[2]
    
    if (kullanici in aktif_giris) {
        sure = cikis_dk - aktif_giris[kullanici]
        
        if (sure > 0 && sure < CIKTI_ESIK_KISA) {
            kisa_sayac[kullanici]++
            printf "[UYARI-KISA] %s - IP: %s - Sure: %d saniye - Tarih: %s %sn",
                kullanici, aktif_ip[kullanici], sure*60, $1, $2
        } else if (sure > CIKTI_ESIK_UZUN) {
            printf "[BILGI-UZUN] %s - IP: %s - Sure: %d saat - Tarih: %s %sn",
                kullanici, aktif_ip[kullanici], int(sure/60), $1, $2
        }
        
        delete aktif_giris[kullanici]
    }
}

END {
    if (length(kisa_sayac) > 0) {
        print "n=== COK SAYIDA KISA OTURUM (BRUTE-FORCE ADAYI) ==="
        for (k in kisa_sayac) {
            if (kisa_sayac[k] > 5) {
                printf "KRITIK: %s - %d adet seffaf oturumn", k, kisa_sayac[k]
            }
        }
    }
}
'

Bu tip bir script’i cron’a ekleyip günlük mail raporuna dahil ettiğimde, bir hafta içinde üç farklı brute-force girişimini yakaladım. Fail2ban zaten devredeydi ama bu analiz bana pattern’leri gösterdi.

Haftalık Özet Raporu Oluşturma

Son olarak her şeyi bir araya getirip CSV formatında çıktı veren, cron ile haftalık çalıştırabileceğiniz bir script:

#!/bin/bash
# haftalik_rapor.sh
# Kullanim: bash haftalik_rapor.sh > rapor_$(date +%Y%m%d).csv

LOG_DOSYA="/var/log/auth.log"
RAPOR_TARIHI=$(date '+%Y-%m-%d %H:%M')

echo "Kullanici Aktivite Raporu - $RAPOR_TARIHI"
echo "Kullanici,Toplam_Sure_Dakika,Giris_Sayisi,Ort_Oturum_Dk,En_Uzun_Oturum_Dk"

grep "sshd" "$LOG_DOSYA" | grep -E "Accepted password for|Disconnected from user" | gawk '
/Accepted password for/ {
    kullanici = $9
    split($3, z, ":")
    giris_dk[kullanici] = z[1] * 60 + z[2]
    giris_gun[kullanici] = $2 + 0
}

/Disconnected from user/ {
    kullanici = $9
    split($3, z, ":")
    cikis_dk = z[1] * 60 + z[2]
    cikis_gun = $2 + 0
    
    if (kullanici in giris_dk) {
        gun_farki = cikis_gun - giris_gun[kullanici]
        sure = cikis_dk - giris_dk[kullanici] + (gun_farki * 1440)
        
        if (sure > 0) {
            toplam[kullanici] += sure
            sayac[kullanici]++
            if (sure > en_uzun[kullanici]) en_uzun[kullanici] = sure
        }
        delete giris_dk[kullanici]
        delete giris_gun[kullanici]
    }
}

END {
    for (k in toplam) {
        ort = (sayac[k] > 0) ? int(toplam[k] / sayac[k]) : 0
        printf "%s,%d,%d,%d,%dn", k, toplam[k], sayac[k], ort, en_uzun[k]
    }
}
'

Bu scripti /etc/cron.d/aktivite_rapor dosyasına ekleyip her Pazartesi sabahı mail gönderecek şekilde ayarlayabilirsiniz.

Dikkat Edilmesi Gereken Pratik Noktalar

Gerçek dünyada bu scriptleri kullanırken karşılaştığım birkaç önemli durum var:

  • Log rotation: /var/log/auth.log.1 veya gzip’li dosyalara da bakmanız gerekebilir. zcat /var/log/auth.log.*.gz | grep ... şeklinde pipe kurabilirsiniz.
  • Farklı log formatları: RHEL/CentOS’ta /var/log/secure kullanılır ve field pozisyonları zaman zaman farklı olabilir. Her script’i kendi ortamınızda test edin.
  • Sudo ve su oturumları: SSH dışında su veya sudo -i ile alınan oturumlar auth.log’da farklı field’larda görünür. Bunları da yakalamak istiyorsanız regex’i genişletmeniz gerekir.
  • Sistem kullanıcıları: daemon, www-data, nobody gibi sistem kullanıcılarını rapordan hariç tutmak için if (kullanici ~ /^[a-z][a-z0-9]+$/ && length(kullanici) > 3) gibi bir filtre ekleyebilirsiniz.
  • Zaman dilimi tutarsızlıkları: Özellikle birden fazla sunucuyu kapsayan log analizi yapıyorsanız, sunucuların UTC’ye set edilmiş olduğundan emin olun, yoksa hesaplamalar kayar.
  • awk vs gawk: mktime ve strftime sadece GNU awk’ta çalışır. macOS’ta built-in awk farklı davranır; orada brew install gawk yapmanız gerekir.

Sonuç

Awk ile kullanıcı aktivite analizi yapmak, önce karmaşık gelir ama alıştıktan sonra Python script’i yazmaktan çok daha hızlı sonuç ürettiğini fark edersiniz. Özellikle tek seferlik veya ara sıra çalıştırılan analizler için script yazma, bağımlılık kurma, çalıştırma döngüsü yerine tek bir awk komutu yeterli olur.

Bu yazıda anlattıklarım temel noktaları kapsıyor: last çıktısından süre hesabı, auth.log’dan oturum takibi, gawk ile doğru epoch hesaplama, tarih aralığı filtreleme, peak saat analizi, anormal oturum tespiti ve otomatik CSV raporu. Bunları kendi ortamınıza uyarlayarak başlayın, zamanla kendi log format’ınıza özel versiyonlarını geliştirin.

Log analizi tek seferlik bir iş değil, sürekli geliştirdiğiniz bir beceri. Her yeni log formatıyla karşılaştığınızda, awk’ın ne kadar esnek bir araç olduğunu bir kez daha göreceksiniz.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir