Accepts incoming events and correctly parses them into events. GeoLite2 integration complete"
This commit is contained in:
117
app/jobs/path_scanner_detector_job.rb
Normal file
117
app/jobs/path_scanner_detector_job.rb
Normal 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
|
||||
Reference in New Issue
Block a user