- Posted on
- • Artificial Intelligence
Artificial Intelligence Security Monitoring
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
AI-Powered Security Monitoring on Linux (with Bash-friendly workflows)
If your Linux servers feel like they’re drowning in logs, you’re not alone. SSH brute-force attempts, odd process spawns, suspicious IPs—it’s too much for humans to triage in real time. The value of Artificial Intelligence in security monitoring is simple: it learns what “normal” looks like on your systems and flags what doesn’t, so you can respond faster and reduce alert fatigue.
This article shows you how to bolt a small, auditable AI layer onto your existing Linux logging using journalctl, Bash pipelines, and a tiny Python model. You’ll get practical commands, code you can read end-to-end, and distro-agnostic install snippets (apt, dnf, zypper).
Why AI for Linux security monitoring?
Scale: Logs grow faster than your team. Models don’t get tired.
Baselines > signatures: ML can highlight deviations you didn’t predefine in a rule.
Faster triage: Focus analyst attention on the 1% of events that look weird.
Cost-effective: Use the logs you already have (journald, SSH), and a lightweight model.
What we’ll build
A lightweight pipeline that reads SSH auth events from journald.
A small Isolation Forest anomaly model to learn normal login patterns.
A streaming scorer that flags suspicious events and can notify Slack or syslog.
Optional systemd unit to run it as a service.
You can extend this same pattern to other streams: sudo, kernel module loads, package installations, container runtime events, etc.
Prerequisites (install once)
Python 3, virtualenv, pip
jq (JSON parsing for quick CLI inspection)
curl (for webhooks/alerts)
git (optional, to version your config)
For Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y python3 python3-venv python3-pip jq curl git
For Fedora/RHEL/CentOS (dnf):
sudo dnf install -y python3 python3-pip python3-virtualenv jq curl git
For openSUSE/SLES (zypper):
sudo zypper refresh
sudo zypper install -y python3 python3-pip python3-virtualenv jq curl git
Recommended: allow a non-root user to read the journal (log out/in or newgrp after running):
sudo usermod -aG systemd-journal $USER
Create a workspace:
sudo mkdir -p /opt/ai-guard
sudo chown -R $USER:$USER /opt/ai-guard
cd /opt/ai-guard
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install scikit-learn pandas numpy joblib python-dateutil
1) Collect high-signal events (SSH via journald)
We’ll use journald as our source (no extra agents needed). Verify SSH logs are available:
journalctl -u ssh -u sshd -n 20
JSON view (for parsing and spot-checks):
journalctl -u ssh -u sshd -o json -n 5 | jq .
Pro tip: if your distro uses sshd, the -u ssh -u sshd pattern covers both.
2) Train a tiny anomaly model (Isolation Forest)
This script pulls a time window of SSH events from journald, extracts simple features (e.g., hour of day, failed/success state, username characteristics, IP octets), and trains an Isolation Forest. Save as train_ai_guard.py.
#!/usr/bin/env python3
import json, subprocess, re, sys, os, time
from datetime import datetime, timezone
from dateutil import parser as dtparser
import numpy as np
from sklearn.ensemble import IsolationForest
import joblib
SINCE = os.environ.get("AIGUARD_SINCE", "7 days ago")
MODEL_PATH = os.environ.get("AIGUARD_MODEL", "model.joblib")
ip_re = re.compile(r'from ([0-9]{1,3}(?:\.[0-9]{1,3}){3})')
user_re = re.compile(r'for (?:invalid user )?(\S+)')
def extract(line):
try:
obj = json.loads(line)
except json.JSONDecodeError:
return None
msg = obj.get("MESSAGE","")
if "sshd" not in obj.get("SYSLOG_IDENTIFIER",""):
return None
# timestamp
ts = obj.get("__REALTIME_TIMESTAMP")
if ts:
# microseconds since epoch
dt = datetime.fromtimestamp(int(ts)/1_000_000, tz=timezone.utc)
else:
# fallback parse if present
dt = dtparser.parse(obj.get("_SOURCE_REALTIME_TIMESTAMP","1970-01-01T00:00:00Z"))
hour = dt.hour
failed = 1 if ("Failed password" in msg or "Failed publickey" in msg) else 0
success = 1 if ("Accepted password" in msg or "Accepted publickey" in msg) else 0
invalid = 1 if "invalid user" in msg else 0
m_ip = ip_re.search(msg)
ip = m_ip.group(1) if m_ip else "0.0.0.0"
octets = [int(x) for x in ip.split(".")] if "." in ip else [0,0,0,0]
m_user = user_re.search(msg)
user = m_user.group(1) if m_user else "-"
user_len = min(len(user), 32)
# crude username entropy proxy: # of unique chars (bounded)
entropyish = min(len(set(user)), 32)
# numeric feature vector
feats = [
hour,
failed,
success,
invalid,
user_len,
entropyish,
octets[0], octets[1], octets[2], octets[3]
]
return feats
def main():
print(f"[train] Fetching journald events since: {SINCE}", file=sys.stderr)
cmd = ["journalctl", "-u", "ssh", "-u", "sshd", "-o", "json", "--since", SINCE]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True)
X = []
for line in proc.stdout:
feats = extract(line)
if feats:
X.append(feats)
if not X:
print("[train] No events found. Expand the time window.", file=sys.stderr)
sys.exit(1)
X = np.array(X, dtype=float)
print(f"[train] Training on {len(X)} events...", file=sys.stderr)
# contamination ~ expected outlier fraction; tune to your environment
model = IsolationForest(n_estimators=200, contamination=0.01, random_state=42)
model.fit(X)
joblib.dump({"model": model}, MODEL_PATH)
print(f"[train] Saved model to {MODEL_PATH}", file=sys.stderr)
if __name__ == "__main__":
main()
Train the model:
cd /opt/ai-guard
source .venv/bin/activate
python3 train_ai_guard.py
Tip: Re-train periodically (e.g., weekly) to capture normal seasonality in your environment.
3) Stream in real-time and alert
This script tails SSH events, scores them, and emits alerts when the anomaly score crosses a threshold. Save as stream_ai_guard.py.
#!/usr/bin/env python3
import json, subprocess, re, os, sys, time
import numpy as np
import joblib
from datetime import datetime, timezone
from dateutil import parser as dtparser
MODEL_PATH = os.environ.get("AIGUARD_MODEL", "model.joblib")
THRESHOLD = float(os.environ.get("AIGUARD_THRESHOLD", "-0.10")) # lower = more anomalous
SLACK_WEBHOOK = os.environ.get("SLACK_WEBHOOK_URL") # optional
ip_re = re.compile(r'from ([0-9]{1,3}(?:\.[0-9]{1,3}){3})')
user_re = re.compile(r'for (?:invalid user )?(\S+)')
def extract(obj):
msg = obj.get("MESSAGE","")
if "sshd" not in obj.get("SYSLOG_IDENTIFIER",""):
return None, None
ts = obj.get("__REALTIME_TIMESTAMP")
if ts:
dt = datetime.fromtimestamp(int(ts)/1_000_000, tz=timezone.utc)
else:
dt = dtparser.parse(obj.get("_SOURCE_REALTIME_TIMESTAMP","1970-01-01T00:00:00Z"))
hour = dt.hour
failed = 1 if ("Failed password" in msg or "Failed publickey" in msg) else 0
success = 1 if ("Accepted password" in msg or "Accepted publickey" in msg) else 0
invalid = 1 if "invalid user" in msg else 0
m_ip = ip_re.search(msg)
ip = m_ip.group(1) if m_ip else "0.0.0.0"
octets = [int(x) for x in ip.split(".")] if "." in ip else [0,0,0,0]
m_user = user_re.search(msg)
user = m_user.group(1) if m_user else "-"
user_len = min(len(user), 32)
entropyish = min(len(set(user)), 32)
feats = [hour, failed, success, invalid, user_len, entropyish,
octets[0], octets[1], octets[2], octets[3]]
return feats, {"ts": dt.isoformat(), "ip": ip, "user": user, "msg": msg}
def slack_notify(text):
import urllib.request, json
if not SLACK_WEBHOOK:
return
data = json.dumps({"text": text}).encode("utf-8")
req = urllib.request.Request(SLACK_WEBHOOK, data=data, headers={"Content-Type":"application/json"})
try:
urllib.request.urlopen(req, timeout=3)
except Exception as e:
print(f"[slack] notify failed: {e}", file=sys.stderr)
def syslog_notify(text):
try:
subprocess.run(["logger", "-p", "auth.warning", text], check=False)
except Exception:
pass
def main():
payload = joblib.load(MODEL_PATH)
model = payload["model"]
cmd = ["journalctl", "-u", "ssh", "-u", "sshd", "-o", "json", "-f"]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True)
print("[stream] Ready. Scoring events...", file=sys.stderr)
for line in proc.stdout:
try:
obj = json.loads(line)
except json.JSONDecodeError:
continue
feats, meta = extract(obj)
if not feats:
continue
X = np.array(feats, dtype=float).reshape(1, -1)
score = float(model.decision_function(X)[0]) # >0 normal-ish, <0 outlier-ish
if score < THRESHOLD:
text = f"AI-Guard anomaly: score={score:.3f} ip={meta['ip']} user={meta['user']} ts={meta['ts']} msg='{meta['msg'][:140]}'"
print(text)
syslog_notify(text)
slack_notify(text)
if __name__ == "__main__":
main()
Run it:
cd /opt/ai-guard
source .venv/bin/activate
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/xxx/yyy/zzz" # optional
export AIGUARD_THRESHOLD="-0.10" # tune later
python3 stream_ai_guard.py
Expect lines like:
AI-Guard anomaly: score=-0.217 ip=203.0.113.45 user=admin ts=2026-07-10T09:03:17+00:00 msg='Failed password for invalid user admin from 203.0.113.45 port 54321 ssh2'
No Slack? You’ll still get messages via logger in /var/log/auth.log (Debian/Ubuntu) or journalctl -t logger.
If you plan to use Slack alerts, ensure curl is installed (for quick manual tests):
- apt:
sudo apt update && sudo apt install -y curl
- dnf:
sudo dnf install -y curl
- zypper:
sudo zypper install -y curl
4) Automate with systemd
Create a dedicated user (optional) and service.
Create /etc/systemd/system/ai-guard.service:
[Unit]
Description=AI Guard - SSH anomaly detection
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/ai-guard
ExecStart=/opt/ai-guard/.venv/bin/python3 /opt/ai-guard/stream_ai_guard.py
Environment=AIGUARD_MODEL=/opt/ai-guard/model.joblib
Environment=AIGUARD_THRESHOLD=-0.10
# Set SLACK_WEBHOOK_URL here if you use Slack:
# Environment=SLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx/yyy/zzz
User=root
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now ai-guard.service
sudo systemctl status ai-guard.service
Automate weekly retraining with a timer.
Create /etc/systemd/system/ai-guard-train.service:
[Unit]
Description=AI Guard - retrain model
[Service]
Type=oneshot
WorkingDirectory=/opt/ai-guard
ExecStart=/opt/ai-guard/.venv/bin/python3 /opt/ai-guard/train_ai_guard.py
Environment=AIGUARD_SINCE=30 days ago
Environment=AIGUARD_MODEL=/opt/ai-guard/model.joblib
User=root
Create /etc/systemd/system/ai-guard-train.timer:
[Unit]
Description=Weekly AI Guard retrain
[Timer]
OnCalendar=Sun 03:00
Persistent=true
Unit=ai-guard-train.service
[Install]
WantedBy=timers.target
Enable:
sudo systemctl daemon-reload
sudo systemctl enable --now ai-guard-train.timer
systemctl list-timers | grep ai-guard
5) Tuning, guardrails, and real-world tips
Tune contamination and threshold:
- If you see too many alerts, raise
AIGUARD_THRESHOLDtoward 0 (e.g., -0.05). - If you see too few, lower it (e.g., -0.20) or increase
contaminationin training.
- If you see too many alerts, raise
Cold starts: Train with at least several days of “normal” activity.
Validate: Compare alerts with known benign/bad events to calibrate.
Privacy: Avoid storing raw PII. The example only logs minimal context; adjust as needed.
Security hygiene:
- Keep the venv dependencies up to date:
pip list --outdatedthenpip install -U ... - Restrict access to
/opt/ai-guardand the service environment. - Version-control your scripts (git) and review diffs like any security change.
- Keep the venv dependencies up to date:
FAQ and extensions
Can I add more signals? Yes. Extend parsers for:
- sudo:
journalctl -t sudo -o json - kernel/FW:
journalctl -k -o json - container runtime (e.g., CRI): service units
- sudo:
Can I ship to SIEM? Pipe alerts to stdout and let your existing forwarder collect them, or send to a webhook with
curl.What about non-systemd distros? You can run the
stream_ai_guard.pyunder a nohup/supervisord/crontab. The journalctl part depends on systemd; if using syslog files, adapt the parser to read/var/log/auth.logor/var/log/secure.
Conclusion and next steps
You just added an AI “sentry” to your Linux box with a few commands and a couple of readable scripts. It’s not a silver bullet—but it’s a force multiplier that highlights the weird stuff so you can respond faster.
Your next steps:
Deploy the service on a staging host, tune
AIGUARD_THRESHOLD.Add a Slack webhook (or your alerting tool of choice) and test fail/success paths.
Extend the feature set for your environment (sudo, kernel, package mgmt).
Schedule weekly retrains and track alert precision over time.
If you found this useful, roll it out to a small fleet and iterate. Share your tweaks and field notes—Bash-friendly AI doesn’t have to be black-box magic.