Updates
This commit is contained in:
@@ -139,23 +139,37 @@ class GeoliteAsnImporter
|
||||
IPAddr.new(network) # This will raise if invalid
|
||||
|
||||
# Store raw GeoLite ASN data in network_data
|
||||
geolite_data = {
|
||||
geolite_asn_data = {
|
||||
asn: {
|
||||
autonomous_system_number: asn,
|
||||
autonomous_system_organization: asn_org
|
||||
}
|
||||
}
|
||||
|
||||
# Use upsert with JSONB merge
|
||||
# COALESCE handles the case where network_data might be NULL
|
||||
# || is PostgreSQL's JSONB concatenation/merge operator
|
||||
# jsonb_set merges the nested geolite data
|
||||
NetworkRange.upsert(
|
||||
{
|
||||
network: network,
|
||||
asn: asn,
|
||||
asn_org: asn_org,
|
||||
source: 'geolite_asn',
|
||||
network_data: { geolite: geolite_data },
|
||||
network_data: { geolite: geolite_asn_data },
|
||||
updated_at: Time.current
|
||||
},
|
||||
unique_by: :index_network_ranges_on_network_unique
|
||||
unique_by: :index_network_ranges_on_network_unique,
|
||||
on_duplicate: Arel.sql("
|
||||
asn = EXCLUDED.asn,
|
||||
asn_org = EXCLUDED.asn_org,
|
||||
network_data = COALESCE(network_ranges.network_data, '{}'::jsonb) ||
|
||||
jsonb_build_object('geolite',
|
||||
COALESCE(network_ranges.network_data->'geolite', '{}'::jsonb) ||
|
||||
EXCLUDED.network_data->'geolite'
|
||||
),
|
||||
updated_at = EXCLUDED.updated_at
|
||||
")
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ class GeoliteCountryImporter
|
||||
location_data = @locations_cache[geoname_id] || @locations_cache[registered_country_geoname_id] || {}
|
||||
|
||||
# Store raw GeoLite country data in network_data[:geolite]
|
||||
geolite_data = {
|
||||
geolite_country_data = {
|
||||
country: {
|
||||
geoname_id: geoname_id,
|
||||
registered_country_geoname_id: registered_country_geoname_id,
|
||||
@@ -227,16 +227,29 @@ class GeoliteCountryImporter
|
||||
}
|
||||
}.compact
|
||||
|
||||
# Use upsert with JSONB merge
|
||||
# COALESCE handles the case where network_data might be NULL
|
||||
# || is PostgreSQL's JSONB concatenation/merge operator
|
||||
NetworkRange.upsert(
|
||||
{
|
||||
network: network,
|
||||
country: location_data[:country_iso_code],
|
||||
is_proxy: is_anonymous_proxy,
|
||||
source: 'geolite_country',
|
||||
network_data: { geolite: geolite_data },
|
||||
network_data: { geolite: geolite_country_data },
|
||||
updated_at: Time.current
|
||||
},
|
||||
unique_by: :index_network_ranges_on_network_unique
|
||||
unique_by: :index_network_ranges_on_network_unique,
|
||||
on_duplicate: Arel.sql("
|
||||
country = EXCLUDED.country,
|
||||
is_proxy = EXCLUDED.is_proxy,
|
||||
network_data = COALESCE(network_ranges.network_data, '{}'::jsonb) ||
|
||||
jsonb_build_object('geolite',
|
||||
COALESCE(network_ranges.network_data->'geolite', '{}'::jsonb) ||
|
||||
EXCLUDED.network_data->'geolite'
|
||||
),
|
||||
updated_at = EXCLUDED.updated_at
|
||||
")
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ class IpRangeResolver
|
||||
|
||||
Rule.network_rules
|
||||
.where(network_range_id: range_ids)
|
||||
.where(action: 'deny')
|
||||
.where(waf_action: :deny)
|
||||
.enabled
|
||||
.where("expires_at IS NULL OR expires_at > ?", Time.current)
|
||||
.exists?
|
||||
@@ -158,7 +158,7 @@ class IpRangeResolver
|
||||
|
||||
Rule.network_rules
|
||||
.where(network_range_id: range_ids)
|
||||
.where(action: 'deny')
|
||||
.where(waf_action: :deny)
|
||||
.enabled
|
||||
.where("expires_at IS NULL OR expires_at > ?", Time.current)
|
||||
.includes(:network_range)
|
||||
|
||||
@@ -24,22 +24,6 @@ class NetworkRangeGenerator
|
||||
IPAddr.new('ff00::/8') # IPv6 multicast
|
||||
].freeze
|
||||
|
||||
# Special network ranges to avoid
|
||||
RESERVED_RANGES = [
|
||||
IPAddr.new('10.0.0.0/8'), # Private
|
||||
IPAddr.new('172.16.0.0/12'), # Private
|
||||
IPAddr.new('192.168.0.0/16'), # Private
|
||||
IPAddr.new('127.0.0.0/8'), # Loopback
|
||||
IPAddr.new('169.254.0.0/16'), # Link-local
|
||||
IPAddr.new('224.0.0.0/4'), # Multicast
|
||||
IPAddr.new('240.0.0.0/4'), # Reserved
|
||||
IPAddr.new('::1/128'), # IPv6 loopback
|
||||
IPAddr.new('fc00::/7'), # IPv6 private
|
||||
IPAddr.new('fe80::/10'), # IPv6 link-local
|
||||
IPAddr.new('ff00::/8') # IPv6 multicast
|
||||
].freeze
|
||||
|
||||
|
||||
class << self
|
||||
# Find or create a network range for the given IP address
|
||||
def find_or_create_for_ip(ip_address, user: nil)
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# WafPolicyMatcher - Service to match NetworkRanges against active WafPolicies
|
||||
# WafPolicyMatcher - Service to match Events against active WafPolicies
|
||||
#
|
||||
# This service provides efficient matching of network ranges against firewall policies
|
||||
# and can generate rules when matches are found.
|
||||
# This service provides efficient matching of events against firewall policies
|
||||
# (both network-based and path-based) and can generate rules when matches are found.
|
||||
class WafPolicyMatcher
|
||||
include ActiveModel::Model
|
||||
include ActiveModel::Attributes
|
||||
|
||||
attr_accessor :network_range
|
||||
attr_accessor :event
|
||||
attr_reader :matching_policies, :generated_rules
|
||||
|
||||
def initialize(network_range:)
|
||||
@network_range = network_range
|
||||
def initialize(event:)
|
||||
@event = event
|
||||
@matching_policies = []
|
||||
@generated_rules = []
|
||||
end
|
||||
|
||||
# Find all active policies that match the given network range
|
||||
# Helper method to get network range from event
|
||||
def network_range
|
||||
event&.network_range
|
||||
end
|
||||
|
||||
# Find all active policies that match the given event (network or path-based)
|
||||
def find_matching_policies
|
||||
return [] unless network_range.present?
|
||||
return [] unless event.present?
|
||||
|
||||
@matching_policies = active_policies.select do |policy|
|
||||
policy.matches_network_range?(network_range)
|
||||
policy.matches_event?(event)
|
||||
end
|
||||
|
||||
# Sort by priority: country > asn > company > network_type, then by creation date
|
||||
@@ -82,25 +87,56 @@ class WafPolicyMatcher
|
||||
end
|
||||
|
||||
# Class methods for batch processing
|
||||
def self.process_network_range(network_range)
|
||||
matcher = new(network_range: network_range)
|
||||
def self.process_event(event)
|
||||
matcher = new(event: event)
|
||||
matcher.match_and_generate_rules
|
||||
end
|
||||
|
||||
# Evaluate a network range against policies and mark it as evaluated
|
||||
# This is the main entry point for inline policy evaluation
|
||||
def self.evaluate_and_mark!(network_range)
|
||||
return { matching_policies: [], generated_rules: [] } unless network_range
|
||||
# Legacy method for backward compatibility - converts network range to event
|
||||
def self.process_network_range(network_range)
|
||||
# Find the most recent event for this network range
|
||||
sample_event = network_range.events.order(created_at: :desc).first
|
||||
if sample_event
|
||||
process_event(sample_event)
|
||||
else
|
||||
# No events exist for this network range, return empty results
|
||||
# Network-based policies need real events to trigger rule creation
|
||||
{ matching_policies: [], generated_rules: [] }
|
||||
end
|
||||
end
|
||||
|
||||
matcher = new(network_range: network_range)
|
||||
# Evaluate an event against policies and mark its network range as evaluated
|
||||
# This is the main entry point for inline policy evaluation
|
||||
def self.evaluate_and_mark!(event)
|
||||
return { matching_policies: [], generated_rules: [] } unless event
|
||||
|
||||
matcher = new(event: event)
|
||||
result = matcher.match_and_generate_rules
|
||||
|
||||
# Mark this network range as evaluated
|
||||
network_range.update_column(:policies_evaluated_at, Time.current)
|
||||
# Mark the event's network range as evaluated
|
||||
if event.network_range
|
||||
event.network_range.update_column(:policies_evaluated_at, Time.current)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# Legacy method for backward compatibility
|
||||
def self.evaluate_and_mark_network_range!(network_range)
|
||||
return { matching_policies: [], generated_rules: [] } unless network_range
|
||||
|
||||
# Find the most recent event for this network range
|
||||
sample_event = network_range.events.order(created_at: :desc).first
|
||||
if sample_event
|
||||
evaluate_and_mark!(sample_event)
|
||||
else
|
||||
# No events exist, use the old network-range based evaluation
|
||||
process_network_range(network_range)
|
||||
network_range.update_column(:policies_evaluated_at, Time.current)
|
||||
{ matching_policies: [], generated_rules: [] }
|
||||
end
|
||||
end
|
||||
|
||||
def self.batch_process_network_ranges(network_ranges)
|
||||
results = []
|
||||
|
||||
@@ -158,8 +194,19 @@ class WafPolicyMatcher
|
||||
potential_ranges.find_each do |network_range|
|
||||
matcher = new(network_range: network_range)
|
||||
if waf_policy.matches_network_range?(network_range)
|
||||
# Check for supernet rules before creating
|
||||
if network_range.supernet_rules.any?
|
||||
supernet = network_range.supernet_rules.first
|
||||
Rails.logger.info "Skipping rule for #{network_range.cidr} - covered by supernet rule ##{supernet.id}"
|
||||
next
|
||||
end
|
||||
|
||||
rule = waf_policy.create_rule_for_network_range(network_range)
|
||||
results << { network_range: network_range, generated_rule: rule } if rule
|
||||
if rule&.persisted?
|
||||
results << { network_range: network_range, generated_rule: rule }
|
||||
elsif rule
|
||||
Rails.logger.warn "Failed to create rule for #{network_range.cidr}: #{rule.errors.full_messages.join(', ')}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -180,7 +227,7 @@ class WafPolicyMatcher
|
||||
{
|
||||
policy_name: waf_policy.name,
|
||||
policy_type: waf_policy.policy_type,
|
||||
action: waf_policy.action,
|
||||
action: waf_policy.policy_action,
|
||||
rules_generated: rules.count,
|
||||
active_rules: rules.active.count,
|
||||
networks_protected: rules.joins(:network_range).count('distinct network_ranges.id'),
|
||||
|
||||
Reference in New Issue
Block a user