Automating IPS: Real-Time Suricata Rule Generation via Fail2ban Hook

In my last posts, I established a central syslog hub feeding Fail2ban and demonstrated Suricata as an intrusion prevention system (IPS). This final piece connects the two: feeding Suricata with the ban results from Fail2ban by creating a dynamic, external rule file.

This process is highly automated, but requires robust Bash scripting and careful handling of security context.

1. Fail2ban Action and Scripting Logic

The core idea is to replace Fail2ban’s default firewall action with a custom script that modifies a public rule file.

Custom Action Definition

The actionban and actionunban directives in /etc/fail2ban/action.d/firewall.conf point to simple Bash wrappers.

[Definition]

actionstart = 
actionstop =

actioncheck =

actionban = /var/www/f2b/bin/ban.sh <ip>
actionunban = /var/www/f2b/bin/unban.sh <ip>

Security-Hardened ban.sh Script

The ban script must: (1) validate the input, (2) generate a unique Signature ID (SID), (3) append the rule, and (4) atomically update the ruleset’s MD5 checksum for Suricata-Update to fetch the change.

#!/bin/bash
#
# ban.sh: Adds a banned IP to the Fail2ban Suricata ruleset.

IP="$1"
RULESFILE="/var/www/f2b/htdocs/fail2ban.rules"
MSG="BRUTE-FORCE detected by fail2ban"

# INPUT VALIDATION: Ensure the input is a valid IPv4/IPv6 address.
if ! [[ $IP =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ || $IP =~ ^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$ ]]; then
    echo "ERROR: Invalid IP address received: $IP" >&2
    exit 1
fi

# 1. Generate Unique SID (Timestamp + Counter)
TSTAMP=$(date +%s)
CNT=$(wc -l $RULESFILE | cut -d' ' -f1);
# Generate a SID in a high range to avoid conflicts with commercial rules (e.g., 90000000+)
SID=$(($CNT + $TSTAMP + 90000000))

if ! grep -q "$IP" $RULESFILE; then
  # Rule: Drop all traffic from the banned IP to any network home port.
  # Using 'drop ip' is robust; adjust ports if required (e.g., $SSH_PORTS).
  RULE="drop ip $IP any -> \$HOME_NET any (msg:\"$MSG - $IP\"; sid:$SID; rev:1;)"

  echo $RULE >> $RULESFILE
  
  # Set correct permissions (Critical step for web delivery)
  chown www-data:www-data $RULESFILE

  # 2. Atomically update the MD5 checksum file
  SUM=$(md5sum $RULESFILE | cut -d' ' -f1);
  echo $SUM > $RULESFILE.md5
fi

The Unban Script (unban.sh)

The unban script removes the line and performs the critical MD5 update.

#!/bin/bash
#
# unban.sh: Removes a banned IP from the Suricata ruleset.

IP="$1"
RULESFILE="/var/www/f2b/htdocs/fail2ban.rules"

if grep -q "$IP" $RULESFILE; then
  # Use sed -i to remove the line containing the IP address
  sed -i '/'$IP'/d' $RULESFILE
  
  # Atomically update the MD5 checksum
  SUM=$(md5sum $RULESFILE | cut -d' ' -f1);
  echo $SUM > $RULESFILE.md5
fi

2. Integration and Verification

The final step is to make the ruleset publicly available (via HTTPS/SSL) and configure Suricata to fetch it.

Suricata-Update Configuration

The rule file (fail2ban.rules) must be made available via a web server (e.g., NGINX) with a specific URL (e.g., https://f2b.example.com/fail2ban.rules). I add this URL as a new source to Suricata-Update.

root@fw2:~# suricata-update add-source
URL: https://f2b.example.com/fail2ban.rules

# Running the update process
19/11/2023 -- 20:17:38 - <Info> -- Fetching https://f2b.example.com/fail2ban.rules.
 100% - 18344/18344                   
19/11/2023 -- 20:17:38 - <Info> -- Done.

Verification and Observability

Verification confirms that the new rules are loaded and actively dropping traffic. The log analysis command must be adapted to track these specific fail2ban drops.

# Awk command to filter and count dropped packets (Excerpt showing drop sources)
# awk '/Drop/{...}' fast.log | sort | uniq -c | sort -hr

   6505 IP dropped due to fail2ban detection
    638 ET DROP Dshield Block Listed Source 
    ...

This ensures a comprehensive, self-healing Incident Response Chain.

Sources / See Also

  1. Suricata Documentation. High-performance AF_PACKET IPS mode configuration and usage. https://docs.suricata.io/en/latest/install/af-packet.html
  2. Suricata Documentation. Working with Suricata-Update (Ruleset Management). https://suricata-update.readthedocs.io/en/latest/update.html
  3. Suricata Documentation. EVE JSON Output for Structured Logging. https://docs.suricata.io/en/latest/output/eve/eve-json-format.html
  4. Google Development. Google Perftools (TCMalloc) Documentation. https://github.com/google/gperftools
  5. Emerging Threats (Proofpoint). Information on the Emerging Threats Open Ruleset. https://www.proofpoint.com/us/security-awareness/blog/emerging-threats
  6. Elastic Stack (ELK) Documentation for Log Analysis. https://www.elastic.co/what-is/elk-stack
  7. Linux Manpage: ethtool (Network Offload Configuration). https://man7.org/linux/man-pages/man8/ethtool.8.html

Suricata Alert Analysis: Tuning Rules and Promoting Detection to Prevention

This is a follow-up to my last post in which I set up Suricata as an IPS. This article demonstrates how to effectively work with the Suricata engine—specifically, how I analyze its log output, silence unnecessary alerts, and promote specific detection rules to prevention rules.

1. Performance and Rule Management Setup

LibTCMalloc Integration

To enhance Suricata’s performance and stability, I integrate Google’s TCMalloc library to achieve memory usage improvements.

  1. Install the library: apt-get install libtcmalloc-minimal4
  2. Edit the Systemd service (systemctl edit suricata) to preload the library:
# /etc/systemd/system/suricata.service.d/override.conf
[Service]
Environment="LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4"

Rule Update Path Management

I correct the Debian setup where the default rule path conflicts with the update path. I align the configuration to use the dedicated data directory (/var/lib/suricata/rules) for updates, simplifying maintenance.

  1. Edit /etc/suricata/suricata.yaml to point the default rule path:default-rule-path: /var/lib/suricata/rules
  2. I ensure that update.yaml is configured correctly, and remove all initial rules from /etc/suricata/rules to avoid duplicate-rules warnings.

2. Alert Analysis and Rule Tuning (Observability in Practice)

By default, Suricata operates as an IDS (Intrusion Detection System). The critical first step is analyzing the generated alerts (fast.log) to separate actual threats from alert noise.

Initial Alert Frequency Analysis

The following command provides a crucial initial overview by counting unique alert messages and sorting them by frequency. This step is essential to understand the top sources of load and noise.

# awk '{$1=""; $2=""; $3=""}1' fast.log | sed 's_\[\*\*\].*__g' | sed 's_ group [0-9]*__g' | sort | uniq -c | sort -h

# Log Analysis (Excerpt showing frequency)
[..]
    100    GPL RPC portmap listing UDP 111 
    103    SURICATA STREAM 3way handshake excessive different SYN/ACKs 
    176    ET SCAN Suspicious inbound to PostgreSQL port 5432 
    216    ET SCAN Suspicious inbound to mySQL port 3306 
    223    SURICATA UDPv4 invalid checksum 
    236    SURICATA STREAM Last ACK with wrong seq 
    241    GPL ICMP_INFO PING speedera 
    325    ET SCAN Suspicious inbound to MSSQL port 1433 
    ...
  12872    GPL ICMP_INFO PING *NIX 

The Decision to Silence Noise

Alerts like the simple GPL ICMP_INFO PING *NIX often provide no actionable security value and must be disabled to prevent log flooding. I disable logging of ping probes by identifying the specific Signature IDs (SIDs) and adding them to a custom disable.conf file.

Code-Snippet

# /etc/suricata/disable.conf (Excerpt for ICMP PINGs)
# Disabled ping logging
2100366
...
2100480 

3. Promotion to IPS: Hardening the Drop Policy

For the system to transition from passive detection to active prevention (IPS), specific detection rules must be promoted to drop rules.

I promote the ET DROP Dshield Block Listed Source rule, as it targets known hostile IPs, by adding its SID to drop.conf.

# /etc/suricata/drop.conf
# Rules matching SIDs in this file will be converted to drop rules.
2402000 # SID for 'ET DROP Dshield Block Listed Source'

After running suricata-update, the engine confirms the change: -- Dropped 1 rules.

Verifying the Drop (Active Defense Check)

I verify the success of the active drop policy by specifically filtering for dropped packets in the logs.

# Command to output only dropped packets, showing the specific rule that triggered the block:
# awk '/Drop/{...}' fast.log | sort | uniq -c | sort -hr
# Example Output:
   6505 IP dropped due to fail2ban detection
    638 ET DROP Dshield Block Listed Source 

4. Advanced Rule Tuning: Leveraging Variables and Custom Logic

My advice is to use the variables whenever possible. By ensuring that network variables ($HOME_NET, $SMTP_SERVERS, etc.) correctly reflect your environment, you maximize the accuracy of existing rules. This prevents false positives and improves performance.

Enhancing Accuracy with Custom Rules

It’s crucial not just to disable bad rules, but to write custom rules that leverage these network variables for precise defense.

Example: Traffic Segregation Rule

To save resources, I would write a custom rule that only inspects for a vulnerability (e.g., a specific HTTP exploit) when the traffic comes from the external network and is destined for the correct server type.

# Example: Only check for sensitive SQL traffic if it comes from the EXTERNAL net.
# This prevents wasting resources checking internal-to-internal traffic.
# alert tcp $EXTERNAL_NET any -> $SQL_SERVERS 3306 (msg:"ET Custom: External Access to SQL Port"; ...)

This ensures that network resources are conserved by avoiding redundant checks on internal traffic.

5. Modern Analysis: Migrating from Bash to Structured Data

While the Bash pipeline is functional, high-traffic environments quickly overwhelm it. For modern Observability and SecOps analysis, the logs must be processed as structured data.

Migrating to EVE JSON

Suricata can output events in the EVE JSON format, which is ideal for ingestion into systems like Elasticsearch (ELK) or Splunk. This eliminates the slow and unreliable Bash parsing of fast.log.

Configuration Change (in suricata.yaml):

To migrate from the legacy fast.log format, you simply need to enable the EVE logger in your configuration.

# Output module setup in suricata.yaml
outputs:
  - eve-log:
      enabled: yes
      file: eve.json
      # Other settings (e.g., adding flow/metadata fields)

Python for High-Performance Analysis

Instead of relying on slow awk and sed pipelines, I recommend using Python for high-performance log analysis. Python’s built-in json library is optimized to read and aggregate large eve.json files far more efficiently. This elevates the analysis layer of the architecture to a production standard.

Sources / See Also

  1. Suricata Documentation. High-performance AF_PACKET IPS mode configuration and usage. https://docs.suricata.io/en/latest/install/af-packet.html
  2. Suricata Documentation. Working with Suricata-Update (Ruleset Management). https://suricata-update.readthedocs.io/en/latest/update.html
  3. Suricata Documentation. EVE JSON Output for Structured Logging. https://docs.suricata.io/en/latest/output/eve/eve-json-format.html
  4. Google Development. Google Perftools (TCMalloc) Documentation. https://github.com/google/gperftools
  5. Emerging Threats (Proofpoint). Information on the Emerging Threats Open Ruleset. https://www.proofpoint.com/us/security-awareness/blog/emerging-threats
  6. Elastic Stack (ELK) Documentation for Log Analysis. https://www.elastic.co/what-is/elk-stack
  7. Linux Manpage: ethtool (Network Offload Configuration). https://man7.org/linux/man-pages/man8/ethtool.8.html

Suricata IPS: Building a Transparent Network Defense Layer with AF-Packet Bridging

Suricata functions as a powerful engine for Network Intrusion Detection and Prevention (IDS/IPS). This guide demonstrates how to set up Suricata as a transparent Intrusion Prevention System (IPS) within a KVM environment by replacing the kernel bridge with the high-performance AF-Packet mechanism.

Continue reading Suricata IPS: Building a Transparent Network Defense Layer with AF-Packet Bridging

Automated Defense: Building a Central Log Hub for Fail2ban and External Firewall Integration

A very light-weight and efficient approach for consolidating logs centrally is by using rsyslog. My virtual machines all use rsyslog to forward their logs to a dedicated internal virtual machine, which acts as the central log hub. A fail2ban instance on this hub checks all incoming logs and sends a block command to an external firewall—a process helpful for automated security.

Continue reading Automated Defense: Building a Central Log Hub for Fail2ban and External Firewall Integration

ZFS Encryption: Mitigating Physical Attacks with Remote Key Management

This article documents the design and implementation of an external key management solution for ZFS encryption. This approach utilizes a custom PHP service to serve encryption keys on demand, specifically designed to mitigate physical and system-level compromises where local keys would fail. This deep dive explores the security architecture, the self-written PHP proof-of-concept (PoC), and the critical security caveats of building a custom Key Management System (KMS).

Continue reading ZFS Encryption: Mitigating Physical Attacks with Remote Key Management

Inside Amavisd-new: Advanced Features for Intelligent Mail Filtering

I guess that most people use amavisd-new together with spamassassin and for example ClamAV. Probably a few more use features like DKIM verification and signing with amavis. However, there are some features which aren’t found in the usual howtos. Here are some of them.

Continue reading Inside Amavisd-new: Advanced Features for Intelligent Mail Filtering

Redis Instance Isolation: Running Multi-Instance Redis with systemd Templates

Instead of running a single global Redis server, I prefer to use multiple isolated instances. This allows me to precisely limit resources like memory (maxmemory) and apply specific tuning per instance. This approach is fundamental to reliable operation in a shared environment. I used systemd templates to manage this, creating an instance for Amavisd-new as a practical example.

Continue reading Redis Instance Isolation: Running Multi-Instance Redis with systemd Templates

NGINX Hardening: Achieving A+ Security & Performance

Improving your web security and performance starts with a solid foundation. I regularly use external online generators and verification tools to ensure my NGINX configuration meets the highest standards. This guide details my steps to achieve an A+ security rating and optimal performance settings.

Continue reading NGINX Hardening: Achieving A+ Security & Performance

Advanced Mail Filtering: A Deep Dive into Amavisd-new and Amavisd-milter Policy Banks

Amavis isn’t new; in fact, AMaViS started as a shell program back in 1997. It has since evolved into a powerful, flexible tool for content filtering. This article will be a hands-on guide to setting up Amavisd-new with a milter and multiple policy banks. I’ll explain the critical difference between after- and before-queue filtering in Postfix, demonstrate how to use both, and show you how to split your mail traffic for a robust, multi-layered defense.

Continue reading Advanced Mail Filtering: A Deep Dive into Amavisd-new and Amavisd-milter Policy Banks

Email Signing and Verification with Amavisd-new and DKIM

I recently had a moment of “why did I do that?” when I temporarily disabled DKIM signing on my mail server. A quick email to a mailing list triggered a flood of DMARC authentication failure reports. It was a clear reminder that a surprising number of administrators have DMARC and DKIM reporting enabled.

Continue reading Email Signing and Verification with Amavisd-new and DKIM