diff --git a/lib/resty/waf/actions.lua b/lib/resty/waf/actions.lua index dd163c28..2c6ffb11 100644 --- a/lib/resty/waf/actions.lua +++ b/lib/resty/waf/actions.lua @@ -76,6 +76,11 @@ _M.nondisruptive_lookup = { --_LOG_"Overriding status from " .. waf._deny_status .. " to " .. status ctx.rule_status = status + end, + rule_remove_id = function(waf, rule) + --_LOG_"Runtime ignoring rule " .. rule + + waf._ignore_rule[rule] = true end } diff --git a/t/acceptance/ctl/01_rule_remove_id.t b/t/acceptance/ctl/01_rule_remove_id.t new file mode 100644 index 00000000..08c3819d --- /dev/null +++ b/t/acceptance/ctl/01_rule_remove_id.t @@ -0,0 +1,44 @@ +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +my $pwd = cwd(); + +our $HttpConfig = qq{ + lua_package_path "$pwd/lib/?.lua;$pwd/t/?.lua;;"; + lua_package_cpath "$pwd/lib/?.lua;;"; +}; + +repeat_each(3); +plan tests => repeat_each() * 4 * blocks(); + +no_shuffle(); +run_tests(); + +__DATA__ + +=== TEST 1: Ignore a rule at runtime +--- http_config eval: $::HttpConfig +--- config + location /t { + access_by_lua_block { + local lua_resty_waf = require "resty.waf" + local waf = lua_resty_waf:new() + + waf:set_option("debug", true) + waf:set_option("mode", "ACTIVE") + waf:set_option("add_ruleset", "10000_ctl") + + waf:exec() + } + + content_by_lua_block { ngx.exit(ngx.HTTP_OK) } + } +--- request +GET /t +--- error_code: 200 +--- error_log +Runtime ignoring rule 12346 +Ignoring rule 12346 +--- no_error_log +[error] + diff --git a/t/rules/10000_ctl.json b/t/rules/10000_ctl.json new file mode 100644 index 00000000..088cee57 --- /dev/null +++ b/t/rules/10000_ctl.json @@ -0,0 +1 @@ +{"access":[{"actions":{"disrupt":"IGNORE","nondisrupt":[{"action":"rule_remove_id","data":"12346"}]},"id":"12345","opts":{"nolog":1},"vars":[{"unconditional":1}]},{"actions":{"disrupt":"DENY"},"id":"12346","operator":"REFIND","pattern":"foo","vars":[{"parse":["values","1"],"type":"REQUEST_ARGS"}]}],"body_filter":[],"header_filter":[]} diff --git a/t/translate/15_translate_actions.t b/t/translate/15_translate_actions.t index 46edc6c1..9326f22f 100644 --- a/t/translate/15_translate_actions.t +++ b/t/translate/15_translate_actions.t @@ -1244,4 +1244,32 @@ warning_like 'warn when capture is used with non-rx operator' ; +$translation = {}; +translate_actions( + { + actions => [ + { + action => 'ctl', + value => 'ruleRemoveById=12345', + } + ] + }, + $translation, + undef +); +is_deeply( + $translation, + { + actions => { + nondisrupt => [ + { + action => 'rule_remove_id', + data => 12345 + } + ] + }, + }, + 'translate ruleRemoveById' +); + done_testing; diff --git a/t/unit/actions/nondisruptive/07_rule_remove_id.t b/t/unit/actions/nondisruptive/07_rule_remove_id.t new file mode 100644 index 00000000..1afde424 --- /dev/null +++ b/t/unit/actions/nondisruptive/07_rule_remove_id.t @@ -0,0 +1,77 @@ +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +my $pwd = cwd(); + +our $HttpConfig = qq{ + lua_package_path "$pwd/lib/?.lua;;"; + lua_package_cpath "$pwd/lib/?.lua;;"; +}; + +repeat_each(3); +plan tests => repeat_each() * 4 * blocks(); + +no_shuffle(); +run_tests(); + +__DATA__ + +=== TEST 1: rule_remove_id adds to waf._ignore_rule +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua_block { + local actions = require "resty.waf.actions" + + local waf = { _debug = true, _debug_log_level = ngx.INFO, _ignore_rule = {} } + + actions.nondisruptive_lookup["rule_remove_id"]( + waf, + 12345 + ) + + for k, v in pairs(waf._ignore_rule) do + ngx.say(k) + end + } + } +--- request +GET /t +--- error_code: 200 +--- response_body +12345 +--- error_log +Runtime ignoring rule 12345 +--- no_error_log +[error] + +=== TEST 2: rule_remove_id adds to waf._ignore_rule with config-time ignore +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua_block { + local actions = require "resty.waf.actions" + + local waf = { _debug = true, _debug_log_level = ngx.INFO, _ignore_rule = { [12344] = true } } + + actions.nondisruptive_lookup["rule_remove_id"]( + waf, + 12345 + ) + + for k, v in pairs(waf._ignore_rule) do + ngx.say(k) + end + } + } +--- request +GET /t +--- error_code: 200 +--- response_body +12345 +12344 +--- error_log +Runtime ignoring rule 12345 +--- no_error_log +[error] + diff --git a/tools/Modsec2LRW.pm b/tools/Modsec2LRW.pm index 90a3707d..35a59a2b 100644 --- a/tools/Modsec2LRW.pm +++ b/tools/Modsec2LRW.pm @@ -149,6 +149,17 @@ my $phase_lookup = { 5 => 'log', }; +my $ctl_lookup = { + ruleRemoveById => sub { + my ($value, $translation) = @_; + + push @{$translation->{actions}->{nondisrupt}}, { + action => 'rule_remove_id', + data => $value, + }; + }, +}; + my $op_sep_lookup = { PM => '\s+', CIDR_MATCH => ',', @@ -795,6 +806,11 @@ sub translate_actions { $translation->{operator} eq 'REFIND' ? $translation->{operator} = 'REGEX' : warn 'capture set when translated operator was not REFIND'; + + } elsif ($key eq 'ctl') { + my ($opt, $data) = split /=/, $value; + + $ctl_lookup->{$opt}($data, $translation); } elsif ($key eq 'expirevar') { my ($var, $time) = split /=/, $value; my ($collection, $element) = split /\./, $var;