- Posted on
- • Artificial Intelligence
AI-Powered SSH Log Analysis
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
AI-Powered SSH Log Analysis: Turn Noisy Auth Logs into Actionable Insights
If you run any Linux server on the public internet, your SSH logs are under siege—botnets scanning 24/7, credential stuffing, and brute force bursts. The signal is there, but it’s buried in a haystack of repetitive entries. What if you could distill a day’s worth of SSH logs into a concise, risk-aware summary—automatically?
In this guide, you’ll build a lightweight Bash pipeline that:
Normalizes SSH logs into clean, machine-readable events
Aggregates “who/what/when” at a glance
Feeds the result to an LLM (local or cloud) for a readable security brief
Translates insights into concrete hardening steps
All with standard Linux tools—and a few optional add-ons.
Why AI-powered log analysis?
SSH logs are verbose. Hundreds or thousands of lines can represent a single campaign from a handful of IPs. Humans miss patterns; models summarize quickly.
Attack patterns evolve. AI excels at describing anomalies, correlating telltale combinations (e.g., username spraying + password failures + new geos).
It accelerates response. A short narrative with prioritized actions beats skimming raw logs.
This isn’t magic detection; it’s about faster triage with the logs you already have.
Prerequisites (install with your package manager)
You’ll need jq, curl, and gawk. Install with one of the following:
apt (Debian/Ubuntu):
sudo apt-get update sudo apt-get install -y jq curl gawkdnf (Fedora/RHEL/CentOS Stream/Alma/Rocky):
sudo dnf install -y jq curl gawkzypper (openSUSE/SLES):
sudo zypper refresh sudo zypper install -y jq curl gawk
Optional packages we’ll mention later:
- fail2ban
- apt:
sudo apt-get install -y fail2ban - dnf:
sudo dnf install -y fail2ban(On some RHEL-like systems you may need:sudo dnf install -y epel-releasefirst.) - zypper:
sudo zypper install -y fail2ban
- apt:
Step 1: Normalize SSH logs into structured events
Different distros store SSH auth logs differently:
Debian/Ubuntu:
/var/log/auth.logRHEL/Fedora/openSUSE:
/var/log/secureSystemd journal (portable):
journalctl
We’ll use journalctl for consistent parsing across distros and extract only the useful bits: timestamp, status (FAILED/ACCEPTED), user, IP, and auth method.
Save as sshlog-normalize.sh:
#!/usr/bin/env bash
set -euo pipefail
# Usage: ./sshlog-normalize.sh [since]
# Example: ./sshlog-normalize.sh -1day or "2024-07-01"
SINCE="${1:--1day}"
# Pull last window of ssh/sshd logs in a time-normalized format (epoch)
journalctl -u ssh -u sshd --since "$SINCE" --output short-unix --no-pager \
| gawk '
function print_event(epoch,status,user,ip,method){
if (user=="" || ip=="") return;
printf("{\"ts\":%d,\"status\":\"%s\",\"user\":\"%s\",\"ip\":\"%s\",\"method\":\"%s\"}\n", epoch, status, user, ip, method);
}
{
epoch=$1
# Remove leading epoch + space
sub(/^[0-9.]+ /,"",$0)
msg=$0
# Keep only message text after first ": "
i=index(msg,": ")
if(i>0){ msg=substr(msg, i+2) }
# Common sshd messages (IPv4/IPv6 supported loosely)
# Failed examples:
# Failed password for invalid user admin from 1.2.3.4 port 5555 ssh2
# Failed publickey for root from 2001:db8::1 port 5555 ssh2
if (match(msg, /^Failed ([^ ]+) for (invalid user )?([^ ]+) from ([^ ]+)/, a)) {
method=a[1]
user=a[3]
ip=a[4]
print_event(int(epoch), "FAILED", user, ip, method)
next
}
# Accepted examples:
# Accepted password for ubuntu from 1.2.3.4 port 5555 ssh2
# Accepted publickey for alice from 2001:db8::1 port 5555 ssh2
if (match(msg, /^Accepted ([^ ]+) for ([^ ]+) from ([^ ]+)/, a)) {
method=a[1]
user=a[2]
ip=a[3]
print_event(int(epoch), "ACCEPTED", user, ip, method)
next
}
}
'
Make it executable and run it:
chmod +x sshlog-normalize.sh
./sshlog-normalize.sh -1day | jq -s '.' > ssh_24h.json
Output:
ssh_24h.jsonis a JSON array of events from the last 24 hours.Adjust the window: replace
-1daywith-12hours,-7days, or a date like"2026-07-01".
Step 2: Summarize what matters (top IPs, users, spikes)
Let’s turn that JSON into a quick threat snapshot.
- Totals and top offenders:
jq '{
window: "last 24h",
totals: {
failed: ([.[] | select(.status=="FAILED")] | length),
accepted: ([.[] | select(.status=="ACCEPTED")] | length)
},
top_ips: (
[.[] | select(.status=="FAILED") | .ip]
| group_by(.) | map({ip:.[0], failed:length})
| sort_by(-.failed) | .[0:10]
),
top_users: (
[.[] | select(.status=="FAILED") | .user]
| group_by(.) | map({user:.[0], failed:length})
| sort_by(-.failed) | .[0:10]
),
failed_per_hour: (
[.[] | select(.status=="FAILED") | {h: ((.ts/3600|floor) % 24)}]
| group_by(.h) | map({hour:.[0].h, count:length})
| sort_by(.hour)
)
}' ssh_24h.json > metrics.json
- Preview the metrics:
jq '.' metrics.json
This already gives you:
Volume of failed vs. successful auth
Top attacking IPs and probed usernames
Hourly distribution of brute-force attempts (to see spikes)
Step 3: Ask an LLM for a concise, risk-aware summary
You have two simple paths: local (private) or cloud. Both take the metrics.json and produce a narrative plus recommendations.
A) Local model with Ollama (private, offline after model download)
Install Ollama (official script):
curl -fsSL https://ollama.com/install.sh | sh
Pull a small, capable model (example):
ollama pull llama3
Summarize your metrics:
cat << 'EOF' > ai_prompt.txt
You are a security analyst. You will receive aggregated SSH authentication metrics as JSON.
1) Explain notable patterns (top IPs, username spraying, method mix).
2) Identify likely false positives.
3) Recommend concrete mitigations (e.g., Fail2ban tuning, blocking ranges, SSH hardening).
Keep it concise and actionable.
Metrics:
EOF
{ cat ai_prompt.txt; cat metrics.json; } | ollama run llama3
Tip: Swap llama3 for any model you prefer that’s available in Ollama.
B) Cloud model via an OpenAI-compatible API (simple curl)
Assuming you have an API key in OPENAI_API_KEY:
export OPENAI_API_KEY="your_key_here"
Send the metrics for a chat-style completion:
curl -s https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d @- << 'JSON' | jq -r '.choices[0].message.content'
{
"model": "gpt-4o-mini",
"messages": [
{"role":"system","content":"You are a security analyst. Be concise and practical."},
{"role":"user","content": "Analyze these SSH authentication metrics and provide: patterns, likely false positives, and prioritized hardening steps.\n\n```json\n" }
]
}
JSON
Note: The above example shows the pattern. In practice, you can inline metrics.json content inside the user message (or send as a tool/input attachment if your provider supports it). Keep payload sizes manageable by only sending aggregates, not raw events.
Step 4: Turn insights into hardening (immediate wins)
Use the model’s analysis to guide action. Here are pragmatic steps you can automate safely.
1) Fail2ban (rate-limit bad actors)
Install:
- apt:
sudo apt-get install -y fail2ban- dnf:
sudo dnf install -y fail2ban- zypper:
sudo zypper install -y fail2banEnable and start:
sudo systemctl enable --now fail2ban sudo fail2ban-client status sudo fail2ban-client status sshdExample tuning (
/etc/fail2ban/jail.local):[sshd] enabled = true bantime = 1h findtime = 15m maxretry = 5Then reload:
sudo systemctl reload fail2ban
2) SSH hardening (reduce attack surface)
- Edit your SSH config:
sudoedit /etc/ssh/sshd_configRecommended lines:PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes ChallengeResponseAuthentication no MaxAuthTries 3Apply:sudo systemctl reload sshd || sudo systemctl reload sshAlways ensure you have working key-based access before disabling passwords.
3) Preview and (optionally) block worst offenders
Preview top attacking IPs (from metrics):
jq -r '.top_ips[] | "\(.ip) \(.failed)"' metrics.jsonIf confident, block a short list temporarily using your firewall. Example with firewalld:
for ip in $(jq -r '.top_ips[0:5] | .[].ip' metrics.json); do sudo firewall-cmd --add-rich-rule="rule family='ipv4' source address='${ip}' drop" done # Make permanent if you’re sure (optional): # for ip in $(jq -r '.top_ips[0:5] | .[].ip' metrics.json); do # sudo firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='${ip}' drop" # done # sudo firewall-cmd --reloadSanity-check IPs to avoid blocking legitimate automation or your own ranges.
Real-world example (what you might see)
totals: 18,452 failed vs. 6 accepted
top_ips: one IP with 7,900 failures; three with ~1,200 each
top_users: “root”, “admin”, “test”, “oracle”
failed_per_hour: spikes at 00:00–03:00 UTC
What an LLM might highlight:
Password-based brute-force with username spraying; very low success rate; likely generic botnet
No evidence of lateral movement from accepted sessions (all from your known office IP)
Recommendations: disable password login, enforce keys, tune Fail2ban to 1h bantime, block the worst /24 if abuse persists
Automate it (daily cron)
Drop a simple cron to generate metrics and a summary each morning:
#!/usr/bin/env bash
set -euo pipefail
cd /root/ssh-ai || exit 1
./sshlog-normalize.sh -1day | jq -s '.' > ssh_24h.json
jq -f summarize.jq ssh_24h.json > metrics.json # or use the inline jq from Step 2
{ cat ai_prompt.txt; cat metrics.json; } | ollama run llama3 > summary.txt
mail -s "Daily SSH Summary" you@example.com < summary.txt
Conclusion and next steps
Your SSH logs already tell a story—AI just makes it faster to read. With a small Bash pipeline and either a local or cloud LLM, you can:
Spot spikes and campaigns at a glance
Prioritize actions (hardening, rate limiting, targeted blocks)
Reduce alert fatigue and improve response time
Call to action:
Put the normalization script in place today and generate your first
metrics.json.Choose local (Ollama) or cloud for the AI summary and run it on a schedule.
Implement the top 2–3 hardening steps your summary recommends—especially key-based auth and Fail2ban.
Have a favorite model or a better parsing trick? Share it with the community and keep refining your pipeline.