|
|
|
@@ -152,10 +152,14 @@ validate :targets_must_be_array
|
|
|
|
def create_rule_for_network_range(network_range)
|
|
|
|
def create_rule_for_network_range(network_range)
|
|
|
|
return nil unless matches_network_range?(network_range)
|
|
|
|
return nil unless matches_network_range?(network_range)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# For country policies, expand to largest matching ancestor
|
|
|
|
|
|
|
|
# This consolidates /24 rules into /16, /8, etc. when possible
|
|
|
|
|
|
|
|
expanded_range = find_largest_matching_ancestor(network_range)
|
|
|
|
|
|
|
|
|
|
|
|
# Check for existing supernet rules before attempting to create
|
|
|
|
# Check for existing supernet rules before attempting to create
|
|
|
|
if network_range.supernet_rules.any?
|
|
|
|
if expanded_range.supernet_rules.any?
|
|
|
|
supernet = network_range.supernet_rules.first
|
|
|
|
supernet = expanded_range.supernet_rules.first
|
|
|
|
Rails.logger.debug "Skipping rule creation for #{network_range.cidr} - covered by supernet rule ##{supernet.id} (#{supernet.network_range.cidr})"
|
|
|
|
Rails.logger.debug "Skipping rule creation for #{expanded_range.cidr} - covered by supernet rule ##{supernet.id} (#{supernet.network_range.cidr})"
|
|
|
|
return nil
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
@@ -164,21 +168,21 @@ validate :targets_must_be_array
|
|
|
|
rule = Rule.create!(
|
|
|
|
rule = Rule.create!(
|
|
|
|
waf_rule_type: 'network',
|
|
|
|
waf_rule_type: 'network',
|
|
|
|
waf_action: policy_action.to_sym,
|
|
|
|
waf_action: policy_action.to_sym,
|
|
|
|
network_range: network_range,
|
|
|
|
network_range: expanded_range,
|
|
|
|
waf_policy: self,
|
|
|
|
waf_policy: self,
|
|
|
|
user: user,
|
|
|
|
user: user,
|
|
|
|
source: "policy",
|
|
|
|
source: "policy",
|
|
|
|
metadata: build_rule_metadata(network_range),
|
|
|
|
metadata: build_rule_metadata(expanded_range),
|
|
|
|
priority: network_range.prefix_length
|
|
|
|
priority: expanded_range.prefix_length
|
|
|
|
)
|
|
|
|
)
|
|
|
|
rescue ActiveRecord::RecordNotUnique
|
|
|
|
rescue ActiveRecord::RecordNotUnique
|
|
|
|
# Rule already exists (created by another job or earlier in this job)
|
|
|
|
# Rule already exists (created by another job or earlier in this job)
|
|
|
|
# Find and return the existing rule
|
|
|
|
# Find and return the existing rule
|
|
|
|
Rails.logger.debug "Rule already exists for #{network_range.cidr} with policy #{name}"
|
|
|
|
Rails.logger.debug "Rule already exists for #{expanded_range.cidr} with policy #{name}"
|
|
|
|
return Rule.find_by(
|
|
|
|
return Rule.find_by(
|
|
|
|
waf_rule_type: 'network',
|
|
|
|
waf_rule_type: 'network',
|
|
|
|
waf_action: policy_action,
|
|
|
|
waf_action: policy_action,
|
|
|
|
network_range: network_range,
|
|
|
|
network_range: expanded_range,
|
|
|
|
waf_policy: self,
|
|
|
|
waf_policy: self,
|
|
|
|
source: "policy"
|
|
|
|
source: "policy"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
@@ -505,6 +509,64 @@ validate :targets_must_be_array
|
|
|
|
base_metadata.merge!(additional_data || {})
|
|
|
|
base_metadata.merge!(additional_data || {})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# For country policies, find the largest ancestor network that matches the same country
|
|
|
|
|
|
|
|
# This allows consolidating /24 rules into /16, /8, etc. when the entire block is in the same country
|
|
|
|
|
|
|
|
def find_largest_matching_ancestor(network_range)
|
|
|
|
|
|
|
|
return network_range unless country_policy?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
country = network_range.country || network_range.inherited_intelligence[:country]
|
|
|
|
|
|
|
|
return network_range unless country
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Check if this network has IPAPI data with a larger CIDR (asn.route or ipapi_returned_cidr)
|
|
|
|
|
|
|
|
ipapi_cidr = network_range.network_data&.dig('ipapi', 'asn', 'route') ||
|
|
|
|
|
|
|
|
network_range.network_data&.dig('ipapi_returned_cidr')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ipapi_cidr && ipapi_cidr != network_range.cidr
|
|
|
|
|
|
|
|
# IPAPI returned a larger network - use it if it exists
|
|
|
|
|
|
|
|
existing = NetworkRange.find_by(network: ipapi_cidr)
|
|
|
|
|
|
|
|
if existing
|
|
|
|
|
|
|
|
existing_country = existing.country || existing.inherited_intelligence[:country]
|
|
|
|
|
|
|
|
if existing_country == country
|
|
|
|
|
|
|
|
Rails.logger.debug "Using IPAPI CIDR #{existing.cidr} instead of #{network_range.cidr} (both #{country})"
|
|
|
|
|
|
|
|
return existing
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
# Create the IPAPI network range if it doesn't exist
|
|
|
|
|
|
|
|
begin
|
|
|
|
|
|
|
|
ipapi_network = NetworkRange.create!(
|
|
|
|
|
|
|
|
network: ipapi_cidr,
|
|
|
|
|
|
|
|
source: 'inherited',
|
|
|
|
|
|
|
|
country: country
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
Rails.logger.info "Created IPAPI network range #{ipapi_cidr} for country #{country}"
|
|
|
|
|
|
|
|
return ipapi_network
|
|
|
|
|
|
|
|
rescue ActiveRecord::RecordNotUnique
|
|
|
|
|
|
|
|
# Race condition - another process created it
|
|
|
|
|
|
|
|
existing = NetworkRange.find_by(network: ipapi_cidr)
|
|
|
|
|
|
|
|
return existing || network_range
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Fallback: Look for existing parent networks with IPAPI data and same country
|
|
|
|
|
|
|
|
# Query for all networks that contain this network and have IPAPI data
|
|
|
|
|
|
|
|
parent_with_ipapi = NetworkRange.where(
|
|
|
|
|
|
|
|
"?::inet << network", network_range.cidr
|
|
|
|
|
|
|
|
).where(
|
|
|
|
|
|
|
|
"network_data ? 'ipapi' AND " \
|
|
|
|
|
|
|
|
"network_data -> 'ipapi' ->> 'location' ->> 'country_code' = ?",
|
|
|
|
|
|
|
|
country
|
|
|
|
|
|
|
|
).order("masklen(network) DESC").first
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if parent_with_ipapi
|
|
|
|
|
|
|
|
Rails.logger.debug "Found existing IPAPI parent #{parent_with_ipapi.cidr} for #{network_range.cidr} (both #{country})"
|
|
|
|
|
|
|
|
return parent_with_ipapi
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# No expansion possible - use original network
|
|
|
|
|
|
|
|
network_range
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def matched_field(network_range)
|
|
|
|
def matched_field(network_range)
|
|
|
|
case policy_type
|
|
|
|
case policy_type
|
|
|
|
when 'country'
|
|
|
|
when 'country'
|
|
|
|
|