Path matching

This commit is contained in:
Dan Milne
2025-11-17 12:12:17 +11:00
parent 093ee71c9f
commit 830810305b
14 changed files with 721 additions and 45 deletions

View File

@@ -0,0 +1,85 @@
# frozen_string_literal: true
# PathRuleMatcher - Service to match Events against path_pattern Rules
#
# This service provides path pattern matching logic for evaluating whether
# an event matches a path_pattern rule. Used for hub-side testing and validation
# before agent deployment.
#
# Match Types:
# - exact: All segments must match exactly
# - prefix: Event path must start with rule segments
# - suffix: Event path must end with rule segments
# - contains: Rule segments must appear consecutively somewhere in event path
class PathRuleMatcher
def self.matches?(rule, event)
return false unless rule.path_pattern_rule?
return false if event.request_segment_ids.blank?
rule_segments = rule.path_segment_ids
event_segments = event.request_segment_ids
return false if rule_segments.blank?
case rule.path_match_type
when 'exact'
exact_match?(event_segments, rule_segments)
when 'prefix'
prefix_match?(event_segments, rule_segments)
when 'suffix'
suffix_match?(event_segments, rule_segments)
when 'contains'
contains_match?(event_segments, rule_segments)
else
false
end
end
# Find all path_pattern rules that match the given event
def self.matching_rules(event)
return [] if event.request_segment_ids.blank?
Rule.path_pattern_rules.active.select do |rule|
matches?(rule, event)
end
end
# Evaluate an event against path rules and return the first matching action
def self.evaluate(event)
matching_rule = matching_rules(event).first
matching_rule&.waf_action || 'allow'
end
private
# Exact match: all segments must match exactly
# Example: [1, 2, 3] matches [1, 2, 3] only
def self.exact_match?(event_segments, rule_segments)
event_segments == rule_segments
end
# Prefix match: event path must start with rule segments
# Example: rule [1, 2] matches events [1, 2], [1, 2, 3], [1, 2, 3, 4]
def self.prefix_match?(event_segments, rule_segments)
return false if event_segments.length < rule_segments.length
event_segments[0...rule_segments.length] == rule_segments
end
# Suffix match: event path must end with rule segments
# Example: rule [2, 3] matches events [2, 3], [1, 2, 3], [0, 1, 2, 3]
def self.suffix_match?(event_segments, rule_segments)
return false if event_segments.length < rule_segments.length
event_segments[-rule_segments.length..-1] == rule_segments
end
# Contains match: rule segments must appear consecutively somewhere in event path
# Example: rule [2, 3] matches [1, 2, 3, 4], [2, 3], [0, 2, 3, 5]
def self.contains_match?(event_segments, rule_segments)
return false if event_segments.length < rule_segments.length
# Check if rule_segments appear consecutively anywhere in event_segments
(0..event_segments.length - rule_segments.length).any? do |i|
event_segments[i, rule_segments.length] == rule_segments
end
end
end