Add WafPolicies

This commit is contained in:
Dan Milne
2025-11-10 14:10:37 +11:00
parent af7413c899
commit 772fae7e8b
22 changed files with 1784 additions and 147 deletions

View File

@@ -32,10 +32,36 @@ class Event < ApplicationRecord
scope :by_ip, ->(ip) { where(ip_address: ip) }
scope :by_user_agent, ->(user_agent) { where(user_agent: user_agent) }
scope :by_waf_action, ->(waf_action) { where(waf_action: waf_action) }
scope :blocked, -> { where(waf_action: ['block', 'deny']) }
scope :allowed, -> { where(waf_action: ['allow', 'pass']) }
scope :blocked, -> { where(waf_action: :deny) }
scope :allowed, -> { where(waf_action: :allow) }
scope :rate_limited, -> { where(waf_action: 'rate_limit') }
# Network-based filtering scopes
scope :by_company, ->(company) {
joins("JOIN network_ranges ON events.ip_address <<= network_ranges.network")
.where("network_ranges.company ILIKE ?", "%#{company}%")
}
scope :by_network_type, ->(type) {
joins("JOIN network_ranges ON events.ip_address <<= network_ranges.network")
.case(type)
.when("datacenter") { where("network_ranges.is_datacenter = ?", true) }
.when("vpn") { where("network_ranges.is_vpn = ?", true) }
.when("proxy") { where("network_ranges.is_proxy = ?", true) }
.when("standard") { where("network_ranges.is_datacenter = ? AND network_ranges.is_vpn = ? AND network_ranges.is_proxy = ?", false, false, false) }
.else { none }
}
scope :by_asn, ->(asn) {
joins("JOIN network_ranges ON events.ip_address <<= network_ranges.network")
.where("network_ranges.asn = ?", asn.to_i)
}
scope :by_network_cidr, ->(cidr) {
joins("JOIN network_ranges ON events.ip_address <<= network_ranges.network")
.where("network_ranges.network = ?", cidr)
}
# Path prefix matching using range queries (uses B-tree index efficiently)
scope :with_path_prefix, ->(prefix_segment_ids) {
return none if prefix_segment_ids.blank?
@@ -112,10 +138,7 @@ class Event < ApplicationRecord
server_name: normalized_payload["server_name"],
environment: normalized_payload["environment"],
# Geographic data
country_code: normalized_payload.dig("geo", "country_code"),
city: normalized_payload.dig("geo", "city"),
# WAF agent info
agent_version: normalized_payload.dig("agent", "version"),
agent_name: normalized_payload.dig("agent", "name")
@@ -269,7 +292,7 @@ class Event < ApplicationRecord
def matching_network_ranges
return [] unless ip_address.present?
NetworkRange.contains_ip(ip_address).map do |range|
NetworkRange.contains_ip(ip_address.to_s).map do |range|
{
range: range,
cidr: range.cidr,
@@ -360,86 +383,34 @@ class Event < ApplicationRecord
active_blocking_rules.exists?
end
# GeoIP enrichment methods (now uses network range data when available)
def enrich_geo_location!
return if ip_address.blank?
return if country_code.present? # Already has geo data
# First try to get from network range
network_info = network_intelligence
if network_info[:country].present?
update!(country_code: network_info[:country])
return
end
# Fallback to direct lookup
country = GeoIpService.lookup_country(ip_address)
update!(country_code: country) if country.present?
rescue => e
Rails.logger.error "Failed to enrich geo location for event #{id}: #{e.message}"
end
# Class method to enrich multiple events
def self.enrich_geo_location_batch(events = nil)
events ||= where(country_code: [nil, '']).where.not(ip_address: [nil, ''])
updated_count = 0
events.find_each do |event|
next if event.country_code.present?
# Try network range first
network_info = event.network_intelligence
if network_info[:country].present?
event.update!(country_code: network_info[:country])
updated_count += 1
next
end
# Fallback to direct lookup
country = GeoIpService.lookup_country(event.ip_address)
if country.present?
event.update!(country_code: country)
updated_count += 1
end
end
updated_count
end
# Lookup country code for this event's IP
def lookup_country
return country_code if country_code.present?
return nil if ip_address.blank?
# First try network range
network_info = network_intelligence
return network_info[:country] if network_info[:country].present?
# Fallback to direct lookup
GeoIpService.lookup_country(ip_address)
rescue => e
Rails.logger.error "GeoIP lookup failed for #{ip_address}: #{e.message}"
nil
end
# Check if event has valid geo location data
def has_geo_data?
country_code.present? || city.present? || network_intelligence[:country].present?
end
# Get full geo location details
# Get full geo location details from network range
def geo_location
network_info = network_intelligence
{
country_code: country_code || network_info[:country],
city: city,
country_code: network_info[:country],
ip_address: ip_address,
has_data: has_geo_data?,
has_data: network_info[:country].present?,
network_intelligence: network_info
}
end
# Check if event has valid geo location data via network range
def has_geo_data?
network_intelligence[:country].present?
end
# Lookup country code for this event's IP via network range
def lookup_country
return nil if ip_address.blank?
network_info = network_intelligence
network_info[:country]
rescue => e
Rails.logger.error "Network lookup failed for #{ip_address}: #{e.message}"
nil
end
private
def should_normalize?
@@ -483,11 +454,7 @@ class Event < ApplicationRecord
self.server_name = payload["server_name"]
self.environment = payload["environment"]
# Extract geographic data
geo_data = payload.dig("geo") || {}
self.country_code = geo_data["country_code"]
self.city = geo_data["city"]
# Extract agent info
agent_data = payload.dig("agent") || {}
self.agent_version = agent_data["version"]