Accepts incoming events and correctly parses them into events. GeoLite2 integration complete"

This commit is contained in:
Dan Milne
2025-11-04 00:11:10 +11:00
parent 0cbd462e7c
commit 5ff166613e
49 changed files with 4489 additions and 322 deletions

View File

@@ -0,0 +1,117 @@
# frozen_string_literal: true
# PathScannerDetectorJob - Detects IPs hitting scanner paths and auto-bans them
#
# This job analyzes recent events to find IPs hitting common scanner/bot paths
# like /.env, /.git, /wp-admin, etc. When detected, it creates temporary IP
# block rules that expire after 24 hours.
#
# Schedule: Every 5 minutes (configured in initializer or cron)
class PathScannerDetectorJob < ApplicationJob
queue_as :default
# Common paths that scanners/bots hit
SCANNER_PATHS = %w[
/.env
/.git
/wp-admin
/wp-login.php
/phpMyAdmin
/phpmyadmin
/.aws
/.ssh
/admin
/administrator
/.config
/backup
/db_backup
/.DS_Store
/web.config
].freeze
# Minimum hits to be considered a scanner
MIN_SCANNER_HITS = 3
# Look back window
LOOKBACK_WINDOW = 5.minutes
# Ban duration
BAN_DURATION = 24.hours
def perform
scanner_ips = detect_scanner_ips
Rails.logger.info "PathScannerDetectorJob: Found #{scanner_ips.count} scanner IPs"
scanner_ips.each do |ip_data|
create_ban_rule(ip_data)
end
scanner_ips.count
end
private
def detect_scanner_ips
# Find IPs that have hit scanner paths multiple times recently
Event
.where("timestamp > ?", LOOKBACK_WINDOW.ago)
.where("request_path IN (?)", SCANNER_PATHS)
.group(:ip_address)
.select("ip_address, COUNT(*) as hit_count, GROUP_CONCAT(DISTINCT request_path) as paths")
.having("COUNT(*) >= ?", MIN_SCANNER_HITS)
.map do |event|
{
ip: event.ip_address,
hit_count: event.hit_count,
paths: event.paths.to_s.split(",")
}
end
end
def create_ban_rule(ip_data)
ip = ip_data[:ip]
# Check if rule already exists for this IP
existing_rule = Rule.active.network_rules.find_by(
"conditions ->> 'cidr' = ?", "#{ip}/32"
)
if existing_rule
Rails.logger.info "PathScannerDetectorJob: Rule already exists for #{ip}, skipping"
return
end
# Determine if IPv4 or IPv6
addr = IPAddr.new(ip)
rule_type = addr.ipv4? ? "network_v4" : "network_v6"
# Create the ban rule
rule = Rule.create!(
rule_type: rule_type,
action: "deny",
conditions: { cidr: "#{ip}/32" },
priority: 32,
expires_at: BAN_DURATION.from_now,
source: "auto:scanner_detected",
enabled: true,
metadata: {
reason: "Scanner detected: hit #{ip_data[:paths].join(', ')}",
hit_count: ip_data[:hit_count],
paths: ip_data[:paths],
detected_at: Time.current.iso8601,
auto_generated: true
}
)
Rails.logger.info "PathScannerDetectorJob: Created ban rule #{rule.id} for #{ip} (expires: #{rule.expires_at})"
rule
rescue IPAddr::InvalidAddressError => e
Rails.logger.error "PathScannerDetectorJob: Invalid IP address #{ip}: #{e.message}"
nil
rescue ActiveRecord::RecordInvalid => e
Rails.logger.error "PathScannerDetectorJob: Failed to create rule for #{ip}: #{e.message}"
nil
end
end