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 gawk
    
  • dnf (Fedora/RHEL/CentOS Stream/Alma/Rocky):

    sudo dnf install -y jq curl gawk
    
  • zypper (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-release first.)
    • zypper: sudo zypper install -y fail2ban

Step 1: Normalize SSH logs into structured events

Different distros store SSH auth logs differently:

  • Debian/Ubuntu: /var/log/auth.log

  • RHEL/Fedora/openSUSE: /var/log/secure

  • Systemd 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.json is a JSON array of events from the last 24 hours.

  • Adjust the window: replace -1day with -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 fail2ban
    
  • Enable and start:

    sudo systemctl enable --now fail2ban
    sudo fail2ban-client status
    sudo fail2ban-client status sshd
    
  • Example tuning (/etc/fail2ban/jail.local):

    [sshd]
    enabled = true
    bantime = 1h
    findtime = 15m
    maxretry = 5
    

    Then reload:

    sudo systemctl reload fail2ban
    

2) SSH hardening (reduce attack surface)

  • Edit your SSH config: sudoedit /etc/ssh/sshd_config Recommended lines: PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes ChallengeResponseAuthentication no MaxAuthTries 3 Apply: sudo systemctl reload sshd || sudo systemctl reload ssh Always 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.json
    
  • If 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 --reload
    

    Sanity-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.