Fix geo rule re-enablement bug

When rules expire and are disabled by ExpiredRulesCleanupJob, the system
was unable to re-enable them due to unique index constraints. This caused
geo-based blocking to stop working in production.

Implemented find-or-update-or-create pattern in WafPolicy#create_rule_for_network_range:
- Re-enables disabled rules and sets new expiration (7 days)
- Extends expiration for enabled rules
- Creates new rules with 7-day TTL
- Handles race conditions gracefully

Added test coverage for all three scenarios.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Dan Milne
2026-01-18 23:06:25 +11:00
parent dad7874352
commit e2b6db2f48
2 changed files with 153 additions and 42 deletions

View File

@@ -442,7 +442,7 @@ class WafPolicyTest < ActiveSupport::TestCase
assert_equal 0, @policy.generated_rules_count
# Create some rules
network_range = NetworkRange.create!(ip_range: "192.168.1.0/24")
network_range = NetworkRange.create!(cidr: "192.168.1.0/24", country: "BR")
@policy.create_rule_for_network_range(network_range)
assert_equal 1, @policy.generated_rules_count
@@ -461,6 +461,75 @@ class WafPolicyTest < ActiveSupport::TestCase
assert_equal 2, stats[:targets_count]
end
# Rule creation and re-enablement tests
test "create_rule_for_network_range re-enables disabled rule" do
@policy.save!
# Create a network range that matches the policy
network_range = NetworkRange.create!(
cidr: "192.168.1.0/24",
country: "BR"
)
# Create a rule via policy
rule = @policy.create_rule_for_network_range(network_range)
assert rule.enabled?, "Rule should be enabled initially"
assert rule.expires_at.present?, "Rule should have expiration"
# Disable it (simulating expiration cleanup)
rule.update!(enabled: false)
assert_not rule.enabled?, "Rule should be disabled"
# Try to create again - should re-enable
result = @policy.create_rule_for_network_range(network_range)
assert_equal rule.id, result.id, "Should return same rule"
assert result.reload.enabled?, "Rule should be re-enabled"
assert result.expires_at.present?, "Should have new expiration"
assert result.expires_at > Time.current, "Expiration should be in the future"
end
test "create_rule_for_network_range extends enabled rule expiration" do
@policy.save!
# Create a network range that matches the policy
network_range = NetworkRange.create!(
cidr: "192.168.1.0/24",
country: "BR"
)
# Create a rule with expiration
rule = @policy.create_rule_for_network_range(network_range)
original_expires_at = rule.expires_at
travel 3.days do
# Try to create again - should extend expiration
result = @policy.create_rule_for_network_range(network_range)
assert_equal rule.id, result.id, "Should return same rule"
assert result.reload.expires_at > original_expires_at, "Expiration should be extended"
assert_in_delta 7.days.from_now, result.expires_at, 5.seconds, "Expiration should be ~7 days from now"
end
end
test "create_rule_for_network_range creates new rule when none exists" do
@policy.save!
# Create a network range that matches the policy
network_range = NetworkRange.create!(
cidr: "192.168.1.0/24",
country: "BR"
)
rule = @policy.create_rule_for_network_range(network_range)
assert rule.persisted?, "Rule should be persisted"
assert rule.enabled?, "Rule should be enabled"
assert_equal network_range, rule.network_range
assert rule.expires_at.present?, "New rule should have expiration"
assert_in_delta 7.days.from_now, rule.expires_at, 5.seconds, "Expiration should be ~7 days from now"
end
# String representations
test "to_s returns name" do
assert_equal @policy.name, @policy.to_s