Updates
This commit is contained in:
@@ -5,9 +5,9 @@
|
||||
# Rules define actions to take for matching traffic conditions.
|
||||
# Network rules are associated with NetworkRange objects for rich context.
|
||||
class Rule < ApplicationRecord
|
||||
# Rule enums
|
||||
enum :waf_action, { allow: 0, deny: 1, rate_limit: 2, redirect: 3, log: 4, challenge: 5 }, scopes: false, prefix: true
|
||||
enum :waf_rule_type, { network: 0, rate_limit: 1, path_pattern: 2 }, scopes: false, prefix: true
|
||||
# Rule enums (prefix needed to avoid rate_limit collision)
|
||||
enum :waf_action, { allow: 0, deny: 1, rate_limit: 2, redirect: 3, log: 4, challenge: 5 }, prefix: :action
|
||||
enum :waf_rule_type, { network: 0, rate_limit: 1, path_pattern: 2 }, prefix: :type
|
||||
|
||||
# Legacy string constants for backward compatibility
|
||||
RULE_TYPES = %w[network rate_limit path_pattern].freeze
|
||||
@@ -20,25 +20,6 @@ class Rule < ApplicationRecord
|
||||
belongs_to :waf_policy, optional: true
|
||||
has_many :events, dependent: :nullify
|
||||
|
||||
# Backward compatibility accessors for transition period
|
||||
def action
|
||||
waf_action
|
||||
end
|
||||
|
||||
def action=(value)
|
||||
self.waf_action = value
|
||||
self[:action] = value # Also set the legacy column
|
||||
end
|
||||
|
||||
def rule_type
|
||||
waf_rule_type
|
||||
end
|
||||
|
||||
def rule_type=(value)
|
||||
self.waf_rule_type = value
|
||||
self[:rule_type] = value # Also set the legacy column
|
||||
end
|
||||
|
||||
# Validations
|
||||
validates :waf_rule_type, presence: true, inclusion: { in: waf_rule_types.keys }
|
||||
validates :waf_action, presence: true, inclusion: { in: waf_actions.keys }
|
||||
@@ -59,6 +40,7 @@ class Rule < ApplicationRecord
|
||||
validate :validate_metadata_by_action
|
||||
validate :network_range_required_for_network_rules
|
||||
validate :validate_network_consistency, if: :network_rule?
|
||||
validate :no_supernet_rule_exists, if: :should_check_supernet?
|
||||
|
||||
# Scopes
|
||||
scope :enabled, -> { where(enabled: true) }
|
||||
@@ -66,20 +48,18 @@ class Rule < ApplicationRecord
|
||||
scope :active, -> { enabled.where("expires_at IS NULL OR expires_at > ?", Time.current) }
|
||||
scope :expired, -> { where("expires_at IS NOT NULL AND expires_at <= ?", Time.current) }
|
||||
scope :by_type, ->(type) { where(waf_rule_type: type) }
|
||||
scope :network_rules, -> { network }
|
||||
scope :rate_limit_rules, -> { rate_limit }
|
||||
scope :path_pattern_rules, -> { path_pattern }
|
||||
scope :network_rules, -> { where(waf_rule_type: :network) }
|
||||
scope :rate_limit_rules, -> { where(waf_rule_type: :rate_limit) }
|
||||
scope :path_pattern_rules, -> { where(waf_rule_type: :path_pattern) }
|
||||
scope :by_source, ->(source) { where(source: source) }
|
||||
scope :surgical_blocks, -> { where(source: "manual:surgical_block") }
|
||||
scope :surgical_exceptions, -> { where(source: "manual:surgical_exception") }
|
||||
scope :policy_generated, -> { where(source: "policy") }
|
||||
scope :from_waf_policy, ->(waf_policy) { where(waf_policy: waf_policy) }
|
||||
|
||||
# Legacy scopes for backward compatibility
|
||||
scope :by_type_legacy, ->(type) { where(rule_type: type) }
|
||||
scope :network_rules_legacy, -> { where(rule_type: "network") }
|
||||
scope :rate_limit_rules_legacy, -> { where(rule_type: "rate_limit") }
|
||||
scope :path_pattern_rules_legacy, -> { where(rule_type: "path_pattern") }
|
||||
# Action scopes (manual to avoid enum collision with rate_limit)
|
||||
scope :deny, -> { where(waf_action: :deny) }
|
||||
scope :allow, -> { where(waf_action: :allow) }
|
||||
|
||||
# Sync queries
|
||||
scope :since, ->(timestamp) { where("updated_at >= ?", Time.at(timestamp)).order(:updated_at, :id) }
|
||||
@@ -89,19 +69,19 @@ class Rule < ApplicationRecord
|
||||
before_validation :set_defaults
|
||||
before_validation :parse_json_fields
|
||||
before_save :calculate_priority_for_network_rules
|
||||
before_save :sync_legacy_columns
|
||||
after_create :expire_redundant_child_rules, if: :should_expire_child_rules?
|
||||
|
||||
# Rule type checks
|
||||
def network_rule?
|
||||
waf_rule_type_network?
|
||||
type_network?
|
||||
end
|
||||
|
||||
def rate_limit_rule?
|
||||
waf_rule_type_rate_limit?
|
||||
type_rate_limit?
|
||||
end
|
||||
|
||||
def path_pattern_rule?
|
||||
waf_rule_type_path_pattern?
|
||||
type_path_pattern?
|
||||
end
|
||||
|
||||
# Network-specific methods
|
||||
@@ -143,11 +123,11 @@ class Rule < ApplicationRecord
|
||||
|
||||
# Action-specific methods
|
||||
def redirect_action?
|
||||
waf_action_redirect?
|
||||
action_redirect?
|
||||
end
|
||||
|
||||
def challenge_action?
|
||||
waf_action_challenge?
|
||||
action_challenge?
|
||||
end
|
||||
|
||||
# Redirect/challenge convenience methods
|
||||
@@ -509,14 +489,52 @@ class Rule < ApplicationRecord
|
||||
self.metadata ||= {}
|
||||
end
|
||||
|
||||
def sync_legacy_columns
|
||||
# Sync enum values to legacy string columns for backward compatibility
|
||||
if waf_action.present?
|
||||
self[:action] = waf_action
|
||||
end
|
||||
if waf_rule_type.present?
|
||||
self[:rule_type] = waf_rule_type
|
||||
# Supernet/subnet redundancy checking
|
||||
def should_check_supernet?
|
||||
network_rule? && network_range.present? && new_record?
|
||||
end
|
||||
|
||||
def no_supernet_rule_exists
|
||||
return unless network_range
|
||||
|
||||
supernet_rule = network_range.supernet_rules.first
|
||||
if supernet_rule
|
||||
errors.add(
|
||||
:base,
|
||||
"A supernet rule already covers this network. " \
|
||||
"Rule ##{supernet_rule.id} for #{supernet_rule.network_range.cidr} " \
|
||||
"(action: #{supernet_rule.waf_action}) makes this rule redundant."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
def should_expire_child_rules?
|
||||
network_rule? && network_range.present? && enabled?
|
||||
end
|
||||
|
||||
def expire_redundant_child_rules
|
||||
return unless network_range
|
||||
|
||||
child_rules = network_range.child_rules
|
||||
return if child_rules.empty?
|
||||
|
||||
expired_count = 0
|
||||
child_rules.find_each do |child_rule|
|
||||
# Disable the child rule and mark it as redundant
|
||||
child_rule.update!(
|
||||
enabled: false,
|
||||
metadata: child_rule.metadata_hash.merge(
|
||||
disabled_at: Time.current.iso8601,
|
||||
disabled_reason: "Redundant - covered by supernet rule ##{id} (#{network_range.cidr})",
|
||||
superseded_by_rule_id: id
|
||||
)
|
||||
)
|
||||
expired_count += 1
|
||||
end
|
||||
|
||||
if expired_count > 0
|
||||
Rails.logger.info "Rule ##{id}: Expired #{expired_count} redundant child rule(s) for #{network_range.cidr}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user