Selamlar, bu yazımda Splunk alarm çıktılarından firewall external feed için kullanılabilecek IP listesinin nasıl otomatik üretilebileceğini ele alacağız. Şimdiden keyifli okumalar.
SOC operasyonlarında Splunk üzerinde yazılan alarmlar sonucunda şüpheli IP adresleri tespit edilebilir. Bu IP adresleri firewall tarafında blocklist veya external feed mekanizmalarına beslenmek istenebilir.
Bu yazıda, Splunk alarm çıktılarında oluşan IP adreslerini firewall tarafında kullanılabilecek raw TXT formatında bir dosyaya otomatik olarak yazdıracağız. Bu yapıda Splunk firewall’a doğrudan API veya CLI ile bağlanmaz; yalnızca belirlenen dizinde güncel bir feed dosyası üretir.
Amaç
Kurulacak yapı ile hedeflenen akış aşağıdaki gibidir:
1. Splunk alarmı tetiklenir.
2. Alarm sonucunda block_ip alanı oluşur.
3. Run a Script adaptive response çalışır.
4. Script alarm result dosyasını okur.
5. Sadece public IPv4 adresleri alınır.
6. Private/internal IP adresleri hariç tutulur.
7. Duplicate ve TTL kontrolü yapılır.
8. /auto/feed/blocked_ips.txt dosyası güncellenir.

Dosya Yapısı
Çalışmada iki temel dosya kullanılmaktadır:
/auto/feed/blocked_ips.txt
/auto/feed/firewall_feed_state.csv
blocked_ips.txt
Firewall veya external feed mekanizmasının okuyacağı asıl raw TXT dosyasıdır. İçinde sadece IP adresleri bulunur.
5.181.87.195
91.177.237.201
130.12.182.231
firewall_feed_state.csv
Script’in takip dosyasıdır. IP adresinin ilk görülme zamanı, son görülme zamanı, kaç kez geldiği ve en son hangi alarmdan geldiği burada tutulur.
ip,first_seen,last_seen,count,last_alert
5.181.87.195,2026-07-28 10:00:00,2026-07-28 10:30:00,3,Manual_Test_Alert
91.177.237.201,2026-07-28 10:00:00,2026-07-28 10:00:00,1,Manual_Test_Alert
Public IP Kontrolü
Script yalnızca public IPv4 adreslerini feed dosyasına yazar. Aşağıdaki private, internal veya reserved IP blokları otomatik olarak exclude edilir:
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
127.0.0.0/8
169.254.0.0/16
0.0.0.0/8
224.0.0.0/4
255.255.255.255/32
Bu sayede 10.x, 172.16-31.x, 192.168.x gibi internal adreslerin yanlışlıkla firewall feed dosyasına yazılması engellenir.
Dosya Şişmesini Engelleme
Feed dosyasının kontrolsüz büyümemesi için script içerisinde iki limit kullanılmıştır:
TTL_DAYS=30
MAX_ENTRIES=50000
Bu ayarlara göre 30 günden eski IP adresleri listeden düşer ve feed içinde en fazla 50.000 IP tutulur. İhtiyaca göre bu değerler script içerisinden değiştirilebilir.
Script Neden App Altında Tutuldu?
Script doğrudan $SPLUNK_HOME/bin altında tutulursa upgrade veya bakım süreçlerinde kaybolma riski oluşabilir. Bu nedenle asıl script app altında tutulur:
/opt/splunk/etc/apps/TA_firewall_feed/bin/scripts/write_firewall_feed.sh
Splunk Run a Script adaptive response bazı sürümlerde script’i $SPLUNK_HOME/bin/scripts altında beklediği için bu konuma sadece symlink verilir:
/opt/splunk/bin/scripts/write_firewall_feed.sh
-> /opt/splunk/etc/apps/TA_firewall_feed/bin/scripts/write_firewall_feed.sh

Kurulum Adımları
1. Feed Dizinini Oluşturma
sudo mkdir -p /auto/feed
sudo touch /auto/feed/blocked_ips.txt
sudo touch /auto/feed/firewall_feed_state.csv
sudo chown -R splunk:splunk /auto/feed
sudo chmod 755 /auto
sudo chmod 755 /auto/feed
sudo chmod 644 /auto/feed/blocked_ips.txt
sudo chmod 644 /auto/feed/firewall_feed_state.csv

2. Splunk App Dizinini Oluşturma
sudo mkdir -p /opt/splunk/etc/apps/TA_firewall_feed/bin/scripts
sudo mkdir -p /opt/splunk/etc/apps/TA_firewall_feed/local
sudo chown -R splunk:splunk /opt/splunk/etc/apps/TA_firewall_feed

3. app.conf Oluşturma
sudo vi /opt/splunk/etc/apps/TA_firewall_feed/local/app.conf
[install]
is_configured = 0
[ui]
is_visible = 0
label = TA Firewall Feed
[launcher]
author = SOC
description = Firewall external feed script for Splunk alerts
version = 1.0.0

4. Script’i Eklemek
sudo vi /opt/splunk/etc/apps/TA_firewall_feed/bin/scripts/write_firewall_feed.sh
Aşağıdaki script alarm result dosyasından yalnızca block_ip kolonunu okur, private IP’leri hariç tutar, 30 günlük TTL ve 50.000 kayıt limiti uygular.
#!/bin/bash
FEED_FILE="/auto/feed/blocked_ips.txt"
STATE_FILE="/auto/feed/firewall_feed_state.csv"
LOCK_FILE="/tmp/write_firewall_feed.lock"
# IP kaç gün listede kalsın?
TTL_DAYS=30
# Maksimum kaç IP tutulsun?
MAX_ENTRIES=50000
RESULT_FILE="${SPLUNK_ARG_8:-$8}"
ALERT_NAME="${SPLUNK_ARG_4:-unknown_alert}"
if [ -z "$RESULT_FILE" ] || [ ! -r "$RESULT_FILE" ]; then
exit 1
fi
mkdir -p "$(dirname "$FEED_FILE")"
touch "$FEED_FILE"
touch "$STATE_FILE"
TMP_CSV="$(mktemp)"
TMP_IPS="$(mktemp)"
if gzip -t "$RESULT_FILE" 2>/dev/null; then
zcat "$RESULT_FILE" > "$TMP_CSV"
else
cat "$RESULT_FILE" > "$TMP_CSV"
fi
# Sadece block_ip kolonunu oku.
awk -F',' '
NR==1 {
for (i=1; i<=NF; i++) {
gsub(/"/, "", $i)
if ($i=="block_ip") col=i
}
next
}
col>0 {
gsub(/"/, "", $col)
print $col
}
' "$TMP_CSV" \
| grep -E '^([0-9]{1,3}\.){3}[0-9]{1,3}$' \
| awk -F. '
$1>=0 && $1<=255 &&
$2>=0 && $2<=255 &&
$3>=0 && $3<=255 &&
$4>=0 && $4<=255
' \
| sort -u > "$TMP_IPS"
if [ ! -s "$TMP_IPS" ]; then
rm -f "$TMP_CSV" "$TMP_IPS"
exit 0
fi
(
flock -x 200
python3 - "$TMP_IPS" "$STATE_FILE" "$FEED_FILE" "$ALERT_NAME" "$TTL_DAYS" "$MAX_ENTRIES" << 'PYCODE'
import csv
import ipaddress
import os
import sys
from datetime import datetime, timedelta
tmp_ips = sys.argv[1]
state_file = sys.argv[2]
feed_file = sys.argv[3]
alert_name = sys.argv[4]
ttl_days = int(sys.argv[5])
max_entries = int(sys.argv[6])
now = datetime.now()
now_str = now.strftime("%Y-%m-%d %H:%M:%S")
cutoff = now - timedelta(days=ttl_days)
blocked_networks = [
ipaddress.ip_network("10.0.0.0/8"),
ipaddress.ip_network("172.16.0.0/12"),
ipaddress.ip_network("192.168.0.0/16"),
ipaddress.ip_network("127.0.0.0/8"),
ipaddress.ip_network("169.254.0.0/16"),
ipaddress.ip_network("0.0.0.0/8"),
ipaddress.ip_network("224.0.0.0/4"),
ipaddress.ip_network("255.255.255.255/32"),
]
def is_allowed_public_ipv4(ip_text):
try:
ip_obj = ipaddress.ip_address(ip_text.strip())
if ip_obj.version != 4:
return False
for net in blocked_networks:
if ip_obj in net:
return False
return True
except ValueError:
return False
def parse_time(value):
try:
return datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
except Exception:
return None
state = {}
if os.path.exists(state_file) and os.path.getsize(state_file) > 0:
with open(state_file, newline="") as f:
reader = csv.DictReader(f)
for row in reader:
ip = row.get("ip", "").strip()
if not is_allowed_public_ipv4(ip):
continue
last_seen = parse_time(row.get("last_seen", ""))
if last_seen is None or last_seen < cutoff:
continue
try:
count = int(row.get("count") or 1)
except Exception:
count = 1
state[ip] = {
"ip": ip,
"first_seen": row.get("first_seen") or now_str,
"last_seen": row.get("last_seen") or now_str,
"count": count,
"last_alert": row.get("last_alert") or "",
}
with open(tmp_ips) as f:
for line in f:
ip = line.strip()
if not is_allowed_public_ipv4(ip):
continue
if ip in state:
state[ip]["last_seen"] = now_str
state[ip]["count"] = int(state[ip].get("count", 0)) + 1
state[ip]["last_alert"] = alert_name
else:
state[ip] = {
"ip": ip,
"first_seen": now_str,
"last_seen": now_str,
"count": 1,
"last_alert": alert_name,
}
rows = list(state.values())
rows.sort(key=lambda r: parse_time(r["last_seen"]) or now, reverse=True)
rows = rows[:max_entries]
tmp_state = state_file + ".tmp"
with open(tmp_state, "w", newline="") as f:
fieldnames = ["ip", "first_seen", "last_seen", "count", "last_alert"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)
os.replace(tmp_state, state_file)
ips = sorted([r["ip"] for r in rows])
tmp_feed = feed_file + ".tmp"
with open(tmp_feed, "w") as f:
for ip in ips:
f.write(ip + "\n")
os.replace(tmp_feed, feed_file)
PYCODE
) 200>"$LOCK_FILE"
rm -f "$TMP_CSV" "$TMP_IPS"
exit 0
5. Yetki ve Symlink
sudo chown splunk:splunk /opt/splunk/etc/apps/TA_firewall_feed/bin/scripts/write_firewall_feed.sh
sudo chmod 750 /opt/splunk/etc/apps/TA_firewall_feed/bin/scripts/write_firewall_feed.sh
sudo mkdir -p /opt/splunk/bin/scripts
sudo ln -sf /opt/splunk/etc/apps/TA_firewall_feed/bin/scripts/write_firewall_feed.sh /opt/splunk/bin/scripts/write_firewall_feed.sh
sudo chown -h splunk:splunk /opt/splunk/bin/scripts/write_firewall_feed.sh
Manuel Test
cat > /tmp/test_alert_result.csv << ‘EOF’
block_ip,event_count
5.181.87.195,20
10.113.4.44,50
91.177.237.201,12
192.168.1.10,5
EOF
sudo -u splunk SPLUNK_ARG_4=”Manual_Test_Alert” SPLUNK_ARG_8=”/tmp/test_alert_result.csv” /opt/splunk/etc/apps/TA_firewall_feed/bin/scripts/write_firewall_feed.sh
Feed dosyası kontrol edilir:
cat /auto/feed/blocked_ips.txt
Beklenen çıktı:
5.181.87.195
91.177.237.201

SPL Tarafında block_ip Standardı
Script sadece block_ip kolonunu okuduğu için alarm sonucunda engellenmek istenen IP alanı block_ip olarak normalize edilmelidir.
| eval block_ip=src_ip
| table block_ip
Data Model Bazlı Örnek
| tstats summariesonly=t
dc(All_Traffic.dest_ip) as unique_dests
dc(All_Traffic.dest_port) as unique_ports
count as conn_count
values(All_Traffic.action) as action
values(All_Traffic.vendor_action) as vendor_action
from datamodel=Network_Traffic.All_Traffic
where NOT All_Traffic.src_ip IN (“10.0.0.0/12″,”172.16.0.0/16″,”192.168.0.0/12”)
AND All_Traffic.transport IN (“tcp”)
by _time span=5m All_Traffic.src_ip
| `drop_dm_object_name(“All_Traffic”)`
| eval other_actions=mvfilter(NOT vendor_action IN (“server-rst”,”client-rst”,”deny”,”timeout”))
| where unique_dests >= 7 AND unique_ports >= 5 AND mvcount(other_actions) > 0
| eval block_ip=src_ip
| table block_ip
Enterprise Security Detection Ayarı
| Ayar | Değer |
| Create when | Number of Results is greater than 0 |
| Create | Once |
| Throttling | 1 hour |
| Fields to group by | block_ip |
| Run a script | write_firewall_feed.sh |
Create: Once seçilmesinin nedeni script’in alarm result dosyasındaki tüm block_ip değerlerini tek seferde okuyabilmesidir. For each result seçilirse her satır için ayrı script process’i oluşur; bu senaryoda gereksiz yük oluşturur.

Opsiyonel: HTTP Üzerinden Yayınlama
İlk kurulumda HTTP servisi zorunlu değildir. Eğer feed dosyasının URL üzerinden erişilebilir olması istenirse Apache/httpd veya mevcut web servis standardı ile /auto/feed/blocked_ips.txt dosyası yayınlanabilir.
Apache varsa:
sudo mkdir -p /var/www/html/firewall-feed
sudo ln -sf /auto/feed/blocked_ips.txt /var/www/html/firewall-feed/blocked_ips.txt
sudo chmod 755 /var/www/html/firewall-feed
sudo chmod 644 /auto/feed/blocked_ips.txt
Apache yoksa Ubuntu/Debian:
sudo apt install -y apache2
sudo systemctl enable apache2
sudo systemctl start apache2
RHEL/CentOS:
sudo yum install -y httpd
sudo systemctl enable httpd
sudo systemctl start httpd
Sonra tekrar symlink:
sudo mkdir -p /var/www/html/firewall-feed
sudo ln -sf /auto/feed/blocked_ips.txt /var/www/html/firewall-feed/blocked_ips.txt
5. URL test
Splunk sunucusunda test et:
curl http://127.0.0.1/firewall-feed/blocked_ips.txt
Sonra auto sunucusundan test edilmeli:
curl http://SPLUNK_SUNUCU_IP/firewall-feed/blocked_ips.txt
http://SPLUNK_SUNUCU_IP/firewall-feed/blocked_ips.txt
Dosya içeriği sadece şu formatta olur:
5.181.87.195
91.177.237.201
45.158.14.77

Kontrol Komutları
Dosya ve script kontrolleri:
ls -lah /opt/splunk/bin/scripts/write_firewall_feed.sh
ls -lah /opt/splunk/etc/apps/TA_firewall_feed/bin/scripts/write_firewall_feed.sh
cat /auto/feed/blocked_ips.txt
cat /auto/feed/firewall_feed_state.csv

Temizlik ve Rollback
Yanlış IP silmek için:
sudo -u splunk sed -i ‘/^YANLIS_IP$/d’ /auto/feed/blocked_ips.txt
sudo -u splunk sed -i ‘/^YANLIS_IP,/d’ /auto/feed/firewall_feed_state.csv
Tüm listeyi temizlemek için:
sudo -u splunk bash -c ‘> /auto/feed/blocked_ips.txt’
sudo -u splunk bash -c ‘> /auto/feed/firewall_feed_state.csv’
Sonuç
Bu çalışma ile Splunk alarm sonuçlarından firewall external feed için otomatik IP listesi üretildi. Yapı firewall tarafına doğrudan API/CLI bağlantısı gerektirmez; Splunk sadece raw TXT feed dosyasını üretir.
- Script yalnızca block_ip kolonunu okur.
- Private/internal IP adresleri exclude edilir.
- Aynı IP tekrar tekrar yazılmaz.
- 30 günden eski IP adresleri listeden düşer.
- En fazla 50.000 IP tutulur.
- Script app altında tutulduğu için upgrade süreçlerinde daha güvenli konumlandırılır.
Share this content: