├── .gitmodules ├── .travis.yml ├── CHANGELOG ├── COPYING ├── Makefile ├── README.md ├── dist.ini ├── lib └── resty │ ├── waf.lua │ └── waf │ ├── actions.lua │ ├── base.lua │ ├── collections.lua │ ├── load_ac.lua │ ├── log.lua │ ├── operators.lua │ ├── options.lua │ ├── phase.lua │ ├── random.lua │ ├── request.lua │ ├── rule_calc.lua │ ├── storage.lua │ ├── storage │ ├── dict.lua │ ├── memcached.lua │ └── redis.lua │ ├── transform.lua │ ├── translate.lua │ └── util.lua ├── lua-resty-waf-0.11.1-1.rockspec ├── rules ├── 11000_whitelist.json ├── 20000_http_violation.json ├── 21000_http_anomaly.json ├── 35000_user_agent.json ├── 40000_generic_attack.json ├── 41000_sqli.json ├── 42000_xss.json ├── 90000_custom.json └── 99000_scoring.json ├── src ├── Makefile └── decode.c ├── t ├── TestDNS.pm ├── acceptance │ ├── actions │ │ ├── 01_accept.t │ │ ├── 02_deny.t │ │ └── 03_status.t │ ├── ctl │ │ ├── 01_rule_remove_id.t │ │ ├── 02_remove_rule_by_meta.t │ │ └── 03_mode.t │ ├── hooks │ │ └── 01_action_hook.t │ ├── http │ │ ├── 01_protocol_violations.t │ │ └── 02_protocol_anomalies.t │ ├── logging │ │ ├── 01_configurables.t │ │ ├── 02_transaction_id.t │ │ └── 03_log_event.t │ ├── multipart │ │ └── 01_multipart.t │ ├── opts │ │ ├── 01_custom_phase.t │ │ └── 02_custom_var.t │ ├── policy │ │ ├── 01_passive.t │ │ ├── 02_images.t │ │ ├── 03_documents.t │ │ ├── 04_html.t │ │ └── 05_media.t │ ├── run │ │ ├── 01_sanity.t │ │ ├── 02_phases.t │ │ ├── 03_rule_strings.t │ │ ├── 03_rulesets.t │ │ ├── 04_unknown_content_types.t │ │ ├── 05_resty_core.t │ │ ├── 06_status.t │ │ ├── 07_merge.t │ │ ├── 08_score_threshold.t │ │ ├── 09_debug_logging.t │ │ ├── 10_load_secrules.t │ │ └── 11_sieve_rule.t │ ├── storage │ │ ├── 01_set_var.t │ │ ├── 02_expire_var.t │ │ ├── 03_delete_var.t │ │ ├── dict │ │ │ ├── 01_initialize.t │ │ │ ├── 02_persist.t │ │ │ └── 03_expire.t │ │ ├── memcached │ │ │ ├── 01_connect.t │ │ │ ├── 02_initialize.t │ │ │ ├── 03_persist.t │ │ │ └── 04_expire.t │ │ └── redis │ │ │ ├── 01_connect.t │ │ │ ├── 02_initialize.t │ │ │ ├── 03_persist.t │ │ │ └── 04_expire.t │ ├── user_agent │ │ └── 01_known_baddies.t │ └── xss │ │ ├── 01_reflected.t │ │ └── 02_stored.t ├── data │ └── ips.txt ├── regression │ ├── 157 │ │ └── 157.t │ └── 242 │ │ └── 242.t ├── rules │ ├── 10000_ctl.json │ ├── 10000_ctl_meta.json │ ├── body_storage.json │ ├── extra.json │ ├── extra_broken.json │ ├── log.json │ ├── mode.rules │ ├── multipart.rules │ ├── sieve.rules │ ├── test.rules │ └── test_errs.rules ├── translate │ ├── 01_sanity.t │ ├── 02_valid_line.t │ ├── 03_clean_input.t │ ├── 04_tokenize.t │ ├── 05_parse_tokens.t │ ├── 06_parse_vars.t │ ├── 07_parse_operator.t │ ├── 08_parse_actions.t │ ├── 09_strip_encap_quotes.t │ ├── 10_build_chains.t │ ├── 11_translate_chains.t │ ├── 12_translate_chain.t │ ├── 13_translate_vars.t │ ├── 14_translate_operator.t │ ├── 15_translate_actions.t │ ├── 16_figure_phase.t │ └── 17_translate_macro.t ├── translation │ ├── 01_sanity.lua │ ├── 02_valid_line.lua │ ├── 03_clean_input.lua │ ├── 04_tokenize.lua │ ├── 05_parse_tokens.lua │ ├── 06_parse_vars.lua │ ├── 07_parse_operator.lua │ ├── 08_parse_actions.lua │ ├── 09_strip_encap_quotes.lua │ ├── 10_build_chains.lua │ ├── 11_translate_chains.lua │ ├── 12_translate_chain.lua │ ├── 13_translate_vars.lua │ ├── 14_translate_operator.lua │ ├── 15_translate_actions.lua │ ├── 16_figure_phase.lua │ ├── 17_translate_macro.lua │ └── 18_translate.lua └── unit │ ├── actions │ ├── disruptive │ │ ├── 01_accept.t │ │ ├── 02_chain.t │ │ ├── 03_score.t │ │ ├── 04_deny.t │ │ ├── 05_ignore.t │ │ ├── 06_alter_actions.t │ │ └── 07_drop.t │ └── nondisruptive │ │ ├── 01_deletevar.t │ │ ├── 02_expirevar.t │ │ ├── 03_initcol.t │ │ ├── 04_setvar.t │ │ ├── 05_sleep.t │ │ ├── 06_status.t │ │ ├── 07_rule_remove_id.t │ │ ├── 08_rule_remove_by_meta.t │ │ └── 09_mode.t │ ├── collections │ ├── 01_remote_addr.t │ ├── 02_http_version.t │ ├── 03_method.t │ ├── 04_uri.t │ ├── 05_uri_args.t │ ├── 06_request_headers.t │ ├── 07_query_string.t │ ├── 08_request_line.t │ ├── 09_cookies.t │ ├── 10_request_body.t │ ├── 11_request_args.t │ ├── 12_request_basename.t │ ├── 13_time.t │ ├── 14_score_threshold.t │ ├── 15_ngx_var.t │ ├── 16_protocol.t │ ├── 17_response_headers.t │ ├── 18_request_uri.t │ ├── 19_status.t │ ├── 20_request_body.t │ ├── 21_response_body.t │ ├── 22_request_uri_raw.t │ ├── 23_multipart.t │ └── 24_args_size.t │ ├── event_log │ ├── 01_write_entry.t │ ├── 02_udp_socket.t │ ├── 03_tcp_socket.t │ └── 04_file.t │ ├── fw │ └── 01_sanity.t │ ├── log │ ├── 01_fatal_fail.t │ ├── 02_warn.t │ └── 03_deprecate.t │ ├── operators │ ├── 01_equals.t │ ├── 02_greater.t │ ├── 02_greater_equals.t │ ├── 02_less.t │ ├── 02_less_equals.t │ ├── 03_refind.t │ ├── 03_regex.t │ ├── 03_str_find.t │ ├── 04_ac_dict.t │ ├── 05_cidr_match.t │ ├── 06_exists.t │ ├── 07_contains.t │ ├── 08_rbl_lookup.t │ ├── 09_detect_sqli.t │ ├── 09_detect_xss.t │ ├── 10_str_match.t │ └── 11_verify_cc.t │ ├── opts │ ├── 01_custom_phase.t │ ├── 02_custom_collections.t │ └── 03_custom_var.t │ ├── phase │ └── 01_phase.t │ ├── random │ └── 01_random.t │ ├── rule_calc │ ├── 01_single_offset.t │ ├── 02_chain.t │ ├── 03_skip.t │ ├── 04_skip_after.t │ ├── 05_collection_key.t │ └── 06_meta_exceptions.t │ ├── storage │ ├── 01_storage_zone.t │ └── 02_storage_backend.t │ ├── transform │ ├── 01_base64.t │ ├── 02_whitespace.t │ ├── 03_comments.t │ ├── 04_string.t │ ├── 05_misc.t │ ├── 06_hex.t │ ├── 07_trim.t │ ├── 08_hash.t │ ├── 09_cmd_line.t │ └── 10_decode.t │ └── util │ ├── 01_table_keys.t │ ├── 02_table_values.t │ ├── 03_table_has_key.t │ ├── 04_table_has_value.t │ ├── 05_parse_dynamic_value.t │ ├── 06_hex.t │ ├── 07_build_rbl_query.t │ ├── 08_parse_ruleset.t │ ├── 09_load_ruleset_file.t │ ├── 10_parse_collection.t │ ├── 11_sieve_collection.t │ ├── 12_table_append.t │ └── 13_rule_exception.t └── tools ├── Modsec2LRW.pm ├── debug-macro.sh ├── lua-releng └── modsec2lua-resty-waf.pl /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libinjection"] 2 | path = libinjection 3 | url = https://github.com/p0pr0ck5/libinjection 4 | [submodule "lua-aho-corasick"] 5 | path = lua-aho-corasick 6 | url = https://github.com/p0pr0ck5/lua-aho-corasick 7 | [submodule "lua-resty-htmlentities"] 8 | path = lua-resty-htmlentities 9 | url = https://github.com/detailyang/lua-resty-htmlentities 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: perl 2 | cache: 3 | - apt 4 | - ccache 5 | notifications: 6 | irc: "chat.freenode.net#lua-resty-waf" 7 | webhooks: https://www.cryptobells.com/endpoint 8 | env: 9 | global: 10 | - V_OPENRESTY=1.11.2.2 11 | - DATE=20170216 12 | matrix: 13 | - TEST=unit RUNNER='perl' 14 | - TEST=acceptance RUNNER='perl' 15 | - TEST=regression RUNNER='perl' 16 | - TEST=translate RUNNER='perl' 17 | - TEST=translation RUNNER='rebusted -o=TAP' 18 | services: 19 | - memcached 20 | - redis 21 | install: 22 | - cpanm -v --notest Test::Nginx Test::More Exporter::Declare Test::Exception Test::MockModule; 23 | before_script: 24 | - export PATH=/usr/local/openresty/bin/:$PATH 25 | - sudo apt-get update -q 26 | - sudo apt-get install lua5.1 liblua5.1-0-dev luarocks libpcre3-dev -y 27 | - sudo wget -O /usr/local/bin/rebusted https://raw.githubusercontent.com/thibaultcha/lua-resty-busted/master/bin/busted 28 | - sudo chmod +x /usr/local/bin/rebusted 29 | - wget https://s3.amazonaws.com/p0pr0ck5-data/openresty-$V_OPENRESTY-$DATE.tar.bz2 30 | - sudo tar -jxvf openresty-$V_OPENRESTY-$DATE.tar.bz2 -C /usr/local/ 31 | - make 32 | - sudo make install-link install-rocks 33 | - sudo luarocks install lrexlib-pcre 2.7.2-1 PCRE_LIBDIR=/lib/x86_64-linux-gnu 34 | script: 35 | - PATH=/usr/local/openresty/nginx/sbin:$PATH prove -v --exec "$RUNNER" -r t/$TEST/* 36 | - ./tools/lua-releng -L 37 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = lua-resty-waf 2 | abstract = High-performance WAF built on the OpenResty stack 3 | author = Robert Paprocki (p0pr0ck5) 4 | is_original = yes 5 | license = gpl3 6 | lib_dir = lib 7 | repo_link = https://github.com/p0pr0ck5/lua-resty-waf 8 | main_module = lib/resty/waf.lua 9 | requires = openresty > 1.9.7.3, hamishforbes/lua-resty-iputils, p0pr0ck5/lua-resty-cookie, p0pr0ck5/lua-ffi-libinjection, p0pr0ck5/lua-resty-logger-socket 10 | -------------------------------------------------------------------------------- /lib/resty/waf/base.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | _M.version = "0.11.1" 4 | 5 | return _M 6 | -------------------------------------------------------------------------------- /lib/resty/waf/load_ac.lua: -------------------------------------------------------------------------------- 1 | -- Helper wrappring script for loading shared object libac.so (FFI interface) 2 | -- from package.cpath instead of LD_LIBRARTY_PATH. 3 | -- 4 | 5 | local ffi = require 'ffi' 6 | ffi.cdef[[ 7 | void* ac_create(const char** str_v, unsigned int* strlen_v, 8 | unsigned int v_len); 9 | int ac_match2(void*, const char *str, int len); 10 | void ac_free(void*); 11 | ]] 12 | 13 | local _M = {} 14 | 15 | local string_gmatch = string.gmatch 16 | local string_match = string.match 17 | 18 | local ac_lib = nil 19 | local ac_create = nil 20 | local ac_match = nil 21 | local ac_free = nil 22 | 23 | --[[ Find shared object file package.cpath, obviating the need of setting 24 | LD_LIBRARY_PATH 25 | ]] 26 | local function find_shared_obj(cpath, so_name) 27 | for k, v in string_gmatch(cpath, "[^;]+") do 28 | local so_path = string_match(k, "(.*/)") 29 | if so_path then 30 | -- "so_path" could be nil. e.g, the dir path component is "." 31 | so_path = so_path .. so_name 32 | 33 | -- Don't get me wrong, the only way to know if a file exist is 34 | -- trying to open it. 35 | local f = io.open(so_path) 36 | if f ~= nil then 37 | io.close(f) 38 | return so_path 39 | end 40 | end 41 | end 42 | end 43 | 44 | function _M.load_ac_lib() 45 | if ac_lib ~= nil then 46 | return ac_lib 47 | else 48 | local so_path = find_shared_obj(package.cpath, "libac.so") 49 | if so_path ~= nil then 50 | ac_lib = ffi.load(so_path) 51 | ac_create = ac_lib.ac_create 52 | ac_match = ac_lib.ac_match2 53 | ac_free = ac_lib.ac_free 54 | return ac_lib 55 | end 56 | end 57 | end 58 | 59 | -- Create an Aho-Corasick instance, and return the instance if it was 60 | -- successful. 61 | function _M.create_ac(dict) 62 | local strnum = #dict 63 | if ac_lib == nil then 64 | _M.load_ac_lib() 65 | end 66 | 67 | local str_v = ffi.new("const char *[?]", strnum) 68 | local strlen_v = ffi.new("unsigned int [?]", strnum) 69 | 70 | for i = 1, strnum do 71 | local s = dict[i] 72 | str_v[i - 1] = s 73 | strlen_v[i - 1] = #s 74 | end 75 | 76 | local ac = ac_create(str_v, strlen_v, strnum); 77 | if ac ~= nil then 78 | return ffi.gc(ac, ac_free) 79 | end 80 | end 81 | 82 | -- Return nil if str doesn't match the dictionary, else return non-nil. 83 | function _M.match(ac, str) 84 | local r = ac_match(ac, str, #str); 85 | if r >= 0 then 86 | return r 87 | end 88 | end 89 | 90 | return _M 91 | -------------------------------------------------------------------------------- /lib/resty/waf/log.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | local base = require "resty.waf.base" 4 | local cjson = require "cjson" 5 | local socket_logger = require "resty.logger.socket" 6 | 7 | _M.version = base.version 8 | 9 | local function split(source, delimiter) 10 | local elements = {} 11 | local pattern = '([^'..delimiter..']+)' 12 | string.gsub(source, pattern, function(value) 13 | elements[#elements + 1] = value 14 | end) 15 | return elements 16 | end 17 | 18 | -- warn logger 19 | function _M.warn(waf, msg) 20 | ngx.log(ngx.WARN, '[', waf.transaction_id, '] ', msg) 21 | end 22 | 23 | -- deprecation logger 24 | function _M.deprecate(waf, msg, ver) 25 | _M.warn(waf, 'DEPRECATED: ' .. msg) 26 | 27 | if not ver then return end 28 | 29 | local ver_tab = split(ver, "%.") 30 | local my_ver = split(base.version, "%.") 31 | 32 | for i = 1, #ver_tab do 33 | local m = tonumber(ver_tab[i]) or 0 34 | local n = tonumber(my_ver[i]) or 0 35 | 36 | if n > m then 37 | _M.fatal_fail("fatal deprecation version passed", 1) 38 | end 39 | end 40 | end 41 | 42 | -- fatal failure logger 43 | function _M.fatal_fail(msg, level) 44 | level = tonumber(level) or 0 45 | ngx.log(ngx.ERR, error(msg, level + 2)) 46 | ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) 47 | end 48 | 49 | -- event log writer lookup table 50 | _M.write_log_events = { 51 | error = function(waf, t) 52 | ngx.log(waf._event_log_level, cjson.encode(t)) 53 | end, 54 | file = function(waf, t) 55 | if not waf._event_log_target_path then 56 | _M.fatal_fail("Event log target path is undefined in file logger") 57 | end 58 | 59 | local f = io.open(waf._event_log_target_path, 'a') 60 | 61 | if not f then 62 | _M.warn(waf, "Could not open " .. waf._event_log_target_path) 63 | return 64 | end 65 | 66 | f:write(cjson.encode(t), "\n") 67 | f:close() 68 | end, 69 | socket = function(waf, t) 70 | if not socket_logger.initted() then 71 | socket_logger.init({ 72 | host = waf._event_log_target_host, 73 | port = waf._event_log_target_port, 74 | sock_type = waf._event_log_socket_proto, 75 | ssl = waf._event_log_ssl, 76 | ssl_verify = waf._event_log_ssl_verify, 77 | sni_host = waf._event_log_ssl_sni_host, 78 | flush_limit = waf._event_log_buffer_size, 79 | periodic_flush = waf._event_log_periodic_flush 80 | }) 81 | end 82 | 83 | socket_logger.log(cjson.encode(t) .. "\n") 84 | end 85 | } 86 | 87 | return _M 88 | -------------------------------------------------------------------------------- /lib/resty/waf/options.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | local actions = require "resty.waf.actions" 4 | local base = require "resty.waf.base" 5 | local logger = require "resty.waf.log" 6 | local util = require "resty.waf.util" 7 | 8 | _M.version = base.version 9 | 10 | _M.lookup = { 11 | ignore_ruleset = function(waf, value) 12 | waf._ignore_ruleset[#waf._ignore_ruleset + 1] = value 13 | waf.need_merge = true 14 | end, 15 | add_ruleset = function(waf, value) 16 | waf._add_ruleset[#waf._add_ruleset + 1] = value 17 | waf.need_merge = true 18 | end, 19 | add_ruleset_string = function(waf, value, ruleset) 20 | waf._add_ruleset_string[value] = ruleset 21 | waf.need_merge = true 22 | end, 23 | ignore_rule = function(waf, value) 24 | waf._ignore_rule[value] = true 25 | end, 26 | disable_pcre_optimization = function(waf, value) 27 | logger.deprecate(waf, 'PCRE flags will force JIT/cache', '0.12') 28 | if value == true then 29 | waf._pcre_flags = 'i' 30 | end 31 | end, 32 | storage_zone = function(waf, value) 33 | if not ngx.shared[value] then 34 | logger.fatal_fail("Attempted to set lua-resty-waf storage zone as " .. tostring(value) .. ", but that lua_shared_dict does not exist") 35 | end 36 | waf._storage_zone = value 37 | end, 38 | allowed_content_types = function(waf, value) 39 | waf._allowed_content_types[value] = true 40 | end, 41 | res_body_mime_types = function(waf, value) 42 | waf._res_body_mime_types[value] = true 43 | end, 44 | event_log_ngx_vars = function(waf, value) 45 | waf._event_log_ngx_vars[value] = true 46 | end, 47 | nameservers = function(waf, value) 48 | waf._nameservers[#waf._nameservers + 1] = value 49 | end, 50 | hook_action = function(waf, value, hook) 51 | if not util.table_has_key(value, actions.disruptive_lookup) then 52 | logger.fatal_fail(value .. " is not a valid action to override") 53 | end 54 | 55 | if type(hook) ~= "function" then 56 | logger.fatal_fail("hook_action must be defined as a function") 57 | end 58 | 59 | waf._hook_actions[value] = hook 60 | end, 61 | } 62 | 63 | return _M 64 | -------------------------------------------------------------------------------- /lib/resty/waf/phase.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | local base = require "resty.waf.base" 4 | local util = require "resty.waf.util" 5 | 6 | _M.version = base.version 7 | 8 | _M.phases = { access = 1, header_filter = 2, body_filter = 3, log = 4 } 9 | 10 | function _M.is_valid_phase(phase) 11 | return util.table_has_key(phase, _M.phases) 12 | end 13 | 14 | return _M 15 | -------------------------------------------------------------------------------- /lib/resty/waf/random.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | local base = require "resty.waf.base" 4 | local random = require "resty.random" 5 | local string = require "resty.string" 6 | 7 | _M.version = base.version 8 | 9 | function _M.random_bytes(len) 10 | return string.to_hex(random.bytes(len)) 11 | end 12 | 13 | return _M 14 | -------------------------------------------------------------------------------- /lib/resty/waf/storage/dict.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | local base = require "resty.waf.base" 4 | local cjson = require "cjson" 5 | local logger = require "resty.waf.log" 6 | local storage = require "resty.waf.storage" 7 | 8 | _M.version = base.version 9 | 10 | _M.col_prefix = storage.col_prefix 11 | 12 | function _M.initialize(waf, storage, col) 13 | if not waf._storage_zone then 14 | logger.fatal_fail("No storage_zone configured for memory-based persistent storage") 15 | end 16 | 17 | local col_name = _M.col_prefix .. col 18 | 19 | local altered, serialized, shm 20 | shm = ngx.shared[waf._storage_zone] 21 | serialized = shm:get(col_name) 22 | altered = false 23 | 24 | if not serialized then 25 | --_LOG_"Initializing an empty collection for " .. col 26 | storage[col] = {} 27 | else 28 | local data = cjson.decode(serialized) 29 | 30 | -- because we're serializing out the contents of the collection 31 | -- we need to roll our own expire handling. lua_shared_dict's 32 | -- internal expiry can't act on individual collection elements 33 | for key in pairs(data) do 34 | if not key:find("__", 1, true) and data["__expire_" .. key] then 35 | --_LOG_"checking " .. key 36 | if data["__expire_" .. key] < ngx.now() then 37 | --_LOG_"Removing expired key: " .. key 38 | data["__expire_" .. key] = nil 39 | data[key] = nil 40 | altered = true 41 | end 42 | end 43 | end 44 | 45 | storage[col] = data 46 | end 47 | 48 | storage[col]["__altered"] = altered 49 | end 50 | 51 | function _M.persist(waf, col, data) 52 | if not waf._storage_zone then 53 | logger.fatal_fail("No storage_zone configured for memory-based persistent storage") 54 | end 55 | 56 | local shm = ngx.shared[waf._storage_zone] 57 | local serialized = cjson.encode(data) 58 | 59 | --_LOG_'Persisting value: ' .. tostring(serialized) 60 | 61 | local col_name = _M.col_prefix .. col 62 | 63 | local ok, err = shm:set(col_name, serialized) 64 | 65 | if not ok then 66 | logger.warn(waf, "Error adding key to persistent storage, increase the size of the lua_shared_dict " .. waf._storage_zone) 67 | end 68 | end 69 | 70 | 71 | return _M 72 | -------------------------------------------------------------------------------- /lua-resty-waf-0.11.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-waf" 2 | version = "0.11.1-1" 3 | source = { 4 | url = "gitrec+https://github.com/p0pr0ck5/lua-resty-waf", 5 | } 6 | description = { 7 | summary = "High-performance WAF built on the OpenResty stack", 8 | homepage = "https://github.com/p0pr0ck5/lua-resty-waf", 9 | license = "GNU GPLv3", 10 | maintainer = "Robert Paprocki " 11 | } 12 | dependencies = { 13 | "lua >= 5.1", 14 | "luarocks-fetch-gitrec", 15 | } 16 | build = { 17 | type = "make", 18 | install_target = "install-hard", 19 | } 20 | -------------------------------------------------------------------------------- /rules/99000_scoring.json: -------------------------------------------------------------------------------- 1 | { 2 | "access" : [ 3 | { 4 | "actions" : { 5 | "disrupt" : "DENY" 6 | }, 7 | "id" : 99001, 8 | "logdata" : "%{TX.anomaly_score}", 9 | "msg" : "Request score greater than score threshold", 10 | "operator" : "GREATER_EQ", 11 | "opts" : { 12 | "parsepattern" : true 13 | }, 14 | "pattern" : "%{SCORE_THRESHOLD}", 15 | "vars" : [ 16 | { 17 | "parse" : [ 18 | "specific", 19 | "anomaly_score" 20 | ], 21 | "storage" : 1, 22 | "type" : "TX" 23 | } 24 | ] 25 | } 26 | ], 27 | "body_filter" : [ 28 | { 29 | "actions" : {}, 30 | "id" : 99003, 31 | "logdata" : "%{TX.anomaly_score}", 32 | "msg" : "Request score greater than score threshold", 33 | "operator" : "GREATER_EQ", 34 | "opts" : { 35 | "parsepattern" : true 36 | }, 37 | "pattern" : "%{SCORE_THRESHOLD}", 38 | "vars" : [ 39 | { 40 | "parse" : [ 41 | "specific", 42 | "anomaly_score" 43 | ], 44 | "storage" : 1, 45 | "type" : "TX" 46 | } 47 | ] 48 | } 49 | ], 50 | "header_filter" : [ 51 | { 52 | "actions" : { 53 | "disrupt" : "DENY" 54 | }, 55 | "id" : 99002, 56 | "logdata" : "%{TX.anomaly_score}", 57 | "msg" : "Request score greater than score threshold", 58 | "operator" : "GREATER_EQ", 59 | "opts" : { 60 | "parsepattern" : true 61 | }, 62 | "pattern" : "%{SCORE_THRESHOLD}", 63 | "vars" : [ 64 | { 65 | "parse" : [ 66 | "specific", 67 | "anomaly_score" 68 | ], 69 | "storage" : 1, 70 | "type" : "TX" 71 | } 72 | ] 73 | } 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS=-c -O3 -Wall -Werror -fpic 3 | 4 | SHARED_OPTS=-shared -o $(LIB_NAME) $(OBJ_NAME) 5 | 6 | LIB=libdecode 7 | LIB_NAME=libdecode.so 8 | OBJ_NAME=decode.o 9 | 10 | all: $(LIB) 11 | 12 | $(LIB): $(OBJ_NAME) 13 | $(CC) $(SHARED_OPTS) 14 | 15 | $(OBJ_NAME): $@ 16 | 17 | .c.o: 18 | $(CC) $(CFLAGS) $< 19 | 20 | clean: 21 | rm -f $(LIB_NAME) $(OBJ_NAME) 22 | -------------------------------------------------------------------------------- /t/acceptance/actions/01_accept.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: ACCEPT exits the phase with ngx.OK 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:set_option("debug", true) 28 | waf:set_option("mode", "ACTIVE") 29 | waf:set_option("add_ruleset_string", "10100", [=[{"access":[{"actions":{"disrupt":"ACCEPT"},"id":"12345","operator":"REGEX","pattern":"foo","vars":[{"parse":["values",1],"type":"REQUEST_ARGS"}]}],"body_filter":[],"header_filter":[]}]=]) 30 | waf:exec() 31 | 32 | ngx.log(ngx.INFO, "We should not see this") 33 | } 34 | 35 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 36 | } 37 | --- request 38 | GET /t?a=foo 39 | --- error_code: 200 40 | --- error_log 41 | Rule action was ACCEPT, so ending this phase with ngx.OK 42 | --- no_error_log 43 | [error] 44 | We should not see this 45 | 46 | === TEST 2: ACCEPT does not exit the phase when mode is not ACTIVE 47 | --- http_config eval: $::HttpConfig 48 | --- config 49 | location /t { 50 | access_by_lua_block { 51 | local lua_resty_waf = require "resty.waf" 52 | local waf = lua_resty_waf:new() 53 | 54 | waf:set_option("debug", true) 55 | waf:set_option("add_ruleset_string", "10100", [=[{"access":[{"actions":{"disrupt":"ACCEPT"},"id":"12345","operator":"REGEX","pattern":"foo","vars":[{"parse":["values",1],"type":"REQUEST_ARGS"}]}],"body_filter":[],"header_filter":[]}]=]) 56 | waf:exec() 57 | 58 | ngx.log(ngx.INFO, "We should see this") 59 | } 60 | 61 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 62 | } 63 | --- request 64 | GET /t?a=foo 65 | --- error_code: 200 66 | --- error_log 67 | Rule action was ACCEPT, so ending this phase with ngx.OK 68 | We should see this 69 | --- no_error_log 70 | [error] 71 | 72 | -------------------------------------------------------------------------------- /t/acceptance/actions/02_deny.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: DENY exits the phase with ngx.HTTP_FORBIDDEN 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:set_option("debug", true) 28 | waf:set_option("mode", "ACTIVE") 29 | waf:set_option("add_ruleset_string", "10100", [=[{"access":[{"actions":{"disrupt":"DENY"},"id":"12345","operator":"REGEX","pattern":"foo","vars":[{"parse":["values",1],"type":"REQUEST_ARGS"}]}],"body_filter":[],"header_filter":[]}]=]) 30 | waf:exec() 31 | 32 | ngx.log(ngx.INFO, "We should not see this") 33 | } 34 | 35 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 36 | } 37 | --- request 38 | GET /t?a=foo 39 | --- error_code: 403 40 | --- error_log 41 | Rule action was DENY, so telling nginx to quit 42 | --- no_error_log 43 | [error] 44 | We should not see this 45 | 46 | === TEST 2: DENY does not exit the phase when mode is not ACTIVE 47 | --- http_config eval: $::HttpConfig 48 | --- config 49 | location /t { 50 | access_by_lua_block { 51 | local lua_resty_waf = require "resty.waf" 52 | local waf = lua_resty_waf:new() 53 | 54 | waf:set_option("debug", true) 55 | waf:set_option("add_ruleset_string", "10100", [=[{"access":[{"actions":{"disrupt":"DENY"},"id":"12345","operator":"REGEX","pattern":"foo","vars":[{"parse":["values",1],"type":"REQUEST_ARGS"}]}],"body_filter":[],"header_filter":[]}]=]) 56 | waf:exec() 57 | 58 | ngx.log(ngx.INFO, "We should see this") 59 | } 60 | 61 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 62 | } 63 | --- request 64 | GET /t?a=foo 65 | --- error_code: 200 66 | --- error_log 67 | Rule action was DENY, so telling nginx to quit 68 | We should see this 69 | --- no_error_log 70 | [error] 71 | 72 | -------------------------------------------------------------------------------- /t/acceptance/ctl/01_rule_remove_id.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;$pwd/t/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Ignore a rule at runtime 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:set_option("debug", true) 28 | waf:set_option("mode", "ACTIVE") 29 | waf:set_option("add_ruleset", "10000_ctl") 30 | 31 | waf:exec() 32 | } 33 | 34 | content_by_lua_block { ngx.exit(ngx.HTTP_OK) } 35 | } 36 | --- request 37 | GET /t 38 | --- error_code: 200 39 | --- error_log 40 | Runtime ignoring rule 12346 41 | Ignoring rule 12346 42 | --- no_error_log 43 | [error] 44 | 45 | -------------------------------------------------------------------------------- /t/acceptance/ctl/03_mode.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | repeat_each(3); 5 | plan tests => repeat_each() * 9; 6 | 7 | our $pwd = cwd(); 8 | 9 | our $HttpConfig = qq{ 10 | lua_package_path "$pwd/lib/?.lua;$pwd/t/?.lua;;"; 11 | lua_package_cpath "$pwd/lib/?.lua;;"; 12 | 13 | init_worker_by_lua_block { 14 | local waf = require "resty.waf" 15 | waf.load_secrules("$::pwd/t/rules/mode.rules") 16 | waf.init() 17 | } 18 | }; 19 | 20 | no_shuffle(); 21 | run_tests(); 22 | 23 | __DATA__ 24 | 25 | === TEST 1: Match the deny rule (verify ruleset) 26 | --- http_config eval: $::HttpConfig 27 | --- config 28 | location /t { 29 | access_by_lua_block { 30 | local lua_resty_waf = require "resty.waf" 31 | local waf = lua_resty_waf:new() 32 | 33 | waf:set_option("debug", true) 34 | waf:set_option("mode", "ACTIVE") 35 | waf:set_option("add_ruleset", "mode.rules") 36 | waf:exec() 37 | } 38 | 39 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 40 | } 41 | --- request 42 | GET /t?c=d 43 | --- error_code: 403 44 | --- error_log 45 | Match of rule 12346 46 | --- no_error_log 47 | [error] 48 | Match of rule 12345 49 | 50 | === TEST 2: Match the ctl rule and deny rule 51 | --- http_config eval: $::HttpConfig 52 | --- config 53 | location /t { 54 | access_by_lua_block { 55 | local lua_resty_waf = require "resty.waf" 56 | local waf = lua_resty_waf:new() 57 | 58 | waf:set_option("debug", true) 59 | waf:set_option("mode", "ACTIVE") 60 | waf:set_option("add_ruleset", "mode.rules") 61 | waf:exec() 62 | } 63 | 64 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 65 | } 66 | --- request 67 | GET /t?c=d&a=b 68 | --- error_code: 200 69 | --- error_log 70 | Match of rule 12345 71 | Overriding mode from ACTIVE to SIMULATE 72 | Match of rule 12346 73 | --- no_error_log 74 | [error] 75 | 76 | -------------------------------------------------------------------------------- /t/acceptance/logging/02_transaction_id.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Transaction ID exists in log file 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:set_option("debug", true) 28 | waf:exec() 29 | } 30 | 31 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 32 | } 33 | --- request 34 | GET /t 35 | --- error_code: 200 36 | --- error_log eval 37 | qr/\[lua\] \w+\.lua:\d+: \w+[(][)]: \[[a-f0-9]{20}\]/ 38 | --- no_error_log 39 | [error] 40 | 41 | === TEST 2: Transaction ID exists as request header 42 | --- http_config eval: $::HttpConfig 43 | --- config 44 | location /t { 45 | access_by_lua_block { 46 | local lua_resty_waf = require "resty.waf" 47 | local waf = lua_resty_waf:new() 48 | 49 | waf:set_option("debug", true) 50 | waf:set_option("req_tid_header", true) 51 | waf:exec() 52 | } 53 | 54 | content_by_lua_block { 55 | local t = ngx.req.get_headers() 56 | ngx.say("X-lua_resty_waf-ID: " .. t["X-Lua-Resty-WAF-ID"]) 57 | } 58 | } 59 | --- request 60 | GET /t 61 | --- error_code: 200 62 | --- response_body_like 63 | ^X-lua_resty_waf-ID: [a-f0-9]{20}$ 64 | --- no_error_log 65 | [error] 66 | 67 | === TEST 3: Transaction ID exists as response header 68 | --- http_config eval: $::HttpConfig 69 | --- config 70 | location /t { 71 | access_by_lua_block { 72 | local lua_resty_waf = require "resty.waf" 73 | local waf = lua_resty_waf:new() 74 | 75 | waf:set_option("debug", true) 76 | waf:set_option("res_tid_header", true) 77 | waf:exec() 78 | } 79 | 80 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 81 | } 82 | --- request 83 | GET /t 84 | --- error_code: 200 85 | --- response_headers_like 86 | X-lua_resty_waf-ID: [a-f0-9]{20} 87 | --- no_error_log 88 | [error] 89 | 90 | -------------------------------------------------------------------------------- /t/acceptance/logging/03_log_event.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | repeat_each(3); 5 | plan tests => repeat_each() * 4 * blocks(); 6 | 7 | my $pwd = cwd(); 8 | 9 | our $HttpConfig = qq{ 10 | lua_package_path "$pwd/lib/?.lua;$pwd/t/?.lua;;"; 11 | lua_package_cpath "$pwd/lib/?.lua;;"; 12 | }; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | 18 | __DATA__ 19 | 20 | === TEST 1: Do not log a rule with nolog set 21 | --- http_config eval: $::HttpConfig 22 | --- config 23 | location /t { 24 | access_by_lua_block { 25 | local lua_resty_waf = require "resty.waf" 26 | local waf = lua_resty_waf:new() 27 | 28 | waf:set_option("debug", true) 29 | waf:set_option("add_ruleset", "log") 30 | waf:exec() 31 | } 32 | 33 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 34 | 35 | log_by_lua_block { 36 | local lua_resty_waf = require "resty.waf" 37 | local waf = lua_resty_waf:new() 38 | 39 | waf:write_log_events() 40 | } 41 | } 42 | --- request 43 | GET /t?arg=foo 44 | --- more_headers 45 | User-Agent: testy mctesterson 46 | Accept: */* 47 | --- error_code: 200 48 | --- error_log 49 | Not logging a request that had no rule alerts 50 | --- no_error_log 51 | [error] 52 | "alerts":[{"match":"foo","id":"12345"}] 53 | 54 | === TEST 2: Do not log chain rules that are not the chain end 55 | --- http_config eval: $::HttpConfig 56 | --- config 57 | location /t { 58 | access_by_lua_block { 59 | local lua_resty_waf = require "resty.waf" 60 | local waf = lua_resty_waf:new() 61 | 62 | waf:set_option("debug", true) 63 | waf:set_option("add_ruleset", "log") 64 | waf:exec() 65 | } 66 | 67 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 68 | 69 | log_by_lua_block { 70 | local lua_resty_waf = require "resty.waf" 71 | local waf = lua_resty_waf:new() 72 | 73 | waf:write_log_events() 74 | } 75 | } 76 | --- request 77 | GET /t?arg=foo2&otherarg=bar 78 | --- more_headers 79 | User-Agent: testy mctesterson 80 | Accept: */* 81 | --- error_code: 200 82 | --- error_log 83 | "alerts":[{"match":"bar","id":"12346"}] 84 | --- no_error_log 85 | [error] 86 | "match":"foo2","id":"12346" 87 | 88 | -------------------------------------------------------------------------------- /t/acceptance/run/01_sanity.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: load module 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location = /t { 23 | access_by_lua_block { 24 | lua_resty_waf = require "resty.waf" 25 | } 26 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 27 | } 28 | --- request 29 | GET /t 30 | --- response_body 31 | --- error_code: 200 32 | --- no_error_log 33 | [error] 34 | 35 | === TEST 2: new instance 36 | --- http_config eval: $::HttpConfig 37 | --- config 38 | location = /t { 39 | access_by_lua_block { 40 | lua_resty_waf = require "resty.waf" 41 | local waf = lua_resty_waf:new() 42 | } 43 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 44 | } 45 | --- request 46 | GET /t 47 | --- response_body 48 | --- error_code: 200 49 | --- no_error_log 50 | [error] 51 | 52 | === TEST 3:do not load invalid module 53 | --- http_config eval: $::HttpConfig 54 | --- config 55 | location = /t { 56 | access_by_lua_block { 57 | lua_resty_waf = require "fw2" 58 | } 59 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 60 | } 61 | --- request 62 | GET /t 63 | --- error_code: 500 64 | --- error_log 65 | [error] 66 | fw2 67 | 68 | -------------------------------------------------------------------------------- /t/acceptance/run/03_rule_strings.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 5 * blocks(); 13 | 14 | check_accum_error_log(); 15 | 16 | no_shuffle(); 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: Add an inline ruleset via set_option 22 | --- http_config eval: $::HttpConfig 23 | --- config 24 | location /t { 25 | access_by_lua_block { 26 | local lua_resty_waf = require "resty.waf" 27 | local waf = lua_resty_waf:new() 28 | 29 | waf:set_option("debug", true) 30 | waf:set_option("add_ruleset_string", "10100", [=[{"access":[{"actions":{"disrupt":"DENY"},"id":73,"operator":"REGEX","opts":{},"pattern":"foo","vars":[{"parse":["values",1],"type":"REQUEST_ARGS"}]}],"body_filter":[],"header_filter":[]}]=]) 31 | 32 | waf:exec() 33 | } 34 | 35 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 36 | } 37 | --- request 38 | GET /t 39 | --- error_code: 200 40 | --- error_log 41 | Adding ruleset string 10100 42 | Beginning ruleset 10100, 43 | Processing rule 73 44 | --- no_error_log 45 | [error] 46 | 47 | === TEST 2: Add an inline ruleset via set_option, then ignore a rule in the ruleset 48 | --- http_config eval: $::HttpConfig 49 | --- config 50 | location /t { 51 | access_by_lua_block { 52 | local lua_resty_waf = require "resty.waf" 53 | local waf = lua_resty_waf:new() 54 | 55 | waf:set_option("debug", true) 56 | waf:set_option("add_ruleset_string", "10100", [=[{"access":[{"actions":{"disrupt":"DENY"},"id":73,"operator":"REGEX","opts":{},"pattern":"foo","vars":[{"parse":["values",1],"type":"REQUEST_ARGS"}]}],"body_filter":[],"header_filter":[]}]=]) 57 | waf:set_option("ignore_rule", 73) 58 | 59 | waf:exec() 60 | } 61 | 62 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 63 | } 64 | --- request 65 | GET /t 66 | --- error_code: 200 67 | --- error_log 68 | Adding ruleset string 10100 69 | Beginning ruleset 10100, 70 | --- no_error_log 71 | [error] 72 | Processing rule 73 73 | 74 | -------------------------------------------------------------------------------- /t/acceptance/run/04_unknown_content_types.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() + 3; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Do not allow unknown content types 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location = /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:set_option("debug", true) 28 | waf:exec() 29 | } 30 | 31 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 32 | } 33 | --- request 34 | GET /t 35 | --- more_headers 36 | Content-Type: application/foobar 37 | --- error_code: 403 38 | --- error_log 39 | application/foobar not a valid content type 40 | --- no_error_log 41 | [error] 42 | 43 | === TEST 2: Explicitly allow unknown content types 44 | --- http_config eval: $::HttpConfig 45 | --- config 46 | location = /t { 47 | access_by_lua_block { 48 | local lua_resty_waf = require "resty.waf" 49 | local waf = lua_resty_waf:new() 50 | 51 | waf:set_option("debug", true) 52 | waf:set_option("allow_unknown_content_types", true) 53 | waf:exec() 54 | } 55 | 56 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 57 | } 58 | --- request 59 | GET /t 60 | --- more_headers 61 | Content-Type: application/foobar 62 | --- error_code: 200 63 | --- error_log 64 | Allowing request with content type application/foobar 65 | --- no_error_log 66 | [error] 67 | application/foobar not a valid content type 68 | 69 | -------------------------------------------------------------------------------- /t/acceptance/run/05_resty_core.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 5 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: lua_resty_waf runs with resty.core included 20 | --- http_config eval 21 | $::HttpConfig . q# 22 | init_by_lua_block { 23 | require "resty.core" 24 | } 25 | # 26 | --- config 27 | location /t { 28 | access_by_lua_block { 29 | local lua_resty_waf = require "resty.waf" 30 | local waf = lua_resty_waf:new() 31 | 32 | waf:set_option("debug", true) 33 | waf:exec() 34 | } 35 | 36 | header_filter_by_lua_block { 37 | local lua_resty_waf = require "resty.waf" 38 | local waf = lua_resty_waf:new() 39 | 40 | waf:exec() 41 | } 42 | 43 | body_filter_by_lua_block { 44 | local lua_resty_waf = require "resty.waf" 45 | local waf = lua_resty_waf:new() 46 | 47 | waf:exec() 48 | } 49 | 50 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 51 | } 52 | --- request 53 | GET /t 54 | --- error_code: 200 55 | --- error_log 56 | Beginning run of phase access 57 | Beginning run of phase header_filter 58 | Beginning run of phase body_filter 59 | --- no_error_log 60 | [error] 61 | 62 | -------------------------------------------------------------------------------- /t/acceptance/run/06_status.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 2 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Default deny status (403) 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:set_option("mode", "ACTIVE") 28 | waf:exec() 29 | } 30 | 31 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 32 | } 33 | --- request 34 | GET /t?foo=alert(1) 35 | --- error_code: 403 36 | --- no_error_log 37 | [error] 38 | 39 | === TEST 2: Alternative deny status (status constant) 40 | --- http_config eval: $::HttpConfig 41 | --- config 42 | location /t { 43 | access_by_lua_block { 44 | local lua_resty_waf = require "resty.waf" 45 | local waf = lua_resty_waf:new() 46 | 47 | waf:set_option("deny_status", ngx.HTTP_NOT_FOUND) 48 | waf:set_option("mode", "ACTIVE") 49 | waf:exec() 50 | } 51 | 52 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 53 | } 54 | --- request 55 | GET /t?foo=alert(1) 56 | --- error_code: 404 57 | --- no_error_log 58 | [error] 59 | 60 | === TEST 2: Alternative deny status (integer) 61 | --- http_config eval: $::HttpConfig 62 | --- config 63 | location /t { 64 | access_by_lua_block { 65 | local lua_resty_waf = require "resty.waf" 66 | local waf = lua_resty_waf:new() 67 | 68 | waf:set_option("deny_status", 418) 69 | waf:set_option("mode", "ACTIVE") 70 | waf:exec() 71 | } 72 | 73 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 74 | } 75 | --- request 76 | GET /t?foo=alert(1) 77 | --- error_code: 418 78 | --- no_error_log 79 | [error] 80 | -------------------------------------------------------------------------------- /t/acceptance/run/08_score_threshold.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 2 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Default threshold doesn't deny benign request 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:set_option("mode", "ACTIVE") 28 | waf:exec() 29 | } 30 | 31 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 32 | } 33 | --- more_headers 34 | Accept: */* 35 | User-Agent: testy mctesterson 36 | --- request 37 | GET /t?foo=bar 38 | --- error_code: 200 39 | --- no_error_log 40 | [error] 41 | 42 | === TEST 2: Lowered threshold denies dummy request 43 | --- http_config eval: $::HttpConfig 44 | --- config 45 | location /t { 46 | access_by_lua_block { 47 | local lua_resty_waf = require "resty.waf" 48 | local waf = lua_resty_waf:new() 49 | 50 | waf:set_option("score_threshold", 3) 51 | waf:set_option("mode", "ACTIVE") 52 | waf:exec() 53 | } 54 | 55 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 56 | } 57 | --- more_headers 58 | Accept: */* 59 | User-Agent: lua-resty-waf Dummy 60 | --- request 61 | GET /t?foo=bar 62 | --- error_code: 403 63 | --- no_error_log 64 | [error] 65 | 66 | === TEST 3: Default threshold denies suspicious request 67 | --- http_config eval: $::HttpConfig 68 | --- config 69 | location /t { 70 | access_by_lua_block { 71 | local lua_resty_waf = require "resty.waf" 72 | local waf = lua_resty_waf:new() 73 | 74 | waf:set_option("mode", "ACTIVE") 75 | waf:exec() 76 | } 77 | 78 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 79 | } 80 | --- more_headers 81 | User-Agent: nessus 82 | --- request 83 | GET /t?foo=bar 84 | --- error_code: 403 85 | --- no_error_log 86 | [error] 87 | 88 | === TEST 4: Raised threshold does not deny suspicious request 89 | --- http_config eval: $::HttpConfig 90 | --- config 91 | location /t { 92 | access_by_lua_block { 93 | local lua_resty_waf = require "resty.waf" 94 | local waf = lua_resty_waf:new() 95 | 96 | waf:set_option("score_threshold", 20) 97 | waf:set_option("mode", "ACTIVE") 98 | waf:exec() 99 | } 100 | 101 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 102 | } 103 | --- more_headers 104 | User-Agent: nessus 105 | --- request 106 | GET /t?foo=bar 107 | --- error_code: 200 108 | --- no_error_log 109 | [error] 110 | 111 | -------------------------------------------------------------------------------- /t/acceptance/run/09_debug_logging.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | our $log_prefix = qr{\d{4}/\d{2}/\d{2} (?:\d+:?){3} \[\w+\] \d{2,5}\#\d+: \*\d+ \[lua\] }; 12 | 13 | repeat_each(3); 14 | plan tests => repeat_each() * 11 * blocks(); 15 | 16 | no_shuffle(); 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: Debug logs written as expected 22 | --- http_config eval: $::HttpConfig 23 | --- config 24 | location /t { 25 | access_by_lua_block { 26 | local lua_resty_waf = require "resty.waf" 27 | local waf = lua_resty_waf:new() 28 | 29 | waf:set_option("debug", true) 30 | waf:exec() 31 | } 32 | 33 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 34 | } 35 | --- more_headers 36 | Accept: */* 37 | User-Agent: testy mctesterson 38 | --- request 39 | GET /t?foo=bar 40 | --- error_code: 200 41 | --- error_log eval 42 | [ 43 | $::log_prefix, 44 | qr{waf\.lua:\d+: exec\(\): \[[0-9a-z]{20}\] Beginning run of phase access}, 45 | qr{waf\.lua:\d+: exec\(\): \[[0-9a-z]{20}\] Beginning ruleset \d+_\w+}, 46 | qr{waf\.lua:\d+: exec\(\): \[[0-9a-z]{20}\] Processing rule \d+}, 47 | qr{waf\.lua:\d+: _build_collection\(\): \[[0-9a-z]{20}\] Checking for collection_key [A-Z]+\|[a-z]+,}, 48 | qr{util\.lua:\d+: _parse_collection\(\): \[[0-9a-z]{20}\] Parse collection}, 49 | qr{waf\.lua:\d+: _process_rule\(\): \[[0-9a-z]{20}\] Returning offset \d+}, 50 | qr{waf\.lua:\d+: _process_rule\(\): \[[0-9a-z]{20}\] Returning offset nil}, 51 | qr{storage\.lua:\d+: persist\(\): \[[0-9a-z]{20}\] Persisting storage type}, 52 | ] 53 | --- no_error_log 54 | [error] 55 | 56 | -------------------------------------------------------------------------------- /t/acceptance/storage/02_expire_var.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Expire a var - confirm the set value and __altered flag 20 | --- http_config eval 21 | $::HttpConfig . q# 22 | lua_shared_dict store 10m; 23 | init_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | } 26 | # 27 | --- config 28 | location = /t { 29 | access_by_lua_block { 30 | local lua_resty_waf = require "resty.waf" 31 | local waf = lua_resty_waf:new() 32 | 33 | waf:set_option("storage_zone", "store") 34 | waf:set_option("debug", true) 35 | 36 | local ctx = { storage = {}, col_lookup = { FOO = "FOO" } } 37 | 38 | local storage = require "resty.waf.storage" 39 | storage.initialize(waf, ctx.storage, "FOO") 40 | 41 | local element = { col = "FOO", key = "COUNT", value = 1 } 42 | storage.set_var(waf, ctx, element, element.value) 43 | storage.expire_var(waf,ctx, element, 10) 44 | 45 | ngx.ctx = ctx.storage["FOO"] 46 | } 47 | 48 | content_by_lua_block { 49 | ngx.say(ngx.ctx["__altered"]) 50 | ngx.say(ngx.ctx["__expire_COUNT"] == ngx.now() + 10) 51 | } 52 | } 53 | --- request 54 | GET /t 55 | --- error_code: 200 56 | --- response_body 57 | true 58 | true 59 | --- error_log 60 | Setting FOO:COUNT to 1 61 | Expiring FOO:COUNT in 10 62 | --- no_error_log 63 | [error] 64 | 65 | === TEST 2: Bail out when collection is unitialized 66 | --- http_config eval 67 | $::HttpConfig . q# 68 | lua_shared_dict store 10m; 69 | # 70 | --- config 71 | location = /t { 72 | access_by_lua_block { 73 | local lua_resty_waf = require "resty.waf" 74 | local waf = lua_resty_waf:new() 75 | local storage = require "resty.waf.storage" 76 | 77 | waf:set_option("storage_zone", "store") 78 | waf:set_option("debug", true) 79 | 80 | local ctx = { storage = {}, col_lookup = {} } 81 | 82 | local element = { col = "FOO", key = "COUNT", value = 1 } 83 | storage.expire_var(waf, ctx, element, 10) 84 | } 85 | 86 | content_by_lua_block {ngx.exit(ngx.OK)} 87 | } 88 | --- request 89 | GET /t 90 | --- error_code: 200 91 | --- error_log 92 | FOO not initialized 93 | --- no_error_log 94 | [error] 95 | -------------------------------------------------------------------------------- /t/acceptance/storage/memcached/01_connect.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Connect with defaults 20 | --- http_config eval 21 | $::HttpConfig . q# 22 | init_by_lua_block { 23 | local lua_resty_waf = require "resty.waf" 24 | } 25 | # 26 | --- config 27 | location = /t { 28 | access_by_lua_block { 29 | local lua_resty_waf = require "resty.waf" 30 | local waf = lua_resty_waf:new() 31 | 32 | waf:set_option("storage_backend", "memcached") 33 | waf:set_option("debug", true) 34 | 35 | local memcached_m = require "resty.memcached" 36 | local memcached = memcached_m:new() 37 | 38 | memcached:connect(waf._storage_memcached_host, waf._storage_memcached_port) 39 | memcached:flush_all() 40 | 41 | local data = {} 42 | 43 | local storage = require "resty.waf.storage" 44 | storage.initialize(waf, data, "FOO") 45 | } 46 | 47 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 48 | } 49 | --- request 50 | GET /t 51 | --- error_code: 200 52 | --- error_log 53 | Initializing an empty collection 54 | --- no_error_log 55 | [error] 56 | Error in connecting to memcached 57 | 58 | === TEST 2: Connect with invalid port 59 | --- http_config eval 60 | $::HttpConfig . q# 61 | init_by_lua_block { 62 | local lua_resty_waf = require "resty.waf" 63 | } 64 | lua_socket_log_errors off; 65 | # 66 | --- config 67 | location = /t { 68 | access_by_lua_block { 69 | local lua_resty_waf = require "resty.waf" 70 | local waf = lua_resty_waf:new() 71 | 72 | waf:set_option("storage_backend", "memcached") 73 | waf:set_option("debug", true) 74 | 75 | local memcached_m = require "resty.memcached" 76 | local memcached = memcached_m:new() 77 | 78 | memcached:connect(waf._storage_memcached_host, waf._storage_memcached_port) 79 | memcached:flush_all() 80 | 81 | waf:set_option("storage_memcached_port", 11221) 82 | 83 | local data = {} 84 | 85 | local storage = require "resty.waf.storage" 86 | storage.initialize(waf, data, "FOO") 87 | } 88 | 89 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 90 | } 91 | --- request 92 | GET /t 93 | --- error_code: 200 94 | --- error_log 95 | Error in connecting to memcached 96 | --- no_error_log 97 | [error] 98 | Initializing an empty collection 99 | -------------------------------------------------------------------------------- /t/acceptance/storage/redis/01_connect.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Connect with defaults 20 | --- http_config eval 21 | $::HttpConfig . q# 22 | init_by_lua_block { 23 | local lua_resty_waf = require "resty.waf" 24 | } 25 | # 26 | --- config 27 | location = /t { 28 | access_by_lua_block { 29 | local lua_resty_waf = require "resty.waf" 30 | local waf = lua_resty_waf:new() 31 | 32 | waf:set_option("storage_backend", "redis") 33 | waf:set_option("debug", true) 34 | 35 | local redis_m = require "resty.redis" 36 | local redis = redis_m:new() 37 | 38 | redis:connect(waf._storage_redis_host, waf._storage_redis_port) 39 | redis:flushall() 40 | 41 | local data = {} 42 | 43 | local storage = require "resty.waf.storage" 44 | storage.initialize(waf, data, "FOO") 45 | } 46 | 47 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 48 | } 49 | --- request 50 | GET /t 51 | --- error_code: 200 52 | --- error_log 53 | Initializing an empty collection 54 | --- no_error_log 55 | [error] 56 | Error in connecting to redis 57 | 58 | === TEST 2: Connect with invalid port 59 | --- http_config eval 60 | $::HttpConfig . q# 61 | init_by_lua_block { 62 | local lua_resty_waf = require "resty.waf" 63 | } 64 | lua_socket_log_errors off; 65 | # 66 | --- config 67 | location = /t { 68 | access_by_lua_block { 69 | local lua_resty_waf = require "resty.waf" 70 | local waf = lua_resty_waf:new() 71 | 72 | waf:set_option("storage_backend", "redis") 73 | waf:set_option("debug", true) 74 | 75 | local redis_m = require "resty.redis" 76 | local redis = redis_m:new() 77 | 78 | redis:connect(waf._storage_redis_host, waf._storage_redis_port) 79 | redis:flushall() 80 | 81 | waf:set_option("storage_redis_port", 6397) 82 | 83 | local data = {} 84 | 85 | local storage = require "resty.waf.storage" 86 | storage.initialize(waf, data, "FOO") 87 | } 88 | 89 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 90 | } 91 | --- request 92 | GET /t 93 | --- error_code: 200 94 | --- error_log 95 | Error in connecting to redis 96 | --- no_error_log 97 | [error] 98 | Initializing an empty collection 99 | -------------------------------------------------------------------------------- /t/data/ips.txt: -------------------------------------------------------------------------------- 1 | 1.2.3.4 2 | 5.6.7.8 3 | 10.10.10.0/24 4 | -------------------------------------------------------------------------------- /t/regression/157/157.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Bail of out request due to if 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | if ($uri ~ t) { 24 | return 403; 25 | } 26 | 27 | access_by_lua_block { 28 | local lua_resty_waf = require "resty.waf" 29 | local waf = lua_resty_waf:new() 30 | 31 | waf:set_option("debug", true) 32 | waf:exec() 33 | } 34 | 35 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 36 | 37 | log_by_lua_block { 38 | local lua_resty_waf = require "resty.waf" 39 | local waf = lua_resty_waf:new() 40 | 41 | waf:write_log_events() 42 | } 43 | } 44 | --- request 45 | GET /t 46 | --- error_code: 403 47 | --- error_log 48 | Not attempting to write log as lua-resty-waf was never exec'd 49 | --- no_error_log 50 | [error] 51 | nil was given to table_keys 52 | 53 | === TEST 2: Do not bail of out request due to if 54 | --- http_config eval: $::HttpConfig 55 | --- config 56 | location /t { 57 | if ($uri ~ s) { 58 | return 403; 59 | } 60 | 61 | access_by_lua_block { 62 | local lua_resty_waf = require "resty.waf" 63 | local waf = lua_resty_waf:new() 64 | 65 | waf:set_option("debug", true) 66 | waf:exec() 67 | } 68 | 69 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 70 | 71 | log_by_lua_block { 72 | local lua_resty_waf = require "resty.waf" 73 | local waf = lua_resty_waf:new() 74 | 75 | waf:write_log_events() 76 | } 77 | } 78 | --- request 79 | GET /t 80 | --- error_code: 200 81 | --- no_error_log 82 | [error] 83 | nil was given to table_keys 84 | Not attempting to write log as lua-resty-waf was never exec'd 85 | -------------------------------------------------------------------------------- /t/rules/10000_ctl.json: -------------------------------------------------------------------------------- 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":[]} 2 | -------------------------------------------------------------------------------- /t/rules/10000_ctl_meta.json: -------------------------------------------------------------------------------- 1 | {"access":[{"actions":{"disrupt":"IGNORE","nondisrupt":[{"action":"rule_remove_by_meta","data":1}]},"exceptions":["mocktag"],"id":"12345","opts":{"nolog":1},"vars":[{"unconditional":1}]},{"actions":{"disrupt":"IGNORE","nondisrupt":[{"action":"rule_remove_by_meta","data":1}]},"exceptions":["mockmsg"],"id":"12355","opts":{"nolog":1},"vars":[{"unconditional":1}]},{"actions":{"disrupt":"DENY"},"id":"12346","operator":"REFIND","pattern":"bar","tag":["mocktag"],"vars":[{"parse":["values","1"],"type":"REQUEST_ARGS"}]},{"actions":{"disrupt":"DENY"},"id":"12347","operator":"REFIND","pattern":"baz","tag":["mocktag","othertag"],"vars":[{"parse":["values","1"],"type":"REQUEST_ARGS"}]},{"actions":{"disrupt":"DENY"},"id":"12348","operator":"REFIND","pattern":"bat","tag":["othertag"],"vars":[{"parse":["values","1"],"type":"REQUEST_ARGS"}]},{"actions":{"disrupt":"DENY"},"id":"12356","msg":"mockmsg","operator":"REFIND","pattern":"foo","vars":[{"parse":["values","1"],"type":"REQUEST_ARGS"}]},{"actions":{"disrupt":"DENY"},"id":"12357","msg":"othermsg","operator":"REFIND","pattern":"frob","vars":[{"parse":["values","1"],"type":"REQUEST_ARGS"}]}],"body_filter":[],"header_filter":[]} 2 | -------------------------------------------------------------------------------- /t/rules/body_storage.json: -------------------------------------------------------------------------------- 1 | {"access":[{"actions":{"disrupt":"IGNORE","nondisrupt":[{"action":"initcol","data":{"col":"IP","value":"%{REMOTE_ADDR}"}}]},"id":"12345","opts":{"nolog":1},"vars":[{"unconditional":1}]},{"actions":{"disrupt":"IGNORE","nondisrupt":[{"action":"setvar","data":{"col":"IP","key":"HIT","value":"foo"}}]},"id":"12346","operator":"EQUALS","opts":{"parsepattern":1},"pattern":"foo","vars":[{"parse":["values","1"],"type":"REQUEST_ARGS"}]},{"actions":{"disrupt":"IGNORE","nondisrupt":[{"action":"deletevar","data":{"col":"IP","key":"HIT"}}]},"id":"12347","operator":"EQUALS","opts":{"parsepattern":1},"pattern":"bar","vars":[{"parse":["values","1"],"type":"REQUEST_ARGS"}]}],"body_filter":[{"actions":{"disrupt":"DENY"},"id":"12348","operator":"EQUALS","opts":{"parsepattern":1},"pattern":"foo","vars":[{"parse":["specific","HIT"],"storage":1,"type":"IP"}]}],"header_filter":[]} 2 | -------------------------------------------------------------------------------- /t/rules/extra.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /t/rules/extra_broken.json: -------------------------------------------------------------------------------- 1 | { 2 | -------------------------------------------------------------------------------- /t/rules/log.json: -------------------------------------------------------------------------------- 1 | {"access":[{"actions":{"disrupt":"DENY"},"id":"12345","operator":"EQUALS","opts":{"nolog":1,"parsepattern":1},"pattern":"foo","vars":[{"parse":["values",1],"type":"REQUEST_ARGS"}]},{"actions":{"disrupt":"CHAIN"},"id":"12346","operator":"EQUALS","opts":{"parsepattern":1},"pattern":"foo2","vars":[{"parse":["values",1],"type":"REQUEST_ARGS"}]},{"actions":{"disrupt":"DENY"},"id":"12346","operator":"EQUALS","opts":{"parsepattern":1},"pattern":"bar","vars":[{"parse":["values",1],"type":"REQUEST_ARGS"}]}],"body_filter":[],"header_filter":[]} 2 | -------------------------------------------------------------------------------- /t/rules/mode.rules: -------------------------------------------------------------------------------- 1 | # this is a set of test rules 2 | SecRule ARGS "@streq b" "id:12345,phase:1,pass,nolog,ctl:ruleEngine=DetectionOnly" 3 | SecRule ARGS "@streq d" "id:12346,phase:1,deny" 4 | -------------------------------------------------------------------------------- /t/rules/multipart.rules: -------------------------------------------------------------------------------- 1 | SecRule ARGS "@streq 12345a" "id:12345,phase:2,chain,deny" 2 | SecRule FILES "@streq a.txt" "id:12345,phase:2,deny" 3 | 4 | SecRule ARGS "@streq 12346a" "id:12346,phase:2,chain,deny" 5 | SecRule FILES_NAMES "@streq test" 6 | 7 | SecRule ARGS "@streq 12347a" "id:12347,phase:2,chain,deny" 8 | SecRule FILES_COMBINED_SIZE "@gt 10" 9 | 10 | SecRule ARGS "@streq 12348a" "id:12348,phase:2,chain,deny" 11 | SecRule FILES_SIZES "@gt 10" 12 | 13 | SecRule ARGS "@streq 12349a" "id:12349,phase:2,chain,deny" 14 | SecRule FILES_TMP_CONTENT "[Hh]ello,\s+world" 15 | 16 | -------------------------------------------------------------------------------- /t/rules/sieve.rules: -------------------------------------------------------------------------------- 1 | # this is a set of test rules 2 | SecRule ARGS "foo" "id:12345,phase:2,msg:'hello world',deny" 3 | SecRule ARGS|!ARGS:bar "baz" "id:12346,phase:2,msg:'hello world',deny" 4 | -------------------------------------------------------------------------------- /t/rules/test.rules: -------------------------------------------------------------------------------- 1 | # this is a set of test rules 2 | SecRule ARGS "foo" "id:12345,phase:2,msg:'hello world',deny" 3 | -------------------------------------------------------------------------------- /t/rules/test_errs.rules: -------------------------------------------------------------------------------- 1 | # this is a set of test rules 2 | SecRule ARGS "foo" "id:12345,phase:2,msg:'hello world',deny,foo" 3 | -------------------------------------------------------------------------------- /t/translate/01_sanity.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | 3 | use lib 'tools'; 4 | 5 | use_ok('Modsec2LRW', ':translate'); 6 | 7 | done_testing; 8 | -------------------------------------------------------------------------------- /t/translate/02_valid_line.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | 3 | use lib 'tools'; 4 | use Modsec2LRW qw(valid_line); 5 | 6 | is( 7 | valid_line('SecRule '), 8 | 1, 9 | 'line starting with SecRule is valid' 10 | ); 11 | 12 | is( 13 | valid_line('SecAction '), 14 | 1, 15 | 'line starting with SecAction is valid' 16 | ); 17 | 18 | is( 19 | valid_line('SecDefaultAction '), 20 | 1, 21 | 'line starting with SecDefaultAction is valid' 22 | ); 23 | 24 | is( 25 | valid_line('SecMarker '), 26 | 1, 27 | 'line starting with SecMarker is valid' 28 | ); 29 | 30 | is( 31 | valid_line('SecFoo '), 32 | '', 33 | 'line starting with unknown directive is invalid' 34 | ); 35 | 36 | is( 37 | valid_line(sprintf "%08X\n", rand(0xffffffff)), 38 | '', 39 | 'line starting with random junk is invalid' 40 | ); 41 | 42 | done_testing; 43 | -------------------------------------------------------------------------------- /t/translate/03_clean_input.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | 3 | use lib 'tools'; 4 | use Modsec2LRW qw(clean_input); 5 | 6 | my $basic = q/SecRule ARGS "foo" "id:12345,pass"/; 7 | my $trim_left = q/ SecRule ARGS "foo" "id:12345,pass"/; 8 | my $trim_right = q/SecRule ARGS "foo" "id:12345,pass" /; 9 | my $trim_both = q/ SecRule ARGS "foo" "id:12345,pass" /; 10 | my $ignore_comment = q/#SecRule ARGS "foo" "id:12345,pass"/; 11 | my $invalid_directive = q/Secrule ARGS "foo" "id:12345,pass"/; 12 | my $multi_line = q/ 13 | SecRule \ 14 | ARGS \ 15 | "foo" \ 16 | "id:12345,pass" 17 | /; 18 | my $multi_line_action = q/ 19 | SecRule \ 20 | ARGS \ 21 | "foo" \ 22 | "id:12345, \ 23 | phase:1, \ 24 | block, \ 25 | setvar:tx.foo=bar, \ 26 | expirevar:tx.foo=60" 27 | /; 28 | 29 | { 30 | my @in = ($basic); 31 | my @out = clean_input(@in); 32 | is_deeply( 33 | \@out, 34 | [ q/SecRule ARGS "foo" "id:12345,pass"/ ], 35 | 'basic' 36 | ); 37 | } 38 | 39 | { 40 | my @in = ($trim_left); 41 | my @out = clean_input(@in); 42 | is_deeply( 43 | \@out, 44 | [ q/SecRule ARGS "foo" "id:12345,pass"/ ], 45 | 'trim left' 46 | ); 47 | } 48 | 49 | { 50 | my @in = ($trim_right); 51 | my @out = clean_input(@in); 52 | is_deeply( 53 | \@out, 54 | [ q/SecRule ARGS "foo" "id:12345,pass"/ ], 55 | 'trim right' 56 | ); 57 | } 58 | 59 | { 60 | my @in = ($trim_both); 61 | my @out = clean_input(@in); 62 | is_deeply( 63 | \@out, 64 | [ q/SecRule ARGS "foo" "id:12345,pass"/ ], 65 | 'trim both' 66 | ); 67 | } 68 | 69 | { 70 | my @in = ($ignore_comment); 71 | my @out = clean_input(@in); 72 | is_deeply( 73 | \@out, 74 | [], 75 | 'comment' 76 | ); 77 | } 78 | 79 | { 80 | my @in = ($invalid_directive); 81 | my @out = clean_input(@in); 82 | is_deeply( 83 | \@out, 84 | [], 85 | 'invalid_directive' 86 | ); 87 | } 88 | 89 | { 90 | my @in = (split /\n/, $multi_line); 91 | my @out = clean_input(@in); 92 | is_deeply( 93 | \@out, 94 | [ q/SecRule ARGS "foo" "id:12345,pass"/ ], 95 | 'multi line' 96 | ); 97 | } 98 | 99 | { 100 | my @in = ($basic, split /\n/, $multi_line); 101 | my @out = clean_input(@in); 102 | is_deeply( 103 | \@out, 104 | [ 105 | q/SecRule ARGS "foo" "id:12345,pass"/, 106 | q/SecRule ARGS "foo" "id:12345,pass"/, 107 | ], 108 | 'multiple elements' 109 | ); 110 | } 111 | 112 | { 113 | my @in = ($basic, $comment, split /\n/, $multi_line); 114 | my @out = clean_input(@in); 115 | is_deeply( 116 | \@out, 117 | [ 118 | q/SecRule ARGS "foo" "id:12345,pass"/, 119 | q/SecRule ARGS "foo" "id:12345,pass"/, 120 | ], 121 | 'multi line with comment' 122 | ); 123 | } 124 | 125 | { 126 | my @in = (split /\n/, $multi_line_action); 127 | my @out = clean_input(@in); 128 | is_deeply( 129 | \@out, 130 | [ q/SecRule ARGS "foo" "id:12345, phase:1, block, setvar:tx.foo=bar, expirevar:tx.foo=60"/ ], 131 | 'multi line action, each line is joined with a space' 132 | ); 133 | } 134 | 135 | done_testing; 136 | -------------------------------------------------------------------------------- /t/translate/04_tokenize.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | 3 | use lib 'tools'; 4 | use Modsec2LRW qw(tokenize); 5 | 6 | my @out; 7 | 8 | @out = tokenize('foo'); 9 | is_deeply( 10 | \@out, 11 | [ qw(foo) ], 12 | 'single token' 13 | ); 14 | 15 | @out = tokenize('foo bar'); 16 | is_deeply( 17 | \@out, 18 | [ qw(foo bar) ], 19 | 'two tokens' 20 | ); 21 | 22 | @out = tokenize('"foo"'); 23 | is_deeply( 24 | \@out, 25 | [ qw(foo) ], 26 | 'quote-wrapped token' 27 | ); 28 | 29 | @out = tokenize('"foo" "bar"'); 30 | is_deeply( 31 | \@out, 32 | [ qw(foo bar) ], 33 | 'two quote-wrapped tokens' 34 | ); 35 | 36 | @out = tokenize('"foo" bar'); 37 | is_deeply( 38 | \@out, 39 | [ qw(foo bar) ], 40 | 'two tokens, first quote-wrapped' 41 | ); 42 | 43 | @out = tokenize('foo "bar"'); 44 | is_deeply( 45 | \@out, 46 | [ qw(foo bar) ], 47 | 'two tokens, second quote-wrapped' 48 | ); 49 | 50 | @out = tokenize('"foo \"bar"'); 51 | is_deeply( 52 | \@out, 53 | [ q(foo "bar) ], 54 | 'quote-wrapped token with single escaped quote' 55 | ); 56 | 57 | @out = tokenize('"foo \"bar\""'); 58 | is_deeply( 59 | \@out, 60 | [ q(foo "bar") ], 61 | 'quote-wrapped token with two escaped quotes' 62 | ); 63 | 64 | @out = tokenize('"foo \"" bar'); 65 | is_deeply( 66 | \@out, 67 | [ q(foo "), q(bar) ], 68 | 'quote-wrapped token with escaped quote, then unquoted token' 69 | ); 70 | 71 | @out = tokenize('foo "bar \""'); 72 | is_deeply( 73 | \@out, 74 | [ q(foo), q(bar ") ], 75 | 'unquoted token, then quote-wrapped token with escaped token' 76 | ); 77 | 78 | @out = tokenize('foo bar baz "bat"'); 79 | is_deeply( 80 | \@out, 81 | [ qw(foo bar baz bat) ], 82 | 'four tokens, last is quote-wrapped' 83 | ); 84 | 85 | @out = tokenize('foo bar baz "bat,qux:\'frob foo\'"'); 86 | is_deeply( 87 | \@out, 88 | [ qw(foo bar baz), q(bat,qux:'frob foo') ], 89 | 'four tokens, last is with escaped single quotes' 90 | ); 91 | 92 | @out = tokenize('foo bar "baz qux" "bat"'); 93 | is_deeply( 94 | \@out, 95 | [ qw(foo bar), q(baz qux), q(bat) ], 96 | 'four tokens, two are quote-wrapped' 97 | ); 98 | 99 | @out = tokenize('foo bar "baz \"qux\"" "bat"'); 100 | is_deeply( 101 | \@out, 102 | [ qw(foo bar), q(baz "qux"), q(bat) ], 103 | 'four tokens, two are quote-wrapped, one escaped quote' 104 | ); 105 | 106 | done_testing; 107 | -------------------------------------------------------------------------------- /t/translate/05_parse_tokens.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | use Test::Exception; 3 | use Test::MockModule; 4 | 5 | use lib 'tools'; 6 | use Modsec2LRW qw(parse_tokens); 7 | 8 | my $Mock = Test::MockModule->new('Modsec2LRW'); 9 | 10 | $Mock->mock(parse_vars => sub { 11 | my ($arg) = @_; 12 | return "$arg-mocked"; 13 | }); 14 | 15 | $Mock->mock(parse_operator => sub { 16 | my ($operator) = @_; 17 | return "$operator-mocked"; 18 | }); 19 | 20 | $Mock->mock(parse_actions => sub { 21 | my ($actions) = @_; 22 | return "$actions-mocked"; 23 | }); 24 | 25 | my $secrule = 'SecRule'; 26 | my $secaction = 'SecAction'; 27 | my $args = 'ARGS'; 28 | my $operator = 'foo'; 29 | my $actions = 'block,id:12345,msg:\'hello world\''; 30 | my $parsed; 31 | 32 | lives_ok( 33 | sub { parse_tokens($secrule, $args, $operator, $actions) }, 34 | 'lives with SecRule and four tokens' 35 | ); 36 | 37 | lives_ok( 38 | sub { parse_tokens($secaction, $actions) }, 39 | 'lives with SecAction and two tokens' 40 | ); 41 | 42 | dies_ok( 43 | sub { parse_tokens($secaction, $args, $operator, $actions) }, 44 | 'dies with SecAction and four tokens' 45 | ); 46 | 47 | $parsed = parse_tokens($secrule, $args, $operator, $actions); 48 | is( 49 | $parsed->{original}, 50 | 'SecRule ARGS foo block,id:12345,msg:\'hello world\'', 51 | 'tokens are rejoined by space' 52 | ); 53 | 54 | is( 55 | $parsed->{directive}, 56 | 'SecRule', 57 | 'directive token is returned directly to the directive key' 58 | ); 59 | 60 | is( 61 | $parsed->{vars}, 62 | 'ARGS-mocked', 63 | 'vars key is built from vars token' 64 | ); 65 | 66 | is( 67 | $parsed->{operator}, 68 | 'foo-mocked', 69 | 'operator key is built from operator token' 70 | ); 71 | 72 | is( 73 | $parsed->{actions}, 74 | 'block,id:12345,msg:\'hello world\'-mocked', 75 | 'actions key is built from actions token' 76 | ); 77 | 78 | $parsed = parse_tokens($secaction, $actions); 79 | is( 80 | $parsed->{original}, 81 | 'SecAction block,id:12345,msg:\'hello world\'', 82 | 'tokens are rejoined by space' 83 | ); 84 | 85 | is( 86 | $parsed->{directive}, 87 | 'SecAction', 88 | 'directive token is returned directly to the directive key' 89 | ); 90 | 91 | is( 92 | $parsed->{vars}, 93 | undef, 94 | 'vars key is undef when directive is not SecRule' 95 | ); 96 | 97 | is( 98 | $parsed->{operator}, 99 | undef, 100 | 'operator key is undef when directive is not SecRule' 101 | ); 102 | 103 | is( 104 | $parsed->{actions}, 105 | 'block,id:12345,msg:\'hello world\'-mocked', 106 | 'actions key is built from actions token' 107 | ); 108 | 109 | done_testing; 110 | -------------------------------------------------------------------------------- /t/translate/07_parse_operator.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | 3 | use lib 'tools'; 4 | use Modsec2LRW qw(parse_operator); 5 | 6 | is_deeply( 7 | parse_operator('foo'), 8 | { 9 | operator => 'rx', 10 | pattern => 'foo', 11 | }, 12 | 'default operator is rx' 13 | ); 14 | 15 | is_deeply( 16 | parse_operator('@rx foo'), 17 | { 18 | operator => 'rx', 19 | pattern => 'foo', 20 | }, 21 | 'explicity define @rx operator' 22 | ); 23 | 24 | is_deeply( 25 | parse_operator('@streq foo'), 26 | { 27 | operator => 'streq', 28 | pattern => 'foo' 29 | }, 30 | 'define an operator' 31 | ); 32 | 33 | is_deeply( 34 | parse_operator('@eq 5'), 35 | { 36 | operator => 'eq', 37 | pattern => '5', 38 | }, 39 | 'define an operator with a numeric pattern' 40 | ); 41 | 42 | is_deeply( 43 | parse_operator('!@rx foo'), 44 | { 45 | operator => 'rx', 46 | pattern => 'foo', 47 | negated => '!' 48 | }, 49 | 'negated operator is defined' 50 | ); 51 | 52 | is_deeply( 53 | parse_operator('@detectSQLi'), 54 | { 55 | operator => 'detectSQLi', 56 | pattern => '', 57 | }, 58 | 'operator with no pattern' 59 | ); 60 | 61 | is_deeply( 62 | parse_operator('!@detectSQLi'), 63 | { 64 | operator => 'detectSQLi', 65 | pattern => '', 66 | negated => '!', 67 | }, 68 | 'negated operator with no pattern' 69 | ); 70 | 71 | is_deeply( 72 | parse_operator('!foo'), 73 | { 74 | operator => 'rx', 75 | pattern => 'foo', 76 | negated => '!' 77 | }, 78 | 'negated operator is defined when operator is implied' 79 | ); 80 | 81 | is_deeply( 82 | parse_operator('! foo'), 83 | { 84 | operator => 'rx', 85 | pattern => ' foo', 86 | negated => '!', 87 | }, 88 | 'space after negation with implicity defined operator' 89 | ); 90 | 91 | is_deeply( 92 | parse_operator('! @rx foo'), 93 | { 94 | operator => 'rx', 95 | pattern => ' @rx foo', 96 | negated => '!', 97 | }, 98 | 'space after negation with explicity defined operator' 99 | ); 100 | 101 | is_deeply( 102 | parse_operator('! @detectSQLi'), 103 | { 104 | operator => 'rx', 105 | pattern => ' @detectSQLi', 106 | negated => '!', 107 | }, 108 | 'space after negation with no pattern' 109 | ); 110 | 111 | done_testing; 112 | -------------------------------------------------------------------------------- /t/translate/09_strip_encap_quotes.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | 3 | use lib 'tools'; 4 | use Modsec2LRW qw(strip_encap_quotes); 5 | 6 | is( 7 | strip_encap_quotes("'foo'"), 8 | 'foo', 9 | 'single quoted string is stripped', 10 | ); 11 | 12 | is( 13 | strip_encap_quotes('"foo"'), 14 | 'foo', 15 | 'double quoted string is stripped' 16 | ); 17 | 18 | is( 19 | strip_encap_quotes('foo'), 20 | 'foo', 21 | 'unquoted string is not stripped' 22 | ); 23 | 24 | is( 25 | strip_encap_quotes("'foo"), 26 | "'foo", 27 | 'unbalanced quotes are not stripped (left)' 28 | ); 29 | 30 | is( 31 | strip_encap_quotes("foo'"), 32 | "foo'", 33 | 'unbalanced quotes are not stripped (right)' 34 | ); 35 | 36 | is( 37 | strip_encap_quotes("'foo\""), 38 | "'foo\"", 39 | 'mismatched quotes are not stripped (left)' 40 | ); 41 | 42 | is( 43 | strip_encap_quotes("\"foo'"), 44 | "\"foo'", 45 | 'mismatched quotes are not stripped (right)' 46 | ); 47 | 48 | is( 49 | strip_encap_quotes('""foo""'), 50 | '"foo"', 51 | 'only set of quotes is stripped' 52 | ); 53 | 54 | done_testing; 55 | -------------------------------------------------------------------------------- /t/translate/16_figure_phase.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | 3 | use lib 'tools'; 4 | use Modsec2LRW qw(figure_phase); 5 | 6 | is( 7 | figure_phase({ 8 | phase => 1, 9 | }), 10 | 'access', 11 | 'phase determined from phase key (phase 1)' 12 | ); 13 | 14 | is( 15 | figure_phase({ 16 | phase => 2, 17 | }), 18 | 'access', 19 | 'phase determined from phase key (phase 2)' 20 | ); 21 | 22 | is( 23 | figure_phase({ 24 | phase => 3, 25 | }), 26 | 'header_filter', 27 | 'phase determined from phase key (phase 3)' 28 | ); 29 | 30 | is( 31 | figure_phase({ 32 | phase => 4, 33 | }), 34 | 'body_filter', 35 | 'phase determined from phase key (phase 4)' 36 | ); 37 | 38 | is( 39 | figure_phase({ 40 | phase => 5, 41 | }), 42 | 'log', 43 | 'phase determined from phase key (phase 5)' 44 | ); 45 | 46 | is( 47 | figure_phase({ 48 | foo => 'bar', 49 | }), 50 | 'access', 51 | 'phase determined from implicit default' 52 | ); 53 | 54 | is( 55 | figure_phase( 56 | { 57 | phase => 1, 58 | }, 59 | { 60 | phase => 3, 61 | } 62 | ), 63 | 'access', 64 | 'phase determined only from first element' 65 | ); 66 | 67 | done_testing; 68 | -------------------------------------------------------------------------------- /t/translate/17_translate_macro.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | 3 | use lib 'tools'; 4 | use Modsec2LRW qw(translate_macro); 5 | 6 | is( 7 | translate_macro('foo'), 8 | 'foo', 9 | 'translate a string without a marker' 10 | ); 11 | 12 | is( 13 | translate_macro('%{foo}'), 14 | '%{foo}', 15 | 'translate a string wrapped in a marker' 16 | ); 17 | 18 | is( 19 | translate_macro('%{REQUEST_METHOD}'), 20 | '%{METHOD}', 21 | 'translating a valid replacement' 22 | ); 23 | 24 | is( 25 | translate_macro('REQUEST_METHOD'), 26 | 'REQUEST_METHOD', 27 | 'translate an unmarked valid replacement' 28 | ); 29 | 30 | is( 31 | translate_macro('%{ARGS.foo}'), 32 | '%{REQUEST_ARGS.foo}', 33 | 'translate a replacement with a specific element' 34 | ); 35 | 36 | is( 37 | translate_macro('%{RESPONSE_CONTENT_TYPE}'), 38 | '%{RESPONSE_HEADERS.Content-Type}', 39 | 'translate a replacement with an implicit specific element' 40 | ); 41 | 42 | is( 43 | translate_macro('%{REQUEST_METHOD} - %{RESPONSE_STATUS}'), 44 | '%{METHOD} - %{STATUS}', 45 | 'translating two valid replacements' 46 | ); 47 | 48 | is( 49 | translate_macro('%{REQUEST_METHOD} - %{foo}'), 50 | '%{METHOD} - %{foo}', 51 | 'translating one valid and one invalid replacement' 52 | ); 53 | 54 | is( 55 | translate_macro('%{ARGS.foo} - %{RESPONSE_STATUS}'), 56 | '%{REQUEST_ARGS.foo} - %{STATUS}', 57 | 'translating two valid replacements, one with a specific element' 58 | ); 59 | 60 | is( 61 | translate_macro('%{RESPONSE_CONTENT_TYPE} - %{RESPONSE_STATUS}'), 62 | '%{RESPONSE_HEADERS.Content-Type} - %{STATUS}', 63 | 'translating two valid replacements, one with an implicit specific element' 64 | ); 65 | 66 | is( 67 | translate_macro('{foo}'), 68 | '{foo}', 69 | 'translate a string with a malformed marker (1/3)' 70 | ); 71 | 72 | is( 73 | translate_macro('%{foo'), 74 | '%{foo', 75 | 'translate a string with a malformed marker (2/3)' 76 | ); 77 | 78 | is( 79 | translate_macro('%{foo { bar }'), 80 | '%{foo { bar }', 81 | 'translate a string with a malformed marker (3/3)' 82 | ); 83 | 84 | is( 85 | translate_macro('%{args.foo}'), 86 | '%{REQUEST_ARGS.foo}', 87 | "collection names are uc'd as part of the lookup" 88 | ); 89 | 90 | is( 91 | translate_macro('%{foo.bar}'), 92 | '%{foo.bar}', 93 | "collection lookup miss does not result in name being uc'd" 94 | ); 95 | 96 | is( 97 | translate_macro('%{ip.foo_bar}'), 98 | '%{IP.FOO_BAR}', 99 | "specific elements in storage collections are also uc'd" 100 | ); 101 | 102 | done_testing; 103 | -------------------------------------------------------------------------------- /t/translation/01_sanity.lua: -------------------------------------------------------------------------------- 1 | describe("module", function() 2 | it("loads without errors", function() 3 | -- dont print warnings 4 | ngx.log = function() end 5 | 6 | assert.has_no.errors(function() require "resty.waf.translate" end) 7 | end) 8 | end) 9 | -------------------------------------------------------------------------------- /t/translation/02_valid_line.lua: -------------------------------------------------------------------------------- 1 | describe("valid_line", function() 2 | local lib = require "resty.waf.translate" 3 | local v = lib.valid_line 4 | 5 | it("starts with known directives", function() 6 | assert.is_true(v('SecRule ')) 7 | assert.is_true(v('SecAction ')) 8 | assert.is_true(v('SecDefaultAction ')) 9 | assert.is_true(v('SecMarker ')) 10 | end) 11 | 12 | it("starts with unsupported directives", function() 13 | assert.is_false(v('SecRuleScript')) 14 | end) 15 | 16 | it("starts with unknown directives", function() 17 | assert.is_false(v('SecFoo ')) 18 | 19 | local r = require "resty.waf.random" 20 | assert.is_false(v(r.random_bytes(8))) 21 | end) 22 | end) 23 | -------------------------------------------------------------------------------- /t/translation/03_clean_input.lua: -------------------------------------------------------------------------------- 1 | local basic = [[SecRule ARGS "foo" "id:12345,pass"]] 2 | local trim_left = [[ SecRule ARGS "foo" "id:12345,pass"]] 3 | local trim_right = [[SecRule ARGS "foo" "id:12345,pass" ]] 4 | local trim_both = [[ SecRule ARGS "foo" "id:12345,pass" ]] 5 | local ignore_comment = [[#SecRule ARGS "foo" "id:12345,pass"]] 6 | local invalid_directive = [[Secrule ARGS "foo" "id:12345,pass"]] 7 | local multi_line = { 8 | [[SecRule \]], 9 | [[ ARGS \]], 10 | [[ "foo" \]], 11 | [[ "id:12345,pass"]] 12 | } 13 | local multi_line_action = { 14 | [[SecRule \]], 15 | [[ ARGS \]], 16 | [[ "foo" \]], 17 | [[ "id:12345, \]], 18 | [[ phase:1, \]], 19 | [[ block, \]], 20 | [[ setvar:tx.foo=bar, \]], 21 | [[ expirevar:tx.foo=60"]] 22 | } 23 | 24 | describe("clean_input", function() 25 | 26 | local lib = require "resty.waf.translate" 27 | local c = lib.clean_input 28 | 29 | it("takes a basic line", function() 30 | assert.are.same(c({basic}), {'SecRule ARGS "foo" "id:12345,pass"'}) 31 | end) 32 | 33 | it("takes a line to trim left", function() 34 | assert.are.same(c({trim_left}), {'SecRule ARGS "foo" "id:12345,pass"'}) 35 | end) 36 | 37 | it("takes a line to trim right", function() 38 | assert.are.same(c({trim_right}), {'SecRule ARGS "foo" "id:12345,pass"'}) 39 | end) 40 | 41 | it("takes a line to trim left and right", function() 42 | assert.are.same(c({trim_both}), {'SecRule ARGS "foo" "id:12345,pass"'}) 43 | end) 44 | 45 | it("takes a commented-out line", function() 46 | assert.are.same(c({ignore_comment}), {}) 47 | end) 48 | 49 | it("takes a line with an invalid directive", function() 50 | assert.are.same(c({invalid_directive}), {}) 51 | end) 52 | 53 | it("takes a multi-line string", function() 54 | assert.are.same(c(multi_line), {'SecRule ARGS "foo" "id:12345,pass"'}) 55 | end) 56 | 57 | it("takes multiple elements", function() 58 | local input = { basic } 59 | for i = 1, #multi_line do 60 | input[i + 1] = multi_line[i] 61 | end 62 | assert.are.same(c(input), { 63 | 'SecRule ARGS "foo" "id:12345,pass"', 64 | 'SecRule ARGS "foo" "id:12345,pass"' 65 | }) 66 | end) 67 | 68 | it("takes multiple elements with a comment", function() 69 | local input = { basic, ignore_comment } 70 | for i = 1, #multi_line do 71 | input[i + 2] = multi_line[i] 72 | end 73 | assert.are.same(c(input), { 74 | 'SecRule ARGS "foo" "id:12345,pass"', 75 | 'SecRule ARGS "foo" "id:12345,pass"' 76 | }) 77 | end) 78 | 79 | it("takes a mutli-line action directive", function() 80 | assert.are.same(c(multi_line_action), { 81 | 'SecRule ARGS "foo" "id:12345, phase:1, block, setvar:tx.foo=bar, expirevar:tx.foo=60"' 82 | }) 83 | end) 84 | end) 85 | -------------------------------------------------------------------------------- /t/translation/04_tokenize.lua: -------------------------------------------------------------------------------- 1 | describe("tokenize", function() 2 | local lib = require "resty.waf.translate" 3 | local t = lib.tokenize 4 | 5 | it("takes a single token", function() 6 | assert.are.same(t('foo'), {'foo'}) 7 | end) 8 | 9 | it("takes two tokens", function() 10 | assert.are.same(t('foo bar'), {'foo', 'bar'}) 11 | end) 12 | 13 | it("takes a single quote-wrapped token", function() 14 | assert.are.same(t('"foo"'), {'foo'}) 15 | end) 16 | 17 | it("takes two quote-wrapped tokens", function() 18 | assert.are.same(t('"foo" "bar"'), {'foo', 'bar'}) 19 | end) 20 | 21 | it("takes two tokens, first quote-wrapped", function() 22 | assert.are.same(t('"foo" bar'), {'foo', 'bar'}) 23 | end) 24 | 25 | it("takes two tokens, second quote-wrapped", function() 26 | assert.are.same(t('foo "bar"'), {'foo', 'bar'}) 27 | end) 28 | 29 | it("takes a quote-wrapped token with a single escaped quote", function() 30 | assert.are.same(t([["foo \"bar"]]), {[[foo "bar]]}) 31 | end) 32 | 33 | it("takes a quote-wrapped token with two escaped quotes", function() 34 | assert.are.same(t([["foo \"bar\""]]), {[[foo "bar"]]}) 35 | end) 36 | 37 | it("takes a quote-wrapped token with a single escaped quote, " .. 38 | "then an unquoted token", function() 39 | assert.are.same(t([["foo \"" bar]]), {[[foo "]], [[bar]]}) 40 | end) 41 | 42 | it("takes an unquoted token, then a quote-wrapped token " .. 43 | "with a single escaped quote", function() 44 | assert.are.same(t([[foo "bar \""]]), {[[foo]], [[bar "]]}) 45 | end) 46 | 47 | it("takes four tokens, the last of which is quote-wrapped", function() 48 | assert.are.same(t([[foo bar baz "bat"]]), { 49 | [[foo]], 50 | [[bar]], 51 | [[baz]], 52 | [[bat]], 53 | }) 54 | end) 55 | 56 | it("takes four tokens, the last of which is quote-wrapped and " .. 57 | "escaped with single quotes", function() 58 | assert.are.same(t([[foo bar baz "bat,qux:'frob foo'"]]), { 59 | [[foo]], 60 | [[bar]], 61 | [[baz]], 62 | [[bat,qux:'frob foo']], 63 | }) 64 | end) 65 | 66 | it("takes four tokens, two of which are quote-wrapped, and one with " .. 67 | "escaped quotes", function() 68 | assert.are.same(t([[foo bar "baz \"qux\"" "bat"]]), { 69 | [[foo]], 70 | [[bar]], 71 | [[baz "qux"]], 72 | [[bat]], 73 | }) 74 | end) 75 | 76 | end) 77 | 78 | -------------------------------------------------------------------------------- /t/translation/05_parse_tokens.lua: -------------------------------------------------------------------------------- 1 | local secrule = 'SecRule' 2 | local secaction = 'SecAction' 3 | local args = 'ARGS' 4 | local operator = 'foo' 5 | local actions = "block,id:12345,msg:'hello world'" 6 | 7 | describe("parse_token", function() 8 | local lib = require "resty.waf.translate" 9 | local p = lib.parse_tokens 10 | 11 | it("lives ok with four tokens", function() 12 | stub(lib, 'parse_vars') 13 | stub(lib, 'parse_operator') 14 | stub(lib, 'parse_actions') 15 | 16 | assert.has_no_errors(function() 17 | p({secrule, args, operator, actions}) 18 | end) 19 | assert.stub(lib.parse_vars).was.called(1) 20 | assert.stub(lib.parse_operator).was.called(1) 21 | assert.stub(lib.parse_actions).was.called(1) 22 | end) 23 | 24 | it("lives ok with two tokens", function() 25 | stub(lib, 'parse_vars') 26 | stub(lib, 'parse_operator') 27 | stub(lib, 'parse_actions') 28 | 29 | assert.has_no_errors(function() p({secaction, actions}) end) 30 | assert.stub(lib.parse_vars).was.not_called() 31 | assert.stub(lib.parse_operator).was.not_called() 32 | assert.stub(lib.parse_actions).was.called(1) 33 | end) 34 | 35 | it("dies with four tokens and SecAction", function() 36 | assert.has_error(function() 37 | p({secaction, args, operator, actions}) 38 | end) 39 | end) 40 | 41 | it("properly forms an entry from SecRule", function() 42 | stub(lib, 'parse_vars') 43 | stub(lib, 'parse_operator') 44 | stub(lib, 'parse_actions') 45 | 46 | local entry = p({secrule, args, operator, actions}) 47 | 48 | assert.is.same(entry.original, 49 | "SecRule ARGS foo block,id:12345,msg:'hello world'") 50 | assert.is.same(entry.directive, 'SecRule') 51 | assert.stub(lib.parse_vars).was.called_with("ARGS") 52 | assert.stub(lib.parse_operator).was.called_with("foo") 53 | assert.stub(lib.parse_actions).was. 54 | called_with("block,id:12345,msg:'hello world'") 55 | end) 56 | 57 | it("properly forms an entry from SecRule with no actions", function() 58 | stub(lib, 'parse_vars') 59 | stub(lib, 'parse_operator') 60 | stub(lib, 'parse_actions') 61 | 62 | local entry = p({secrule, args, operator}) 63 | 64 | assert.is.same(entry.original, "SecRule ARGS foo") 65 | assert.is.same(entry.directive, 'SecRule') 66 | assert.stub(lib.parse_vars).was.called_with("ARGS") 67 | assert.stub(lib.parse_operator).was.called_with("foo") 68 | assert.stub(lib.parse_actions).was.not_called() 69 | end) 70 | 71 | it("properly forms an entry from SecAction", function() 72 | stub(lib, 'parse_vars') 73 | stub(lib, 'parse_operator') 74 | stub(lib, 'parse_actions') 75 | 76 | local entry = p({secaction, actions}) 77 | 78 | assert.is.same(entry.original, 79 | "SecAction block,id:12345,msg:'hello world'") 80 | assert.is.same(entry.directive, 'SecAction') 81 | assert.stub(lib.parse_actions).was. 82 | called_with("block,id:12345,msg:'hello world'") 83 | end) 84 | end) 85 | -------------------------------------------------------------------------------- /t/translation/07_parse_operator.lua: -------------------------------------------------------------------------------- 1 | describe("parse_operator", function() 2 | local lib = require "resty.waf.translate" 3 | local p = lib.parse_operator 4 | 5 | it("has a default operator", function() 6 | assert.is.same(p('foo'), { 7 | operator = 'rx', 8 | pattern = 'foo' 9 | }) 10 | end) 11 | 12 | it("parses an explicitly defined operator", function() 13 | assert.is.same(p('@rx foo'), { 14 | operator = 'rx', 15 | pattern = 'foo' 16 | }) 17 | end) 18 | 19 | it("parses an explicitly defined (non default) operator", function() 20 | assert.is.same(p('@streq foo'), { 21 | operator = 'streq', 22 | pattern = 'foo' 23 | }) 24 | end) 25 | 26 | it("parses a numeric pattern", function() 27 | assert.is.same(p('@eq 5'), { 28 | operator = 'eq', 29 | pattern = '5' 30 | }) 31 | end) 32 | 33 | it("parses a negative operator", function() 34 | assert.is.same(p('!@rx foo'), { 35 | operator = 'rx', 36 | pattern = 'foo', 37 | negated = '!' 38 | }) 39 | end) 40 | 41 | it("parses an operator with no pattern", function() 42 | assert.is.same(p('@detectSQLi'), { 43 | operator = 'detectSQLi', 44 | pattern = '' 45 | }) 46 | end) 47 | 48 | it("parses a negative operator with no pattern", function() 49 | assert.is.same(p('!@detectSQLi'), { 50 | operator = 'detectSQLi', 51 | pattern = '', 52 | negated = '!' 53 | }) 54 | end) 55 | 56 | it("parses negation with the implicit operator", function() 57 | assert.is.same(p('!foo'), { 58 | operator = 'rx', 59 | pattern = 'foo', 60 | negated = '!' 61 | }) 62 | end) 63 | 64 | it("parses negation with a proceeding space char", function() 65 | assert.is.same(p('! foo'), { 66 | operator = 'rx', 67 | pattern = ' foo', 68 | negated = '!' 69 | }) 70 | end) 71 | 72 | it("parses negation with a proceeding space char and an incorrect ".. 73 | "operator assignment", function() 74 | assert.is.same(p('! @rx foo'), { 75 | operator = 'rx', 76 | pattern = ' @rx foo', 77 | negated = '!' 78 | }) 79 | end) 80 | 81 | it("parses an operator with no pattern, and space following negation", 82 | function() 83 | assert.is.same(p('! @detectSQLi'), { 84 | operator = 'rx', 85 | pattern = ' @detectSQLi', 86 | negated = '!' 87 | }) 88 | end) 89 | 90 | it("errors when encountering a pattern that does not match its regex", 91 | function() 92 | pending("need to figure out mocking ngx.re") 93 | 94 | assert.has.error(function() p('foo') end) 95 | end) 96 | end) 97 | -------------------------------------------------------------------------------- /t/translation/09_strip_encap_quotes.lua: -------------------------------------------------------------------------------- 1 | describe("strip_encap_quotes", function() 2 | local lib = require "resty.waf.translate" 3 | local s = lib.strip_encap_quotes 4 | 5 | it("strips quotes from a single-quoted string", function() 6 | assert.is.same(s("'foo'"), "foo") 7 | end) 8 | 9 | it("strips quotes from a double-quoted string", function() 10 | assert.is.same(s('"foo"'), "foo") 11 | end) 12 | 13 | it("leaves an unquoted string as-is", function() 14 | assert.is.same(s("foo"), "foo") 15 | end) 16 | 17 | it("does not strip left-unbalanced quotes", function() 18 | assert.is.same(s("'foo"), "'foo") 19 | end) 20 | 21 | it("does not strip right-unbalanced quotes", function() 22 | assert.is.same(s("foo'"), "foo'") 23 | end) 24 | 25 | it("does not strip left-mismatched quotes", function() 26 | assert.is.same(s([['foo"]]), [['foo"]]) 27 | end) 28 | 29 | it("does not strip right-mismatched quotes", function() 30 | assert.is.same(s([["foo']]), [["foo']]) 31 | end) 32 | 33 | it("only strips one set of quotes", function() 34 | assert.is.same(s([[""foo""]]), [["foo"]]) 35 | end) 36 | 37 | end) 38 | -------------------------------------------------------------------------------- /t/translation/16_figure_phase.lua: -------------------------------------------------------------------------------- 1 | describe("figure_phase", function() 2 | local lib = require "resty.waf.translate" 3 | local f = lib.figure_phase 4 | 5 | for i = 1, #lib.phase_lookup do 6 | local p = lib.phase_lookup[i] 7 | 8 | it("translates a phase determined from phase key " .. p, function() 9 | assert.is.same(f({{phase = i}}), lib.phase_lookup[i]) 10 | end) 11 | 12 | it("translates a phase determined from phase key " .. p .. 13 | " using only the first rule", function() 14 | assert.is.same(f({{phase = i}, {phase = 2}}), lib.phase_lookup[i]) 15 | end) 16 | 17 | end 18 | 19 | do 20 | local phase_names = { 21 | request = 'access', 22 | response = 'body_filter', 23 | logging = 'log' 24 | } 25 | 26 | for k, v in pairs(phase_names) do 27 | it("translates a phase determined from phase key " .. k, function() 28 | assert.is.same(f({{phase = k}}), lib.phase_lookup[k]) 29 | assert.is.same(f({{phase = k}}), v) 30 | end) 31 | 32 | it("translates a phase determined from phase key " .. k .. 33 | " using only the first rule", function() 34 | assert.is.same(f({{phase = k}, {phase = 2}}), lib.phase_lookup[k]) 35 | assert.is.same(f({{phase = k}, {phase = 2}}), v) 36 | end) 37 | end 38 | end 39 | 40 | it("translates a phase determined from implicit default", function() 41 | assert.is.same(f({{}}), 'access') 42 | end) 43 | 44 | it("removes the phase key from the translation", function() 45 | local translation = { 46 | { 47 | phase = 1, 48 | foo = 'bar' 49 | } 50 | } 51 | 52 | f(translation) 53 | assert.is_nil(translation.phase) 54 | end) 55 | end) 56 | -------------------------------------------------------------------------------- /t/translation/17_translate_macro.lua: -------------------------------------------------------------------------------- 1 | describe("translate_macro", function() 2 | local lib = require "resty.waf.translate" 3 | local t = lib.translate_macro 4 | 5 | it("does not modify a string without a marker", function() 6 | assert.is.same(t('foo'), 'foo') 7 | end) 8 | 9 | it("does not modify a string wrapped in a marker", function() 10 | assert.is.same(t('%{foo}'), '%{foo}') 11 | end) 12 | 13 | it("translates a string containing a var", function() 14 | assert.is.same(t('%{REQUEST_METHOD}'), '%{METHOD}') 15 | end) 16 | 17 | it("does not modify a var string without a marker", function() 18 | assert.is.same(t('REQUEST_METHOD'), 'REQUEST_METHOD') 19 | end) 20 | 21 | it("translates a string containing a var and a specific", function() 22 | assert.is.same(t('%{ARGS.foo}'), '%{REQUEST_ARGS.foo}') 23 | end) 24 | 25 | it("translates a string containing a var and an implicit specific", 26 | function() 27 | assert.is.same(t('%{RESPONSE_CONTENT_TYPE}'), 28 | '%{RESPONSE_HEADERS.Content-Type}') 29 | end) 30 | 31 | it("translates a string containing two marked vars", function() 32 | assert.is.same(t('%{REQUEST_METHOD} - %{RESPONSE_STATUS}'), 33 | '%{METHOD} - %{STATUS}') 34 | end) 35 | 36 | it("translates a string containing two marked vars " .. 37 | "one of which is not a valid var", function() 38 | assert.is.same(t('%{REQUEST_METHOD} - %{foo}'), 39 | '%{METHOD} - %{foo}') 40 | end) 41 | 42 | it("translates a string containing two marked vars " .. 43 | "one of which has a specific element", function() 44 | assert.is.same(t('%{ARGS.foo} - %{RESPONSE_STATUS}'), 45 | '%{REQUEST_ARGS.foo} - %{STATUS}') 46 | end) 47 | 48 | it("does not modify a string wrapped in a malformed marker (1/3)", 49 | function() 50 | assert.is.same(t('{foo}'), '{foo}') 51 | end) 52 | 53 | it("does not modify a string wrapped in a malformed marker (2/3)", 54 | function() 55 | assert.is.same(t('%{foo'), '%{foo') 56 | end) 57 | 58 | it("does not modify a string wrapped in a malformed marker (3/3)", 59 | function() 60 | assert.is.same(t('%{foo { bar}'), '%{foo { bar}') 61 | end) 62 | 63 | it("uc's the collection name as part of translation", function() 64 | assert.is.same(t('%{args.foo}'), '%{REQUEST_ARGS.foo}') 65 | end) 66 | 67 | it("does not uc the collection name as part of missed lookup", function() 68 | assert.is.same(t('%{foo.bar}'), '%{foo.bar}') 69 | end) 70 | 71 | it("uc's the collection name and element as part of translation " .. 72 | "of storage collections", function() 73 | assert.is.same(t('%{ip.foo}'), '%{IP.FOO}') 74 | end) 75 | 76 | end) 77 | -------------------------------------------------------------------------------- /t/unit/actions/disruptive/01_accept.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: ACCEPT exits the phase with ngx.OK 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | 26 | actions.disruptive_lookup["ACCEPT"]({ _debug = true, _debug_log_level = ngx.INFO, _mode = "ACTIVE" }, {}) 27 | 28 | ngx.log(ngx.INFO, "We should not see this") 29 | } 30 | 31 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 32 | } 33 | --- request 34 | GET /t 35 | --- error_code: 200 36 | --- error_log 37 | Rule action was ACCEPT, so ending this phase with ngx.OK 38 | --- no_error_log 39 | [error] 40 | We should not see this 41 | 42 | === TEST 2: ACCEPT does not exit the phase when mode is not ACTIVE 43 | --- http_config eval: $::HttpConfig 44 | --- config 45 | location /t { 46 | access_by_lua_block { 47 | local actions = require "resty.waf.actions" 48 | 49 | actions.disruptive_lookup["ACCEPT"]({ _debug = true, _debug_log_level = ngx.INFO, _mode = "SIMULATE" }, {}) 50 | 51 | ngx.log(ngx.INFO, "We should see this") 52 | } 53 | 54 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 55 | } 56 | --- request 57 | GET /t 58 | --- error_code: 200 59 | --- error_log 60 | Rule action was ACCEPT, so ending this phase with ngx.OK 61 | We should see this 62 | --- no_error_log 63 | [error] 64 | -------------------------------------------------------------------------------- /t/unit/actions/disruptive/02_chain.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: CHAIN logs and does not exit 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | 26 | actions.disruptive_lookup["CHAIN"]({ _debug = true, _debug_log_level = ngx.INFO, _mode = "ACTIVE" }, {}) 27 | 28 | ngx.log(ngx.INFO, "We should see this") 29 | } 30 | 31 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 32 | } 33 | --- request 34 | GET /t 35 | --- error_code: 200 36 | --- error_log 37 | Chaining (pre-processed) 38 | We should see this 39 | --- no_error_log 40 | [error] 41 | 42 | -------------------------------------------------------------------------------- /t/unit/actions/disruptive/03_score.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: SCORE logs and does not exit 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | 26 | actions.disruptive_lookup["SCORE"]({ _debug = true, _debug_log_level = ngx.INFO, _mode = "ACTIVE" }, {}) 27 | 28 | ngx.log(ngx.INFO, "We should see this") 29 | } 30 | 31 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 32 | } 33 | --- request 34 | GET /t 35 | --- error_code: 200 36 | --- error_log 37 | Score isn't a thing anymore, see TX.anomaly_score 38 | We should see this 39 | --- no_error_log 40 | [error] 41 | 42 | -------------------------------------------------------------------------------- /t/unit/actions/disruptive/04_deny.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: DENY exits the phase with waf._deny_status 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | 26 | actions.disruptive_lookup["DENY"]({ _debug = true, _debug_log_level = ngx.INFO, _mode = "ACTIVE", _deny_status = 403 }, {}) 27 | 28 | ngx.log(ngx.INFO, "We should not see this") 29 | } 30 | 31 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 32 | } 33 | --- request 34 | GET /t 35 | --- error_code: 403 36 | --- error_log 37 | Rule action was DENY, so telling nginx to quit 38 | --- no_error_log 39 | [error] 40 | We should not see this 41 | 42 | === TEST 2: DENY does not exit the phase when mode is not ACTIVE 43 | --- http_config eval: $::HttpConfig 44 | --- config 45 | location /t { 46 | access_by_lua_block { 47 | local actions = require "resty.waf.actions" 48 | 49 | actions.disruptive_lookup["DENY"]({ _debug = true, _debug_log_level = ngx.INFO, _mode = "SIMULATE", _deny_status = 403 }, {}) 50 | 51 | ngx.log(ngx.INFO, "We should see this") 52 | } 53 | 54 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 55 | } 56 | --- request 57 | GET /t 58 | --- error_code: 200 59 | --- error_log 60 | Rule action was DENY, so telling nginx to quit 61 | We should see this 62 | --- no_error_log 63 | [error] 64 | -------------------------------------------------------------------------------- /t/unit/actions/disruptive/05_ignore.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: IGNORE logs and does not exit 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | 26 | actions.disruptive_lookup["IGNORE"]({ _debug = true, _debug_log_level = ngx.INFO, _mode = "ACTIVE" }, {}) 27 | 28 | ngx.log(ngx.INFO, "We should see this") 29 | } 30 | 31 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 32 | } 33 | --- request 34 | GET /t 35 | --- error_code: 200 36 | --- error_log 37 | Ignoring rule for now 38 | We should see this 39 | --- no_error_log 40 | [error] 41 | 42 | -------------------------------------------------------------------------------- /t/unit/actions/disruptive/06_alter_actions.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: ACCEPT is not an alter action 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | local alter_actions = actions.alter_actions 26 | for action in pairs(alter_actions) do 27 | ngx.say(action) 28 | end 29 | } 30 | 31 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 32 | } 33 | --- request 34 | GET /t 35 | --- error_code: 200 36 | --- response_body_unlike 37 | ACCEPT 38 | --- no_error_log 39 | [error] 40 | 41 | === TEST 2: DENY is an alter action 42 | --- request 43 | GET /t 44 | --- error_code: 200 45 | --- response_body_like 46 | DENY 47 | --- no_error_log 48 | [error] 49 | 50 | === TEST 3: IGNORE is not an alter action 51 | --- request 52 | GET /t 53 | --- error_code: 200 54 | --- response_body_unlike 55 | IGNORE 56 | --- no_error_log 57 | [error] 58 | 59 | === TEST 4: CHAIN is not an alter action 60 | --- request 61 | GET /t 62 | --- error_code: 200 63 | --- response_body_unlike 64 | CHAIN 65 | --- no_error_log 66 | [error] 67 | 68 | === TEST 5: SCORE is not an alter action 69 | --- request 70 | GET /t 71 | --- error_code: 200 72 | --- response_body_unlike 73 | SCORE 74 | --- no_error_log 75 | [error] 76 | 77 | === TEST 5: DROP is an alter action 78 | --- request 79 | GET /t 80 | --- error_code: 200 81 | --- response_body_like 82 | DROP 83 | --- no_error_log 84 | [error] 85 | -------------------------------------------------------------------------------- /t/unit/actions/disruptive/07_drop.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: DROP exits the phase with ngx.HTTP_CLOSE 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | 26 | actions.disruptive_lookup["DROP"]({ _debug = true, _debug_log_level = ngx.INFO, _mode = "ACTIVE" }, {}) 27 | 28 | ngx.log(ngx.INFO, "We should not see this") 29 | } 30 | 31 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 32 | } 33 | --- request 34 | GET /t 35 | --- error_code: 36 | --- error_log 37 | Rule action was DROP, ending eith ngx.HTTP_CLOSE 38 | --- no_error_log 39 | [error] 40 | We should not see this 41 | 42 | === TEST 2: DROP does not exit the phase when mode is not ACTIVE 43 | --- http_config eval: $::HttpConfig 44 | --- config 45 | location /t { 46 | access_by_lua_block { 47 | local actions = require "resty.waf.actions" 48 | 49 | actions.disruptive_lookup["DROP"]({ _debug = true, _debug_log_level = ngx.INFO, _mode = "SIMULATE" }, {}) 50 | 51 | ngx.log(ngx.INFO, "We should see this") 52 | } 53 | 54 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 55 | } 56 | --- request 57 | GET /t 58 | --- error_code: 200 59 | --- error_log 60 | Rule action was DROP, ending eith ngx.HTTP_CLOSE 61 | We should see this 62 | --- no_error_log 63 | [error] 64 | -------------------------------------------------------------------------------- /t/unit/actions/nondisruptive/01_deletevar.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: deletevar calls storage.delete_var 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | local storage = require "resty.waf.storage" 26 | 27 | storage.delete_var = function(waf, ctx, data) 28 | ngx.log(ngx.DEBUG, "Called storage.delete_var with data.value " .. data.value) 29 | end 30 | 31 | actions.nondisruptive_lookup["deletevar"]({}, { value = "foo" }, {}, {}) 32 | } 33 | 34 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 35 | } 36 | --- request 37 | GET /t 38 | --- error_code: 200 39 | --- error_log 40 | Called storage.delete_var with data.value foo 41 | --- no_error_log 42 | [error] 43 | 44 | -------------------------------------------------------------------------------- /t/unit/actions/nondisruptive/02_expirevar.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: expirevar calls storage.expire_var 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | local storage = require "resty.waf.storage" 26 | local util = require "resty.waf.util" 27 | 28 | util.parse_dynamic_value = function(waf, key, collections) 29 | return collections[key] 30 | end 31 | 32 | storage.expire_var = function(waf, ctx, data, time) 33 | ngx.log(ngx.DEBUG, "Called storage.expire_var with data.value " .. data.value .. " and time " .. time) 34 | end 35 | 36 | actions.nondisruptive_lookup["expirevar"]({}, { value = "foo", time = "foo" }, {}, { foo = 5 }) 37 | } 38 | 39 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 40 | } 41 | --- request 42 | GET /t 43 | --- error_code: 200 44 | --- error_log 45 | Called storage.expire_var with data.value foo and time 5 46 | --- no_error_log 47 | [error] 48 | 49 | -------------------------------------------------------------------------------- /t/unit/actions/nondisruptive/03_initcol.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: initcol calls storage.initialize 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | local storage = require "resty.waf.storage" 26 | local util = require "resty.waf.util" 27 | 28 | util.parse_dynamic_value = function(waf, key, collections) 29 | return collections[key] 30 | end 31 | 32 | storage.initialize = function(waf, storage, parsed) 33 | ngx.log(ngx.DEBUG, "Called storage.initialize with parsed " .. parsed) 34 | end 35 | 36 | actions.nondisruptive_lookup["initcol"]( 37 | { _debug = true, _debug_log_level = ngx.DEBUG }, 38 | { col = "IP", value = "IP" }, 39 | { col_lookup = {}, storage = {} }, 40 | { IP = "127.0.0.1" } 41 | ) 42 | } 43 | 44 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 45 | } 46 | --- request 47 | GET /t 48 | --- error_code: 200 49 | --- error_log 50 | Initializing IP as 127.0.0.1 51 | Called storage.initialize with parsed 127.0.0.1 52 | --- no_error_log 53 | [error] 54 | 55 | -------------------------------------------------------------------------------- /t/unit/actions/nondisruptive/04_setvar.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: setvar calls storage.set_var 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | local storage = require "resty.waf.storage" 26 | local util = require "resty.waf.util" 27 | 28 | util.parse_dynamic_value = function(waf, key, collections) 29 | return collections[key] 30 | end 31 | 32 | storage.set_var = function(waf, ctx, data, value) 33 | ngx.log(ngx.DEBUG, "Called storage.set_var with data.value " .. data.value .. " and value " .. value) 34 | end 35 | 36 | actions.nondisruptive_lookup["setvar"]({}, { value = "foo", time = "foo" }, {}, { foo = "dynamic-foo" }) 37 | } 38 | 39 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 40 | } 41 | --- request 42 | GET /t 43 | --- error_code: 200 44 | --- error_log 45 | Called storage.set_var with data.value foo and value dynamic-foo 46 | --- no_error_log 47 | [error] 48 | 49 | === TEST 2: setvar calls storage.set_var (macro'd key) 50 | --- http_config eval: $::HttpConfig 51 | --- config 52 | location /t { 53 | access_by_lua_block { 54 | local actions = require "resty.waf.actions" 55 | local storage = require "resty.waf.storage" 56 | local util = require "resty.waf.util" 57 | 58 | util.parse_dynamic_value = function(waf, key, collections) 59 | return collections[key] 60 | end 61 | 62 | storage.set_var = function(waf, ctx, data, value) 63 | ngx.log(ngx.DEBUG, "Called storage.set_var with data.key " .. data.key) 64 | end 65 | 66 | actions.nondisruptive_lookup["setvar"]({}, { value = "foo", key = "bar" }, {}, { foo = "dynamic-foo", bar = "dynamic-bar" }) 67 | } 68 | 69 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 70 | } 71 | --- request 72 | GET /t 73 | --- error_code: 200 74 | --- error_log 75 | Called storage.set_var with data.key dynamic-bar 76 | --- no_error_log 77 | [error] 78 | 79 | -------------------------------------------------------------------------------- /t/unit/actions/nondisruptive/05_sleep.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: sleep calls ngx.sleep 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | 26 | ngx.sleep = function(time) 27 | ngx.say("Slept for " .. time .. " seconds") 28 | end 29 | 30 | actions.nondisruptive_lookup["sleep"]({ _debug = true, _debug_log_level = ngx.INFO }, 5) 31 | } 32 | } 33 | --- request 34 | GET /t 35 | --- error_code: 200 36 | --- response_body 37 | Slept for 5 seconds 38 | --- error_log 39 | Sleeping for 5 40 | --- no_error_log 41 | [error] 42 | 43 | -------------------------------------------------------------------------------- /t/unit/actions/nondisruptive/06_status.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: status sets ctx.rule_status 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | 26 | local ctx = {} 27 | 28 | local new_status = ngx.HTTP_NOT_FOUND 29 | 30 | actions.nondisruptive_lookup["status"]( 31 | { _debug = true, _debug_log_level = ngx.INFO, _deny_status = ngx.HTTP_FORBIDDEN }, 32 | new_status, 33 | ctx 34 | ) 35 | 36 | ngx.say(ctx.rule_status) 37 | } 38 | } 39 | --- request 40 | GET /t 41 | --- error_code: 200 42 | --- response_body 43 | 404 44 | --- error_log 45 | Overriding status from 403 to 404 46 | --- no_error_log 47 | [error] 48 | 49 | -------------------------------------------------------------------------------- /t/unit/actions/nondisruptive/07_rule_remove_id.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: rule_remove_id adds to waf._ignore_rule 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | 26 | local waf = { _debug = true, _debug_log_level = ngx.INFO, _ignore_rule = {} } 27 | 28 | actions.nondisruptive_lookup["rule_remove_id"]( 29 | waf, 30 | 12345 31 | ) 32 | 33 | for k, v in pairs(waf._ignore_rule) do 34 | ngx.say(k) 35 | end 36 | } 37 | } 38 | --- request 39 | GET /t 40 | --- error_code: 200 41 | --- response_body 42 | 12345 43 | --- error_log 44 | Runtime ignoring rule 12345 45 | --- no_error_log 46 | [error] 47 | 48 | === TEST 2: rule_remove_id adds to waf._ignore_rule with config-time ignore 49 | --- http_config eval: $::HttpConfig 50 | --- config 51 | location /t { 52 | content_by_lua_block { 53 | local actions = require "resty.waf.actions" 54 | 55 | local waf = { _debug = true, _debug_log_level = ngx.INFO, _ignore_rule = { [12344] = true } } 56 | 57 | actions.nondisruptive_lookup["rule_remove_id"]( 58 | waf, 59 | 12345 60 | ) 61 | 62 | for k, v in pairs(waf._ignore_rule) do 63 | ngx.say(k) 64 | end 65 | } 66 | } 67 | --- request 68 | GET /t 69 | --- error_code: 200 70 | --- response_body 71 | 12345 72 | 12344 73 | --- error_log 74 | Runtime ignoring rule 12345 75 | --- no_error_log 76 | [error] 77 | 78 | -------------------------------------------------------------------------------- /t/unit/actions/nondisruptive/08_rule_remove_by_meta.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 5 * blocks() + 3; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: rule_remove_by_meta adds to waf._ignore_rule 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | 26 | local meta_exception = { 27 | meta_ids = {} 28 | } 29 | 30 | meta_exception.meta_ids[12345] = { 1, 2, 3 } 31 | 32 | local waf = { _debug = true, _debug_log_level = ngx.INFO, 33 | _ignore_rule = {}, _meta_exception = meta_exception } 34 | 35 | actions.nondisruptive_lookup["rule_remove_by_meta"]( 36 | waf, 37 | nil, 38 | { id = 12345 } 39 | ) 40 | 41 | for k, v in pairs(waf._ignore_rule) do 42 | ngx.say(k) 43 | end 44 | } 45 | } 46 | --- request 47 | GET /t 48 | --- error_code: 200 49 | --- response_body 50 | 1 51 | 2 52 | 3 53 | --- error_log 54 | Runtime ignoring rules by meta 55 | --- no_error_log 56 | [error] 57 | 58 | === TEST 2: rule_remove_by_meta adds to waf._ignore_rule with config-time ignore 59 | --- http_config eval: $::HttpConfig 60 | --- config 61 | location /t { 62 | content_by_lua_block { 63 | local actions = require "resty.waf.actions" 64 | 65 | local meta_exception = { 66 | meta_ids = {} 67 | } 68 | 69 | meta_exception.meta_ids[12345] = { 1, 2, 3 } 70 | 71 | local waf = { _debug = true, _debug_log_level = ngx.INFO, 72 | _ignore_rule = { [12344] = true }, _meta_exception = meta_exception } 73 | 74 | actions.nondisruptive_lookup["rule_remove_by_meta"]( 75 | waf, 76 | nil, 77 | { id = 12345 } 78 | ) 79 | 80 | for k, v in pairs(waf._ignore_rule) do 81 | ngx.say(k) 82 | end 83 | } 84 | } 85 | --- request 86 | GET /t 87 | --- error_code: 200 88 | --- response_body 89 | 1 90 | 2 91 | 3 92 | 12344 93 | --- error_log 94 | Runtime ignoring rules by meta 95 | Runtime ignoring rule 1 96 | Runtime ignoring rule 2 97 | Runtime ignoring rule 3 98 | --- no_error_log 99 | [error] 100 | 101 | -------------------------------------------------------------------------------- /t/unit/actions/nondisruptive/09_mode.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 4 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: status sets ctx.rule_status 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local actions = require "resty.waf.actions" 25 | 26 | local waf = { _debug = true, _debug_log_level = ngx.INFO, _mode = 'ACTIVE' } 27 | ngx.say(waf._mode) 28 | 29 | local new_mode = 'SIMULATE' 30 | 31 | actions.nondisruptive_lookup["mode_update"]( 32 | waf, 33 | new_mode 34 | ) 35 | 36 | ngx.say(waf._mode) 37 | } 38 | } 39 | --- request 40 | GET /t 41 | --- error_code: 200 42 | --- response_body 43 | ACTIVE 44 | SIMULATE 45 | --- error_log 46 | Overriding mode from ACTIVE to SIMULATE 47 | --- no_error_log 48 | [error] 49 | 50 | -------------------------------------------------------------------------------- /t/unit/collections/01_remote_addr.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() ; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: REMOTE_ADDR collections variable 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:exec() 28 | } 29 | 30 | content_by_lua_block { 31 | local collections = ngx.ctx.lua_resty_waf.collections 32 | 33 | ngx.say(collections.REMOTE_ADDR) 34 | } 35 | } 36 | --- request 37 | GET /t 38 | --- error_code: 200 39 | --- response_body 40 | 127.0.0.1 41 | --- no_error_log 42 | [error] 43 | 44 | === TEST 2: REMOTE_ADDR collections variable (type verification) 45 | --- http_config eval: $::HttpConfig 46 | --- config 47 | location /t { 48 | access_by_lua_block { 49 | local lua_resty_waf = require "resty.waf" 50 | local waf = lua_resty_waf:new() 51 | 52 | waf:exec() 53 | } 54 | 55 | content_by_lua_block { 56 | local collections = ngx.ctx.lua_resty_waf.collections 57 | 58 | ngx.say(type(collections.REMOTE_ADDR)) 59 | } 60 | } 61 | --- request 62 | GET /t 63 | --- error_code: 200 64 | --- response_body 65 | string 66 | --- no_error_log 67 | [error] 68 | 69 | -------------------------------------------------------------------------------- /t/unit/collections/02_http_version.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() ; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: HTTP_VERSION collections variable 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:exec() 28 | } 29 | 30 | content_by_lua_block { 31 | local collections = ngx.ctx.lua_resty_waf.collections 32 | 33 | ngx.say(collections.HTTP_VERSION) 34 | } 35 | } 36 | --- request 37 | GET /t 38 | --- error_code: 200 39 | --- response_body 40 | 1.1 41 | --- no_error_log 42 | [error] 43 | 44 | === TEST 2: HTTP_VERSION collections variable (HTTP/1.0) 45 | --- http_config eval: $::HttpConfig 46 | --- config 47 | location /t { 48 | access_by_lua_block { 49 | local lua_resty_waf = require "resty.waf" 50 | local waf = lua_resty_waf:new() 51 | 52 | waf:exec() 53 | } 54 | 55 | content_by_lua_block { 56 | local collections = ngx.ctx.lua_resty_waf.collections 57 | 58 | ngx.say(collections.HTTP_VERSION) 59 | } 60 | } 61 | --- request 62 | GET /t HTTP/1.0 63 | --- error_code: 200 64 | --- response_body 65 | 1 66 | --- no_error_log 67 | [error] 68 | 69 | === TEST 3: HTTP_VERSION collections variable (type verification) 70 | --- http_config eval: $::HttpConfig 71 | --- config 72 | location /t { 73 | access_by_lua_block { 74 | local lua_resty_waf = require "resty.waf" 75 | local waf = lua_resty_waf:new() 76 | 77 | waf:exec() 78 | } 79 | 80 | content_by_lua_block { 81 | local collections = ngx.ctx.lua_resty_waf.collections 82 | 83 | ngx.say(type(collections.HTTP_VERSION)) 84 | } 85 | } 86 | --- request 87 | GET /t 88 | --- error_code: 200 89 | --- response_body 90 | number 91 | --- no_error_log 92 | [error] 93 | 94 | -------------------------------------------------------------------------------- /t/unit/collections/03_method.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() ; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: METHOD collections variable (GET) 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:exec() 28 | } 29 | 30 | content_by_lua_block { 31 | local collections = ngx.ctx.lua_resty_waf.collections 32 | 33 | ngx.say(collections.METHOD) 34 | } 35 | } 36 | --- request 37 | GET /t 38 | --- error_code: 200 39 | --- response_body 40 | GET 41 | --- no_error_log 42 | [error] 43 | 44 | === TEST 2: METHOD collections variable (POST) 45 | --- http_config eval: $::HttpConfig 46 | --- config 47 | location /t { 48 | access_by_lua_block { 49 | local lua_resty_waf = require "resty.waf" 50 | local waf = lua_resty_waf:new() 51 | 52 | waf:exec() 53 | } 54 | 55 | content_by_lua_block { 56 | local collections = ngx.ctx.lua_resty_waf.collections 57 | 58 | ngx.say(collections.METHOD) 59 | } 60 | } 61 | --- request 62 | POST /t 63 | --- error_code: 200 64 | --- response_body 65 | POST 66 | --- no_error_log 67 | [error] 68 | 69 | === TEST 3: METHOD collections variable (HEAD) 70 | --- http_config eval: $::HttpConfig 71 | --- config 72 | location /t { 73 | access_by_lua_block { 74 | local lua_resty_waf = require "resty.waf" 75 | local waf = lua_resty_waf:new() 76 | 77 | waf:exec() 78 | 79 | local collections = ngx.ctx.lua_resty_waf.collections 80 | ngx.header["X-Method"] = collections.METHOD 81 | } 82 | 83 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 84 | } 85 | --- request 86 | HEAD /t 87 | --- error_code: 200 88 | --- response_headers 89 | X-Method: HEAD 90 | --- no_error_log 91 | [error] 92 | 93 | === TEST 4: METHOD collections variable (type verification) 94 | --- http_config eval: $::HttpConfig 95 | --- config 96 | location /t { 97 | access_by_lua_block { 98 | local lua_resty_waf = require "resty.waf" 99 | local waf = lua_resty_waf:new() 100 | 101 | waf:exec() 102 | } 103 | 104 | content_by_lua_block { 105 | local collections = ngx.ctx.lua_resty_waf.collections 106 | 107 | ngx.say(type(collections.METHOD)) 108 | } 109 | } 110 | --- request 111 | GET /t 112 | --- error_code: 200 113 | --- response_body 114 | string 115 | --- no_error_log 116 | [error] 117 | 118 | -------------------------------------------------------------------------------- /t/unit/collections/04_uri.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() ; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: URI collections variable 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:exec() 28 | } 29 | 30 | content_by_lua_block { 31 | local collections = ngx.ctx.lua_resty_waf.collections 32 | 33 | ngx.say(collections.URI) 34 | } 35 | } 36 | --- request 37 | GET /t 38 | --- error_code: 200 39 | --- response_body 40 | /t 41 | --- no_error_log 42 | [error] 43 | 44 | === TEST 2: URI collections variable (longer URI) 45 | --- http_config eval: $::HttpConfig 46 | --- config 47 | location /t { 48 | access_by_lua_block { 49 | local lua_resty_waf = require "resty.waf" 50 | local waf = lua_resty_waf:new() 51 | 52 | waf:exec() 53 | } 54 | 55 | content_by_lua_block { 56 | local collections = ngx.ctx.lua_resty_waf.collections 57 | 58 | ngx.say("Hello world!") 59 | } 60 | } 61 | 62 | location /foo/bar { 63 | access_by_lua_block { 64 | local lua_resty_waf = require "resty.waf" 65 | local waf = lua_resty_waf:new() 66 | 67 | waf:exec() 68 | } 69 | 70 | content_by_lua_block { 71 | local collections = ngx.ctx.lua_resty_waf.collections 72 | 73 | ngx.say(collections.URI) 74 | } 75 | } 76 | --- request 77 | GET /foo/bar/ 78 | --- error_code: 200 79 | --- response_body 80 | /foo/bar/ 81 | --- no_error_log 82 | [error] 83 | 84 | === TEST 3: URI collections variable (type verification) 85 | --- http_config eval: $::HttpConfig 86 | --- config 87 | location /t { 88 | access_by_lua_block { 89 | local lua_resty_waf = require "resty.waf" 90 | local waf = lua_resty_waf:new() 91 | 92 | waf:exec() 93 | } 94 | 95 | content_by_lua_block { 96 | local collections = ngx.ctx.lua_resty_waf.collections 97 | 98 | ngx.say(type(collections.URI)) 99 | } 100 | } 101 | --- request 102 | GET /t 103 | --- error_code: 200 104 | --- response_body 105 | string 106 | --- no_error_log 107 | [error] 108 | 109 | -------------------------------------------------------------------------------- /t/unit/collections/05_uri_args.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() ; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: URI_ARGS collections variable (single element) 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:exec() 28 | } 29 | 30 | content_by_lua_block { 31 | local collections = ngx.ctx.lua_resty_waf.collections 32 | 33 | ngx.say(collections.URI_ARGS["foo"]) 34 | } 35 | } 36 | --- request 37 | GET /t?foo=bar 38 | --- error_code: 200 39 | --- response_body 40 | bar 41 | --- no_error_log 42 | [error] 43 | 44 | === TEST 2: URI_ARGS collections variable (multiple elements) 45 | --- http_config eval: $::HttpConfig 46 | --- config 47 | location /t { 48 | access_by_lua_block { 49 | local lua_resty_waf = require "resty.waf" 50 | local waf = lua_resty_waf:new() 51 | 52 | waf:exec() 53 | } 54 | 55 | content_by_lua_block { 56 | local collections = ngx.ctx.lua_resty_waf.collections 57 | 58 | ngx.say(collections.URI_ARGS["foo"]) 59 | } 60 | } 61 | --- request 62 | GET /t?foo=bar&foo=baz 63 | --- error_code: 200 64 | --- response_body 65 | barbaz 66 | --- no_error_log 67 | [error] 68 | 69 | === TEST 3: URI_ARGS collections variable (non-existent element) 70 | --- http_config eval: $::HttpConfig 71 | --- config 72 | location /t { 73 | access_by_lua_block { 74 | local lua_resty_waf = require "resty.waf" 75 | local waf = lua_resty_waf:new() 76 | 77 | waf:exec() 78 | } 79 | 80 | content_by_lua_block { 81 | local collections = ngx.ctx.lua_resty_waf.collections 82 | 83 | ngx.say(collections.URI_ARGS["foo"]) 84 | } 85 | } 86 | --- request 87 | GET /t?frob=qux 88 | --- error_code: 200 89 | --- response_body 90 | nil 91 | --- no_error_log 92 | [error] 93 | 94 | === TEST 4: URI_ARGS collections variable (type verification) 95 | --- http_config eval: $::HttpConfig 96 | --- config 97 | location /t { 98 | access_by_lua_block { 99 | local lua_resty_waf = require "resty.waf" 100 | local waf = lua_resty_waf:new() 101 | 102 | waf:exec() 103 | } 104 | 105 | content_by_lua_block { 106 | local collections = ngx.ctx.lua_resty_waf.collections 107 | 108 | ngx.say(type(collections.URI_ARGS)) 109 | } 110 | } 111 | --- request 112 | GET /t 113 | --- error_code: 200 114 | --- response_body 115 | table 116 | --- no_error_log 117 | [error] 118 | 119 | -------------------------------------------------------------------------------- /t/unit/collections/06_request_headers.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() ; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: REQUEST_HEADERS collections variable (single element) 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:exec() 28 | } 29 | 30 | content_by_lua_block { 31 | local collections = ngx.ctx.lua_resty_waf.collections 32 | 33 | ngx.say(collections.REQUEST_HEADERS["x-foo"]) 34 | } 35 | } 36 | --- request 37 | GET /t 38 | --- more_headers 39 | x-foo: bar 40 | --- error_code: 200 41 | --- response_body 42 | bar 43 | --- no_error_log 44 | [error] 45 | 46 | === TEST 2: REQUEST_HEADERS collections variable (multiple elements) 47 | --- http_config eval: $::HttpConfig 48 | --- config 49 | location /t { 50 | access_by_lua_block { 51 | local lua_resty_waf = require "resty.waf" 52 | local waf = lua_resty_waf:new() 53 | 54 | waf:exec() 55 | } 56 | 57 | content_by_lua_block { 58 | local collections = ngx.ctx.lua_resty_waf.collections 59 | 60 | ngx.say(collections.REQUEST_HEADERS["x-foo"]) 61 | } 62 | } 63 | --- request 64 | GET /t 65 | --- more_headers 66 | x-foo: bar 67 | x-foo: baz 68 | --- error_code: 200 69 | --- response_body 70 | barbaz 71 | --- no_error_log 72 | [error] 73 | 74 | === TEST 3: REQUEST_HEADERS collections variable (non-existent element) 75 | --- http_config eval: $::HttpConfig 76 | --- config 77 | location /t { 78 | access_by_lua_block { 79 | local lua_resty_waf = require "resty.waf" 80 | local waf = lua_resty_waf:new() 81 | 82 | waf:exec() 83 | } 84 | 85 | content_by_lua_block { 86 | local collections = ngx.ctx.lua_resty_waf.collections 87 | 88 | ngx.say(collections.REQUEST_HEADERS["x-foo"]) 89 | } 90 | } 91 | --- request 92 | GET /t 93 | --- error_code: 200 94 | --- response_body 95 | nil 96 | --- no_error_log 97 | [error] 98 | 99 | === TEST 4: REQUEST_HEADERS collections variable (type verification) 100 | --- http_config eval: $::HttpConfig 101 | --- config 102 | location /t { 103 | access_by_lua_block { 104 | local lua_resty_waf = require "resty.waf" 105 | local waf = lua_resty_waf:new() 106 | 107 | waf:exec() 108 | } 109 | 110 | content_by_lua_block { 111 | local collections = ngx.ctx.lua_resty_waf.collections 112 | 113 | ngx.say(type(collections.REQUEST_HEADERS)) 114 | } 115 | } 116 | --- request 117 | GET /t 118 | --- error_code: 200 119 | --- response_body 120 | table 121 | --- no_error_log 122 | [error] 123 | 124 | -------------------------------------------------------------------------------- /t/unit/collections/08_request_line.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() ; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: REQUEST_LINE collections variable (simple) 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:exec() 28 | } 29 | 30 | content_by_lua_block { 31 | local collections = ngx.ctx.lua_resty_waf.collections 32 | 33 | ngx.say(collections.REQUEST_LINE) 34 | } 35 | } 36 | --- request 37 | GET /t 38 | --- error_code: 200 39 | --- response_body 40 | GET /t HTTP/1.1 41 | --- no_error_log 42 | [error] 43 | 44 | === TEST 2: REQUEST_LINE collections variable (single pair) 45 | --- http_config eval: $::HttpConfig 46 | --- config 47 | location /t { 48 | access_by_lua_block { 49 | local lua_resty_waf = require "resty.waf" 50 | local waf = lua_resty_waf:new() 51 | 52 | waf:exec() 53 | } 54 | 55 | content_by_lua_block { 56 | local collections = ngx.ctx.lua_resty_waf.collections 57 | 58 | ngx.say(collections.REQUEST_LINE) 59 | } 60 | } 61 | --- request 62 | GET /t?foo=bar 63 | --- error_code: 200 64 | --- response_body 65 | GET /t?foo=bar HTTP/1.1 66 | --- no_error_log 67 | [error] 68 | 69 | === TEST 3: REQUEST_LINE collections variable (complex) 70 | --- http_config eval: $::HttpConfig 71 | --- config 72 | location /t { 73 | access_by_lua_block { 74 | local lua_resty_waf = require "resty.waf" 75 | local waf = lua_resty_waf:new() 76 | 77 | waf:exec() 78 | } 79 | 80 | content_by_lua_block { 81 | local collections = ngx.ctx.lua_resty_waf.collections 82 | 83 | ngx.say(collections.REQUEST_LINE) 84 | } 85 | } 86 | --- request 87 | GET /t?foo=bar&foo=bat&frob&qux= 88 | --- error_code: 200 89 | --- response_body 90 | GET /t?foo=bar&foo=bat&frob&qux= HTTP/1.1 91 | --- no_error_log 92 | [error] 93 | 94 | === TEST 4: REQUEST_LINE collections variable (type verification) 95 | --- http_config eval: $::HttpConfig 96 | --- config 97 | location /t { 98 | access_by_lua_block { 99 | local lua_resty_waf = require "resty.waf" 100 | local waf = lua_resty_waf:new() 101 | 102 | waf:exec() 103 | } 104 | 105 | content_by_lua_block { 106 | local collections = ngx.ctx.lua_resty_waf.collections 107 | 108 | ngx.say(type(collections.REQUEST_LINE)) 109 | } 110 | } 111 | --- request 112 | GET /t 113 | --- error_code: 200 114 | --- response_body 115 | string 116 | --- no_error_log 117 | [error] 118 | 119 | -------------------------------------------------------------------------------- /t/unit/collections/09_cookies.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() ; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: COOKIES collections variable (single element) 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:exec() 28 | } 29 | 30 | content_by_lua_block { 31 | local collections = ngx.ctx.lua_resty_waf.collections 32 | 33 | ngx.say(collections.COOKIES["x-foo"]) 34 | } 35 | } 36 | --- request 37 | GET /t 38 | --- more_headers 39 | Cookie: x-foo=bar 40 | --- error_code: 200 41 | --- response_body 42 | bar 43 | --- no_error_log 44 | [error] 45 | 46 | === TEST 2: COOKIES collections variable (multiple elements) 47 | # n.b. resty.cookie will only override a cookie sent multiple times 48 | --- http_config eval: $::HttpConfig 49 | --- config 50 | location /t { 51 | access_by_lua_block { 52 | local lua_resty_waf = require "resty.waf" 53 | local waf = lua_resty_waf:new() 54 | 55 | waf:exec() 56 | } 57 | 58 | content_by_lua_block { 59 | local collections = ngx.ctx.lua_resty_waf.collections 60 | 61 | ngx.say(collections.COOKIES["x-foo"]) 62 | } 63 | } 64 | --- request 65 | GET /t 66 | --- more_headers 67 | Cookie: x-foo=bar; x-foo=baz 68 | --- error_code: 200 69 | --- response_body 70 | baz 71 | --- no_error_log 72 | [error] 73 | 74 | === TEST 3: COOKIES collections variable (non-existent element) 75 | --- http_config eval: $::HttpConfig 76 | --- config 77 | location /t { 78 | access_by_lua_block { 79 | local lua_resty_waf = require "resty.waf" 80 | local waf = lua_resty_waf:new() 81 | 82 | waf:exec() 83 | } 84 | 85 | content_by_lua_block { 86 | local collections = ngx.ctx.lua_resty_waf.collections 87 | 88 | ngx.say(collections.COOKIES["x-foo"]) 89 | } 90 | } 91 | --- request 92 | GET /t 93 | --- error_code: 200 94 | --- response_body 95 | nil 96 | --- no_error_log 97 | [error] 98 | 99 | === TEST 4: COOKIES collections variable (type verification) 100 | --- http_config eval: $::HttpConfig 101 | --- config 102 | location /t { 103 | access_by_lua_block { 104 | local lua_resty_waf = require "resty.waf" 105 | local waf = lua_resty_waf:new() 106 | 107 | waf:exec() 108 | } 109 | 110 | content_by_lua_block { 111 | local collections = ngx.ctx.lua_resty_waf.collections 112 | 113 | ngx.say(type(collections.COOKIES)) 114 | } 115 | } 116 | --- request 117 | GET /t 118 | --- error_code: 200 119 | --- response_body 120 | table 121 | --- no_error_log 122 | [error] 123 | 124 | -------------------------------------------------------------------------------- /t/unit/collections/10_request_body.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() ; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: REQUEST_BODY collections variable (single element) 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:exec() 28 | } 29 | 30 | content_by_lua_block { 31 | local collections = ngx.ctx.lua_resty_waf.collections 32 | 33 | ngx.say(collections.REQUEST_BODY["foo"]) 34 | } 35 | } 36 | --- request 37 | POST /t? 38 | foo=bar 39 | --- more_headers 40 | Content-Type: application/x-www-form-urlencoded 41 | --- error_code: 200 42 | --- response_body 43 | bar 44 | --- no_error_log 45 | [error] 46 | 47 | === TEST 2: REQUEST_BODY collections variable (multiple elements) 48 | --- http_config eval: $::HttpConfig 49 | --- config 50 | location /t { 51 | access_by_lua_block { 52 | local lua_resty_waf = require "resty.waf" 53 | local waf = lua_resty_waf:new() 54 | 55 | waf:exec() 56 | } 57 | 58 | content_by_lua_block { 59 | local collections = ngx.ctx.lua_resty_waf.collections 60 | 61 | ngx.say(collections.REQUEST_BODY["foo"]) 62 | } 63 | } 64 | --- request 65 | POST /t 66 | foo=bar&foo=baz 67 | --- more_headers 68 | Content-Type: application/x-www-form-urlencoded 69 | --- error_code: 200 70 | --- response_body 71 | barbaz 72 | --- no_error_log 73 | [error] 74 | 75 | === TEST 3: REQUEST_BODY collections variable (non-existent element) 76 | --- http_config eval: $::HttpConfig 77 | --- config 78 | location /t { 79 | access_by_lua_block { 80 | local lua_resty_waf = require "resty.waf" 81 | local waf = lua_resty_waf:new() 82 | 83 | waf:exec() 84 | } 85 | 86 | content_by_lua_block { 87 | local collections = ngx.ctx.lua_resty_waf.collections 88 | 89 | ngx.say(collections.REQUEST_BODY["foo"]) 90 | } 91 | } 92 | --- request 93 | POST /t 94 | frob=qux 95 | --- more_headers 96 | Content-Type: application/x-www-form-urlencoded 97 | --- error_code: 200 98 | --- response_body 99 | nil 100 | --- no_error_log 101 | [error] 102 | 103 | === TEST 4: REQUEST_BODY collections variable (type verification) 104 | --- http_config eval: $::HttpConfig 105 | --- config 106 | location /t { 107 | access_by_lua_block { 108 | local lua_resty_waf = require "resty.waf" 109 | local waf = lua_resty_waf:new() 110 | 111 | waf:exec() 112 | } 113 | 114 | content_by_lua_block { 115 | local collections = ngx.ctx.lua_resty_waf.collections 116 | 117 | ngx.say(type(collections.REQUEST_BODY)) 118 | } 119 | } 120 | --- request 121 | POST /t 122 | --- more_headers 123 | Content-Type: application/x-www-form-urlencoded 124 | --- error_code: 200 125 | --- response_body 126 | table 127 | --- no_error_log 128 | [error] 129 | 130 | -------------------------------------------------------------------------------- /t/unit/collections/12_request_basename.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() ; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: REQUEST_BASENAME collections variable 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:exec() 28 | } 29 | 30 | content_by_lua_block { 31 | local collections = ngx.ctx.lua_resty_waf.collections 32 | 33 | ngx.say(collections.REQUEST_BASENAME) 34 | } 35 | } 36 | --- request 37 | GET /t 38 | --- error_code: 200 39 | --- response_body 40 | /t 41 | --- no_error_log 42 | [error] 43 | 44 | === TEST 2: REQUEST_BASENAME collections variable (longer URI) 45 | --- http_config eval: $::HttpConfig 46 | --- config 47 | location /t { 48 | access_by_lua_block { 49 | local lua_resty_waf = require "resty.waf" 50 | local waf = lua_resty_waf:new() 51 | 52 | waf:exec() 53 | } 54 | 55 | content_by_lua_block { 56 | local collections = ngx.ctx.lua_resty_waf.collections 57 | 58 | ngx.say("Hello world!") 59 | } 60 | } 61 | 62 | location /foo/bar { 63 | access_by_lua_block { 64 | local lua_resty_waf = require "resty.waf" 65 | local waf = lua_resty_waf:new() 66 | 67 | waf:exec() 68 | } 69 | 70 | content_by_lua_block { 71 | local collections = ngx.ctx.lua_resty_waf.collections 72 | 73 | ngx.say(collections.REQUEST_BASENAME) 74 | } 75 | } 76 | --- request 77 | GET /foo/bar/index.php 78 | --- error_code: 200 79 | --- response_body 80 | /index.php 81 | --- no_error_log 82 | [error] 83 | 84 | === TEST 3: REQUEST_BASENAME collections variable (type verification) 85 | --- http_config eval: $::HttpConfig 86 | --- config 87 | location /t { 88 | access_by_lua_block { 89 | local lua_resty_waf = require "resty.waf" 90 | local waf = lua_resty_waf:new() 91 | 92 | waf:exec() 93 | } 94 | 95 | content_by_lua_block { 96 | local collections = ngx.ctx.lua_resty_waf.collections 97 | 98 | ngx.say(type(collections.REQUEST_BASENAME)) 99 | } 100 | } 101 | --- request 102 | GET /t 103 | --- error_code: 200 104 | --- response_body 105 | string 106 | --- no_error_log 107 | [error] 108 | 109 | -------------------------------------------------------------------------------- /t/unit/collections/13_time.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: TIME collections variable 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | -- mock out ngx.localtime as a global function for testing 28 | localtime = function() return "1991-09-13 16:21:59" end 29 | ngx.localtime = localtime 30 | 31 | --ngx.time as well 32 | time = function() return "684793319" end 33 | ngx.time = time 34 | 35 | waf:exec() 36 | } 37 | 38 | content_by_lua_block { 39 | local collections = ngx.ctx.lua_resty_waf.collections 40 | 41 | local res = {} 42 | 43 | res[1] = collections.TIME 44 | res[2] = collections.TIME_DAY 45 | res[3] = collections.TIME_EPOCH 46 | res[4] = collections.TIME_HOUR 47 | res[5] = collections.TIME_MIN 48 | res[6] = collections.TIME_MON 49 | res[7] = collections.TIME_SEC 50 | res[8] = collections.TIME_YEAR 51 | 52 | ngx.say(table.concat(res, "\n")) 53 | } 54 | } 55 | --- request 56 | GET /t 57 | --- error_code: 200 58 | --- response_body 59 | 16:21:59 60 | 13 61 | 684793319 62 | 16 63 | 21 64 | 09 65 | 59 66 | 1991 67 | --- no_error_log 68 | [error] 69 | 70 | -------------------------------------------------------------------------------- /t/unit/collections/14_score_threshold.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() ; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: SCORE_THRESHOLD collections variable 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:exec() 28 | } 29 | 30 | content_by_lua_block { 31 | local collections = ngx.ctx.lua_resty_waf.collections 32 | local lua_resty_waf = require "resty.waf" 33 | local waf = lua_resty_waf:new() 34 | 35 | ngx.say(collections.SCORE_THRESHOLD) 36 | } 37 | } 38 | --- request 39 | GET /t 40 | --- error_code: 200 41 | --- response_body 42 | 5 43 | --- no_error_log 44 | [error] 45 | 46 | === TEST 2: SCORE_THRESHOLD collections variable (type verification) 47 | --- http_config eval: $::HttpConfig 48 | --- config 49 | location /t { 50 | access_by_lua_block { 51 | local lua_resty_waf = require "resty.waf" 52 | local waf = lua_resty_waf:new() 53 | 54 | waf:exec() 55 | } 56 | 57 | content_by_lua_block { 58 | local collections = ngx.ctx.lua_resty_waf.collections 59 | 60 | ngx.say(type(collections.SCORE_THRESHOLD)) 61 | } 62 | } 63 | --- request 64 | GET /t 65 | --- error_code: 200 66 | --- response_body 67 | number 68 | --- no_error_log 69 | [error] 70 | 71 | === TEST 3: SCORE_THRESHOLD collections variable (return type verification) 72 | --- http_config eval: $::HttpConfig 73 | --- config 74 | location /t { 75 | access_by_lua_block { 76 | local lua_resty_waf = require "resty.waf" 77 | local waf = lua_resty_waf:new() 78 | 79 | waf:exec() 80 | } 81 | 82 | content_by_lua_block { 83 | local collections = ngx.ctx.lua_resty_waf.collections 84 | local lua_resty_waf = require "resty.waf" 85 | local waf = lua_resty_waf:new() 86 | 87 | ngx.say(type(collections.SCORE_THRESHOLD)) 88 | } 89 | } 90 | --- request 91 | GET /t 92 | --- error_code: 200 93 | --- response_body 94 | number 95 | --- no_error_log 96 | [error] 97 | 98 | -------------------------------------------------------------------------------- /t/unit/collections/15_ngx_var.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() ; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: NGX_VAR collections variable 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:exec() 28 | } 29 | 30 | content_by_lua_block { 31 | local collections = ngx.ctx.lua_resty_waf.collections 32 | 33 | ngx.say(collections.NGX_VAR["remote_addr"]) 34 | } 35 | } 36 | --- request 37 | GET /t 38 | --- error_code: 200 39 | --- response_body 40 | 127.0.0.1 41 | --- no_error_log 42 | [error] 43 | 44 | === TEST 2: NGX_VAR collections variable (type verification) 45 | --- http_config eval: $::HttpConfig 46 | --- config 47 | location /t { 48 | access_by_lua_block { 49 | local lua_resty_waf = require "resty.waf" 50 | local waf = lua_resty_waf:new() 51 | 52 | waf:exec() 53 | } 54 | 55 | content_by_lua_block { 56 | local collections = ngx.ctx.lua_resty_waf.collections 57 | 58 | ngx.say(type(collections.NGX_VAR)) 59 | } 60 | } 61 | --- request 62 | GET /t 63 | --- error_code: 200 64 | --- response_body 65 | table 66 | --- no_error_log 67 | [error] 68 | 69 | -------------------------------------------------------------------------------- /t/unit/collections/16_protocol.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() ; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: PROTOCOL collections variable 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:exec() 28 | } 29 | 30 | content_by_lua_block { 31 | local collections = ngx.ctx.lua_resty_waf.collections 32 | 33 | ngx.say(collections.PROTOCOL) 34 | } 35 | } 36 | --- request 37 | GET /t 38 | --- error_code: 200 39 | --- response_body 40 | HTTP/1.1 41 | --- no_error_log 42 | [error] 43 | 44 | === TEST 2: PROTOCOL collections variable (HTTP/1.0) 45 | --- http_config eval: $::HttpConfig 46 | --- config 47 | location /t { 48 | access_by_lua_block { 49 | local lua_resty_waf = require "resty.waf" 50 | local waf = lua_resty_waf:new() 51 | 52 | waf:exec() 53 | } 54 | 55 | content_by_lua_block { 56 | local collections = ngx.ctx.lua_resty_waf.collections 57 | 58 | ngx.say(collections.PROTOCOL) 59 | } 60 | } 61 | --- request 62 | GET /t HTTP/1.0 63 | --- error_code: 200 64 | --- response_body 65 | HTTP/1.0 66 | --- no_error_log 67 | [error] 68 | 69 | === TEST 3: PROTOCOL collections variable (type verification) 70 | --- http_config eval: $::HttpConfig 71 | --- config 72 | location /t { 73 | access_by_lua_block { 74 | local lua_resty_waf = require "resty.waf" 75 | local waf = lua_resty_waf:new() 76 | 77 | waf:exec() 78 | } 79 | 80 | content_by_lua_block { 81 | local collections = ngx.ctx.lua_resty_waf.collections 82 | 83 | ngx.say(type(collections.PROTOCOL)) 84 | } 85 | } 86 | --- request 87 | GET /t 88 | --- error_code: 200 89 | --- response_body 90 | string 91 | --- no_error_log 92 | [error] 93 | 94 | -------------------------------------------------------------------------------- /t/unit/collections/19_status.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() ; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: STATUS collections variable (200) 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:exec() 28 | } 29 | 30 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 31 | 32 | header_filter_by_lua_block { 33 | local lua_resty_waf = require "resty.waf" 34 | local waf = lua_resty_waf:new() 35 | 36 | waf:exec() 37 | } 38 | 39 | log_by_lua_block { 40 | local collections = ngx.ctx.lua_resty_waf.collections 41 | 42 | ngx.log(ngx.INFO, [["]] .. collections.STATUS .. [["]]) 43 | } 44 | } 45 | --- request 46 | GET /t 47 | --- error_code: 200 48 | --- error_log 49 | "200" while logging request 50 | --- no_error_log 51 | [error] 52 | 53 | === TEST 2: STATUS collections variable (403) 54 | --- http_config eval: $::HttpConfig 55 | --- config 56 | location /t { 57 | access_by_lua_block { 58 | local lua_resty_waf = require "resty.waf" 59 | local waf = lua_resty_waf:new() 60 | 61 | waf:exec() 62 | } 63 | 64 | content_by_lua_block {ngx.exit(ngx.HTTP_FORBIDDEN)} 65 | 66 | header_filter_by_lua_block { 67 | local lua_resty_waf = require "resty.waf" 68 | local waf = lua_resty_waf:new() 69 | 70 | waf:exec() 71 | } 72 | 73 | log_by_lua_block { 74 | local collections = ngx.ctx.lua_resty_waf.collections 75 | 76 | ngx.log(ngx.INFO, [["]] .. collections.STATUS .. [["]]) 77 | } 78 | } 79 | --- request 80 | GET /t 81 | --- error_code: 403 82 | --- error_log 83 | "403" while logging request 84 | --- no_error_log 85 | [error] 86 | 87 | === TEST 3: STATUS collections variable (type verification) 88 | --- http_config eval: $::HttpConfig 89 | --- config 90 | location /t { 91 | access_by_lua_block { 92 | local lua_resty_waf = require "resty.waf" 93 | local waf = lua_resty_waf:new() 94 | 95 | waf:exec() 96 | } 97 | 98 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 99 | 100 | header_filter_by_lua_block { 101 | local lua_resty_waf = require "resty.waf" 102 | local waf = lua_resty_waf:new() 103 | 104 | waf:exec() 105 | } 106 | 107 | log_by_lua_block { 108 | local collections = ngx.ctx.lua_resty_waf.collections 109 | 110 | ngx.log(ngx.INFO, [["]] .. type(collections.STATUS) .. [["]]) 111 | } 112 | } 113 | --- request 114 | GET /t 115 | --- error_code: 200 116 | --- error_log 117 | "number" while logging request 118 | --- no_error_log 119 | [error] 120 | 121 | -------------------------------------------------------------------------------- /t/unit/event_log/04_file.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 2 * blocks() + 3; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Write an entry to file without error 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local log = require "resty.waf.log" 26 | local waf = lua_resty_waf:new() 27 | 28 | waf:set_option("event_log_target", "file") 29 | waf:set_option("event_log_target_path", "/tmp/waf.log") 30 | 31 | log.write_log_events[waf._event_log_target](waf, {foo = "bar"}) 32 | } 33 | 34 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 35 | } 36 | --- request 37 | GET /t 38 | --- error_code: 200 39 | --- error_log 40 | --- no_error_log 41 | [error] 42 | 43 | === TEST 2: Fatally fail when path is unset 44 | --- http_config eval: $::HttpConfig 45 | --- config 46 | location /t { 47 | access_by_lua_block { 48 | local lua_resty_waf = require "resty.waf" 49 | local log = require "resty.waf.log" 50 | local waf = lua_resty_waf:new() 51 | 52 | waf:set_option("event_log_target", "file") 53 | 54 | log.write_log_events[waf._event_log_target](waf, {foo = "bar"}) 55 | } 56 | 57 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 58 | } 59 | --- request 60 | GET /t 61 | --- error_code: 500 62 | --- error_log 63 | Event log target path is undefined in file logger 64 | 65 | === TEST 3: Warn when file path cannot be opened 66 | --- http_config eval: $::HttpConfig 67 | --- config 68 | location /t { 69 | access_by_lua_block { 70 | local lua_resty_waf = require "resty.waf" 71 | local log = require "resty.waf.log" 72 | local waf = lua_resty_waf:new() 73 | 74 | waf:set_option("event_log_target", "file") 75 | waf:set_option("event_log_target_path", "/tmp/waf.log") 76 | 77 | io.open = function() return false end 78 | 79 | log.write_log_events[waf._event_log_target](waf, {foo = "bar"}) 80 | } 81 | 82 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 83 | } 84 | --- request 85 | GET /t 86 | --- error_code: 200 87 | --- error_log 88 | Could not open /tmp/waf.log 89 | --- no_error_log 90 | [error] 91 | -------------------------------------------------------------------------------- /t/unit/fw/01_sanity.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: load module 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location = /t { 23 | access_by_lua_block { 24 | lua_resty_waf = require "resty.waf" 25 | } 26 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 27 | } 28 | --- request 29 | GET /t 30 | --- response_body 31 | --- error_code: 200 32 | --- no_error_log 33 | [error] 34 | 35 | === TEST 2: new instance 36 | --- http_config eval: $::HttpConfig 37 | --- config 38 | location = /t { 39 | access_by_lua_block { 40 | lua_resty_waf = require "resty.waf" 41 | local waf = lua_resty_waf:new() 42 | } 43 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 44 | } 45 | --- request 46 | GET /t 47 | --- response_body 48 | --- error_code: 200 49 | --- no_error_log 50 | [error] 51 | 52 | === TEST 3:do not load invalid module 53 | --- http_config eval: $::HttpConfig 54 | --- config 55 | location = /t { 56 | access_by_lua_block { 57 | lua_resty_waf = require "fw2" 58 | } 59 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 60 | } 61 | --- request 62 | GET /t 63 | --- error_code: 500 64 | --- error_log 65 | [error] 66 | fw2 67 | 68 | -------------------------------------------------------------------------------- /t/unit/log/01_fatal_fail.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Handle a fatal failure 20 | --- http_config eval 21 | $::HttpConfig . q# 22 | init_by_lua_block { 23 | logger = require "resty.waf.log" 24 | } 25 | # 26 | --- config 27 | location /t { 28 | access_by_lua_block { 29 | logger.fatal_fail("We have encountered a fatal failure!") 30 | } 31 | } 32 | --- request 33 | GET /t 34 | --- error_code: 500 35 | --- error_log eval 36 | ["in function 'fatal_fail'", qr/\[error\].*We have encountered a fatal failure!/] 37 | 38 | === TEST 2: Handle a fatal failure with warn error log level 39 | --- http_config eval 40 | $::HttpConfig . q# 41 | init_by_lua_block { 42 | logger = require "resty.waf.log" 43 | } 44 | # 45 | --- config 46 | location /t { 47 | access_by_lua_block { 48 | logger.fatal_fail("We have encountered a fatal failure!") 49 | } 50 | } 51 | --- log_level 52 | warn 53 | --- request 54 | GET /t 55 | --- error_code: 500 56 | --- error_log eval 57 | ["in function 'fatal_fail'", qr/\[error\].*We have encountered a fatal failure!/] 58 | 59 | -------------------------------------------------------------------------------- /t/unit/log/02_warn.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Log a warning 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | local logger = require "resty.waf.log" 27 | 28 | logger.warn(waf, "We have logged a warning!") 29 | } 30 | 31 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 32 | } 33 | --- request 34 | GET /t 35 | --- error_code: 200 36 | --- error_log eval 37 | qr/\[warn\].*We have logged a warning!/ 38 | --- no_error_log 39 | [error] 40 | 41 | === TEST 2: Log a warning at INFO level 42 | --- http_config eval: $::HttpConfig 43 | --- config 44 | location /t { 45 | access_by_lua_block { 46 | local lua_resty_waf = require "resty.waf" 47 | local waf = lua_resty_waf:new() 48 | local logger = require "resty.waf.log" 49 | 50 | logger.warn(waf, "We have logged a warning!") 51 | } 52 | 53 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 54 | } 55 | --- log_level 56 | info 57 | --- request 58 | GET /t 59 | --- error_code: 200 60 | --- error_log eval 61 | qr/\[warn\].*We have logged a warning!/ 62 | --- no_error_log 63 | [error] 64 | 65 | === TEST 3: Log a warning at ERROR level 66 | --- http_config eval: $::HttpConfig 67 | --- config 68 | location /t { 69 | access_by_lua_block { 70 | local lua_resty_waf = require "resty.waf" 71 | local waf = lua_resty_waf:new() 72 | local logger = require "resty.waf.log" 73 | 74 | logger.warn(waf, "We have logged a warning!") 75 | } 76 | 77 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 78 | } 79 | --- log_level 80 | error 81 | --- request 82 | GET /t 83 | --- error_code: 200 84 | --- no_error_log 85 | [error] 86 | We have logged a warning! 87 | 88 | -------------------------------------------------------------------------------- /t/unit/log/03_deprecate.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Print a warning via deprecate 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | local logger = require "resty.waf.log" 27 | 28 | logger.deprecate(waf, "We have logged a warning!") 29 | } 30 | 31 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 32 | } 33 | --- request 34 | GET /t 35 | --- error_code: 200 36 | --- error_log eval 37 | qr/\[warn\].*DEPRECATED: We have logged a warning!/ 38 | --- no_error_log 39 | [error] 40 | 41 | === TEST 2: Deprecate with a future fatal version 42 | --- http_config eval: $::HttpConfig 43 | --- config 44 | location /t { 45 | access_by_lua_block { 46 | local base = require "resty.waf.base" 47 | base.version = "0.1" 48 | 49 | local lua_resty_waf = require "resty.waf" 50 | local waf = lua_resty_waf:new() 51 | local logger = require "resty.waf.log" 52 | 53 | logger.deprecate(waf, "We have logged a warning!", "0.2") 54 | } 55 | 56 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 57 | } 58 | --- request 59 | GET /t 60 | --- error_code: 200 61 | --- error_log eval 62 | qr/\[warn\].*DEPRECATED: We have logged a warning!/ 63 | --- no_error_log 64 | [error] 65 | 66 | === TEST 3: Deprecate with a matching fatal version 67 | --- http_config eval: $::HttpConfig 68 | --- config 69 | location /t { 70 | access_by_lua_block { 71 | local base = require "resty.waf.base" 72 | base.version = "0.1" 73 | 74 | local lua_resty_waf = require "resty.waf" 75 | local waf = lua_resty_waf:new() 76 | local logger = require "resty.waf.log" 77 | 78 | logger.deprecate(waf, "We have logged a warning!", "0.1") 79 | } 80 | 81 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 82 | } 83 | --- request 84 | GET /t 85 | --- error_code: 200 86 | --- error_log eval 87 | qr/\[warn\].*DEPRECATED: We have logged a warning!/ 88 | --- no_error_log 89 | [error] 90 | 91 | === TEST 4: Deprecate with a past fatal version 92 | --- http_config eval: $::HttpConfig 93 | --- config 94 | location /t { 95 | access_by_lua_block { 96 | local base = require "resty.waf.base" 97 | base.version = "0.1" 98 | 99 | local lua_resty_waf = require "resty.waf" 100 | local waf = lua_resty_waf:new() 101 | local logger = require "resty.waf.log" 102 | 103 | logger.deprecate(waf, "We have logged a warning!", "0.0.9") 104 | } 105 | 106 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 107 | } 108 | --- request 109 | GET /t 110 | --- error_code: 500 111 | --- error_log eval 112 | [qr/\[warn\].*DEPRECATED: We have logged a warning!/, 113 | qr/fatal deprecation version passed/] 114 | 115 | -------------------------------------------------------------------------------- /t/unit/operators/09_detect_sqli.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Match (individual) 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location = /t { 23 | content_by_lua_block { 24 | local op = require "resty.waf.operators" 25 | local match, value = op.detect_sqli("\'; DROP TABLES foo --") 26 | ngx.say(match) 27 | } 28 | } 29 | --- request 30 | GET /t 31 | --- error_code: 200 32 | --- response_body 33 | true 34 | --- no_error_log 35 | [error] 36 | 37 | === TEST 2: Match (table) 38 | --- http_config eval: $::HttpConfig 39 | --- config 40 | location = /t { 41 | content_by_lua_block { 42 | local op = require "resty.waf.operators" 43 | local match, value = op.detect_sqli({"this string has the word DROP and SELECT", "\'; DROP TABLES foo --"}) 44 | ngx.say(match) 45 | } 46 | } 47 | --- request 48 | GET /t?f 49 | --- error_code: 200 50 | --- response_body 51 | true 52 | --- no_error_log 53 | [error] 54 | 55 | === TEST 3: No match (individual) 56 | --- http_config eval: $::HttpConfig 57 | --- config 58 | location = /t { 59 | content_by_lua_block { 60 | local op = require "resty.waf.operators" 61 | local match, value = op.detect_sqli("this string has the word DROP and SELECT") 62 | ngx.say(match) 63 | } 64 | } 65 | --- request 66 | GET /t 67 | --- error_code: 200 68 | --- response_body 69 | false 70 | --- no_error_log 71 | [error] 72 | 73 | === TEST 4: No match (table) 74 | --- http_config eval: $::HttpConfig 75 | --- config 76 | location = /t { 77 | content_by_lua_block { 78 | local op = require "resty.waf.operators" 79 | local match, value = op.detect_sqli({"this string has the word DROP and SELECT", "so does DROP this SELECT one"}) 80 | ngx.say(match) 81 | } 82 | } 83 | --- request 84 | GET /t 85 | --- error_code: 200 86 | --- response_body 87 | false 88 | --- no_error_log 89 | [error] 90 | 91 | === TEST 5: Return types 92 | --- http_config eval: $::HttpConfig 93 | --- config 94 | location = /t { 95 | content_by_lua_block { 96 | local op = require "resty.waf.operators" 97 | local match, value = op.detect_sqli("; DROP TABLES foo --") 98 | ngx.say(type(match)) 99 | ngx.say(type(value)) 100 | } 101 | } 102 | --- request 103 | GET /t 104 | --- error_code: 200 105 | --- response_body 106 | boolean 107 | string 108 | --- no_error_log 109 | [error] 110 | -------------------------------------------------------------------------------- /t/unit/operators/09_detect_xss.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Match (individual) 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location = /t { 23 | content_by_lua_block { 24 | local op = require "resty.waf.operators" 25 | local match, value = op.detect_xss("\'>") 26 | ngx.say(match) 27 | } 28 | } 29 | --- request 30 | GET /t 31 | --- error_code: 200 32 | --- response_body 33 | true 34 | --- no_error_log 35 | [error] 36 | 37 | === TEST 2: Match (table) 38 | --- http_config eval: $::HttpConfig 39 | --- config 40 | location = /t { 41 | content_by_lua_block { 42 | local op = require "resty.waf.operators" 43 | local match, value = op.detect_xss({"this string has the word select and alert", "\'>"}) 44 | ngx.say(match) 45 | } 46 | } 47 | --- request 48 | GET /t?f 49 | --- error_code: 200 50 | --- response_body 51 | true 52 | --- no_error_log 53 | [error] 54 | 55 | === TEST 3: No match (individual) 56 | --- http_config eval: $::HttpConfig 57 | --- config 58 | location = /t { 59 | content_by_lua_block { 60 | local op = require "resty.waf.operators" 61 | local match, value = op.detect_xss("this string has the word select and alert") 62 | ngx.say(match) 63 | } 64 | } 65 | --- request 66 | GET /t 67 | --- error_code: 200 68 | --- response_body 69 | false 70 | --- no_error_log 71 | [error] 72 | 73 | === TEST 4: No match (table) 74 | --- http_config eval: $::HttpConfig 75 | --- config 76 | location = /t { 77 | content_by_lua_block { 78 | local op = require "resty.waf.operators" 79 | local match, value = op.detect_xss({"this string has the word select and alert", "so does select this alert one"}) 80 | ngx.say(match) 81 | } 82 | } 83 | --- request 84 | GET /t 85 | --- error_code: 200 86 | --- response_body 87 | false 88 | --- no_error_log 89 | [error] 90 | 91 | === TEST 5: Return types 92 | --- http_config eval: $::HttpConfig 93 | --- config 94 | location = /t { 95 | content_by_lua_block { 96 | local op = require "resty.waf.operators" 97 | local match, value = op.detect_xss("\'>") 98 | ngx.say(type(match)) 99 | ngx.say(type(value)) 100 | } 101 | } 102 | --- request 103 | GET /t 104 | --- error_code: 200 105 | --- response_body 106 | boolean 107 | string 108 | --- no_error_log 109 | [error] 110 | -------------------------------------------------------------------------------- /t/unit/operators/10_str_match.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Match (individual) 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location = /t { 23 | content_by_lua_block { 24 | local op = require "resty.waf.operators" 25 | local match, value = op.str_match("hello, 1234", "hello") 26 | ngx.say(match) 27 | } 28 | } 29 | --- request 30 | GET /t 31 | --- error_code: 200 32 | --- response_body 33 | true 34 | --- no_error_log 35 | [error] 36 | 37 | === TEST 2: Match (table) 38 | --- http_config eval: $::HttpConfig 39 | --- config 40 | location = /t { 41 | content_by_lua_block { 42 | local op = require "resty.waf.operators" 43 | local match, value = op.str_match({ "99-99-99", " _\\\\", "hello, 1234"}, "hello") 44 | ngx.say(match) 45 | } 46 | } 47 | --- request 48 | GET /t 49 | --- error_code: 200 50 | --- response_body 51 | true 52 | --- no_error_log 53 | [error] 54 | 55 | === TEST 3: No match (individual) 56 | --- http_config eval: $::HttpConfig 57 | --- config 58 | location = /t { 59 | content_by_lua_block { 60 | local op = require "resty.waf.operators" 61 | local match, value = op.str_match("HELLO, 1234", "hello") 62 | ngx.say(match) 63 | } 64 | } 65 | --- request 66 | GET /t 67 | --- error_code: 200 68 | --- response_body 69 | false 70 | --- no_error_log 71 | [error] 72 | 73 | === TEST 4: No match (table) 74 | --- http_config eval: $::HttpConfig 75 | --- config 76 | location = /t { 77 | content_by_lua_block { 78 | local op = require "resty.waf.operators" 79 | local match, value = op.str_match({ "99-99-99", " _\\\\", "HELLO, 1234"}, "hello") 80 | ngx.say(match) 81 | } 82 | } 83 | --- request 84 | GET /t 85 | --- error_code: 200 86 | --- response_body 87 | false 88 | --- no_error_log 89 | [error] 90 | 91 | === TEST 5: Return values 92 | --- http_config eval: $::HttpConfig 93 | --- config 94 | location = /t { 95 | content_by_lua_block { 96 | local op = require "resty.waf.operators" 97 | local match, value = op.str_match("hello, 1234", "hello") 98 | ngx.say(match) 99 | ngx.say(value) 100 | } 101 | } 102 | --- request 103 | GET /t 104 | --- error_code: 200 105 | --- response_body 106 | true 107 | hello, 1234 108 | --- no_error_log 109 | [error] 110 | 111 | === TEST 6: Return value types 112 | --- http_config eval: $::HttpConfig 113 | --- config 114 | location = /t { 115 | content_by_lua_block { 116 | local op = require "resty.waf.operators" 117 | local match, value = op.str_match("hello, 1234", "hello") 118 | ngx.say(type(match)) 119 | ngx.say(type(value)) 120 | } 121 | } 122 | --- request 123 | GET /t 124 | --- error_code: 200 125 | --- response_body 126 | boolean 127 | string 128 | --- no_error_log 129 | [error] 130 | 131 | -------------------------------------------------------------------------------- /t/unit/opts/01_custom_phase.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks() - 3; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Execute user-defined phase (collections available) 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | local opts = { 28 | phase = "access" 29 | } 30 | 31 | waf:exec(opts) 32 | 33 | local ctx = ngx.ctx.lua_resty_waf 34 | 35 | ngx.say(ctx.phase) 36 | ngx.say(ctx.collections.URI) 37 | } 38 | } 39 | --- request 40 | GET /t 41 | --- error_code: 200 42 | --- response_body 43 | access 44 | /t 45 | --- no_error_log 46 | [error] 47 | 48 | === TEST 2: Execute user-defined phase (some collections not available) 49 | --- http_config eval: $::HttpConfig 50 | --- config 51 | location /t { 52 | content_by_lua_block { 53 | local lua_resty_waf = require "resty.waf" 54 | local waf = lua_resty_waf:new() 55 | 56 | local opts = { 57 | phase = "header_filter" 58 | } 59 | 60 | waf:exec(opts) 61 | 62 | local ctx = ngx.ctx.lua_resty_waf 63 | ngx.say(ctx.phase) 64 | ngx.say(ctx.collections.STATUS) 65 | } 66 | } 67 | --- request 68 | GET /t 69 | --- error_code: 200 70 | --- response_body 71 | header_filter 72 | 0 73 | --- no_error_log 74 | [error] 75 | 76 | === TEST 3: Execute user-defined phase (API disabled in collections lookup) 77 | --- http_config eval: $::HttpConfig 78 | --- config 79 | location /t { 80 | content_by_lua_block { 81 | local lua_resty_waf = require "resty.waf" 82 | local waf = lua_resty_waf:new() 83 | 84 | local opts = { 85 | phase = "body_filter" 86 | } 87 | 88 | waf:exec(opts) 89 | 90 | local ctx = ngx.ctx.lua_resty_waf 91 | ngx.say(ctx.phase) 92 | ngx.say(ctx.collections.STATUS) 93 | } 94 | } 95 | --- request 96 | GET /t 97 | --- error_code: 500 98 | --- error_log 99 | API disabled 100 | -------------------------------------------------------------------------------- /t/unit/opts/03_custom_var.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Set a static var 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | access_by_lua_block { 24 | local lua_resty_waf = require "resty.waf" 25 | local waf = lua_resty_waf:new() 26 | 27 | waf:set_var("foo", "bar") 28 | waf:exec(opts) 29 | 30 | local ctx = ngx.ctx.lua_resty_waf 31 | 32 | ngx.say(ctx.collections.TX.foo) 33 | } 34 | 35 | content_by_lua_block { ngx.exit(ngx.HTTP_OK) } 36 | } 37 | --- request 38 | GET /t 39 | --- error_code: 200 40 | --- response_body 41 | bar 42 | --- no_error_log 43 | [error] 44 | 45 | === TEST 2: Set a dynamic var 46 | --- http_config eval: $::HttpConfig 47 | --- config 48 | location /t { 49 | access_by_lua_block { 50 | local lua_resty_waf = require "resty.waf" 51 | local waf = lua_resty_waf:new() 52 | 53 | waf:set_var("foo", "%{REMOTE_ADDR}") 54 | waf:exec(opts) 55 | 56 | local ctx = ngx.ctx.lua_resty_waf 57 | 58 | ngx.say(ctx.collections.TX.foo) 59 | } 60 | 61 | content_by_lua_block { ngx.exit(ngx.HTTP_OK) } 62 | } 63 | --- request 64 | GET /t 65 | --- error_code: 200 66 | --- response_body 67 | 127.0.0.1 68 | --- no_error_log 69 | [error] 70 | 71 | === TEST 3: Set a dynamic var with a specific element 72 | --- http_config eval: $::HttpConfig 73 | --- config 74 | location /t { 75 | access_by_lua_block { 76 | local lua_resty_waf = require "resty.waf" 77 | local waf = lua_resty_waf:new() 78 | 79 | waf:set_var("foo", "%{REQUEST_HEADERS.X-Foo}") 80 | waf:exec(opts) 81 | 82 | local ctx = ngx.ctx.lua_resty_waf 83 | 84 | ngx.say(ctx.collections.TX.foo) 85 | } 86 | 87 | content_by_lua_block { ngx.exit(ngx.HTTP_OK) } 88 | } 89 | --- request 90 | GET /t 91 | --- more_headers 92 | X-Foo: bar 93 | --- error_code: 200 94 | --- response_body 95 | bar 96 | --- no_error_log 97 | [error] 98 | 99 | -------------------------------------------------------------------------------- /t/unit/random/01_random.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Generate 8 hex-encoded random bytes 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local random = require "resty.waf.random" 25 | local string = random.random_bytes(8) 26 | 27 | ngx.say(string) 28 | } 29 | } 30 | --- request 31 | GET /t 32 | --- error_code: 200 33 | --- response_body_like 34 | ^[0-9a-f]{16}$ 35 | --- no_error_log 36 | [error] 37 | 38 | === TEST 2: Generate 16 hex-encoded random bytes 39 | --- http_config eval: $::HttpConfig 40 | --- config 41 | location /t { 42 | content_by_lua_block { 43 | local random = require "resty.waf.random" 44 | local string = random.random_bytes(16) 45 | 46 | ngx.say(string) 47 | } 48 | } 49 | --- request 50 | GET /t 51 | --- error_code: 200 52 | --- response_body_like 53 | ^[0-9a-f]{32}$ 54 | --- no_error_log 55 | [error] 56 | 57 | === TEST 3: Two random strings should not be equal 58 | --- http_config eval: $::HttpConfig 59 | --- config 60 | location /t { 61 | content_by_lua_block { 62 | local random = require "resty.waf.random" 63 | local str1 = random.random_bytes(8) 64 | local str2 = random.random_bytes(8) 65 | 66 | ngx.say(str1 == str2) 67 | } 68 | } 69 | --- request 70 | GET /t 71 | --- error_code: 200 72 | --- response_body 73 | false 74 | --- no_error_log 75 | [error] 76 | 77 | -------------------------------------------------------------------------------- /t/unit/rule_calc/01_single_offset.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Ruleset starter offsets 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local rule_calc = require "resty.waf.rule_calc" 25 | local mock_rules = { 26 | { id = 1, vars = {}, actions = { disrupt = "DENY" } }, 27 | { id = 2, vars = {}, actions = { disrupt = "DENY" } }, 28 | { id = 3, vars = {}, actions = { disrupt = "DENY" } }, 29 | } 30 | 31 | rule_calc.calculate(mock_rules) 32 | 33 | ngx.say(mock_rules[1].offset_match) 34 | ngx.say(mock_rules[1].offset_nomatch) 35 | } 36 | } 37 | --- request 38 | GET /t 39 | --- response_body 40 | 1 41 | 1 42 | --- error_code: 200 43 | --- no_error_log 44 | [error] 45 | 46 | === TEST 2: Ruleset middle element offsets 47 | --- http_config eval: $::HttpConfig 48 | --- config 49 | location /t { 50 | content_by_lua_block { 51 | local rule_calc = require "resty.waf.rule_calc" 52 | local mock_rules = { 53 | { id = 1, vars = {}, actions = { disrupt = "DENY" } }, 54 | { id = 2, vars = {}, actions = { disrupt = "DENY" } }, 55 | { id = 3, vars = {}, actions = { disrupt = "DENY" } }, 56 | } 57 | 58 | rule_calc.calculate(mock_rules) 59 | 60 | ngx.say(mock_rules[2].offset_match) 61 | ngx.say(mock_rules[2].offset_nomatch) 62 | } 63 | } 64 | --- request 65 | GET /t 66 | --- response_body 67 | 1 68 | 1 69 | --- error_code: 200 70 | --- no_error_log 71 | [error] 72 | 73 | === TEST 3: Ruleset end offsets 74 | --- http_config eval: $::HttpConfig 75 | --- config 76 | location /t { 77 | content_by_lua_block { 78 | local rule_calc = require "resty.waf.rule_calc" 79 | local mock_rules = { 80 | { id = 1, vars = {}, actions = { disrupt = "DENY" } }, 81 | { id = 2, vars = {}, actions = { disrupt = "DENY" } }, 82 | { id = 3, vars = {}, actions = { disrupt = "DENY" } }, 83 | } 84 | 85 | rule_calc.calculate(mock_rules) 86 | 87 | ngx.say(mock_rules[3].offset_match) 88 | ngx.say(mock_rules[3].offset_nomatch) 89 | } 90 | } 91 | --- request 92 | GET /t 93 | --- response_body 94 | nil 95 | nil 96 | --- error_code: 200 97 | --- no_error_log 98 | [error] 99 | 100 | -------------------------------------------------------------------------------- /t/unit/storage/01_storage_zone.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Set valid storage zone 20 | --- http_config eval 21 | $::HttpConfig . q# 22 | lua_shared_dict storage 10m; 23 | init_by_lua_block { 24 | lua_resty_waf = require "resty.waf" 25 | } 26 | # 27 | --- config 28 | location = /t { 29 | access_by_lua_block { 30 | local waf = lua_resty_waf:new() 31 | waf:set_option("storage_zone", "storage") 32 | } 33 | 34 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 35 | } 36 | --- request 37 | GET /t 38 | --- error_code: 200 39 | --- no_error_log eval 40 | ["[error]", qr/Attempted to set lua_resty_waf storage zone as.*, but that lua_shared_dict does not exist/] 41 | 42 | === TEST 2: Set invalid storage zone 43 | --- http_config eval 44 | $::HttpConfig . q# 45 | init_by_lua_block { 46 | lua_resty_waf = require "resty.waf" 47 | } 48 | # 49 | --- config 50 | location = /t { 51 | access_by_lua_block { 52 | local waf = lua_resty_waf:new() 53 | waf:set_option("storage_zone", "storage") 54 | } 55 | 56 | content_by_lua_block {ngx.exit(ngx.HTTP_OK)} 57 | } 58 | --- request 59 | GET /t 60 | --- error_code: 500 61 | --- error_log eval 62 | ["[error]", qr/Attempted to set lua-resty-waf storage zone as.*, but that lua_shared_dict does not exist/] 63 | 64 | -------------------------------------------------------------------------------- /t/unit/transform/01_base64.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: base64_decode 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local lookup = require "resty.waf.transform" 25 | local value = "aGVsbG8gd29ybGQ=" 26 | local transform = lookup.lookup["base64_decode"]({}, value) 27 | ngx.say(transform) 28 | } 29 | } 30 | --- request 31 | GET /t 32 | --- error_code: 200 33 | --- response_body 34 | hello world 35 | --- no_error_log 36 | [error] 37 | 38 | === TEST 2: base64_encode 39 | --- http_config eval: $::HttpConfig 40 | --- config 41 | location /t { 42 | content_by_lua_block { 43 | local lookup = require "resty.waf.transform" 44 | local value = "goodbye world" 45 | local transform = lookup.lookup["base64_encode"]({}, value) 46 | ngx.say(transform) 47 | } 48 | } 49 | --- request 50 | GET /t 51 | --- error_code: 200 52 | --- response_body 53 | Z29vZGJ5ZSB3b3JsZA== 54 | --- no_error_log 55 | [error] 56 | 57 | -------------------------------------------------------------------------------- /t/unit/transform/02_whitespace.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: compress_whitespace 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local lookup = require "resty.waf.transform" 25 | local value = "how are you doing?" 26 | local transform = lookup.lookup["compress_whitespace"]({ _pcre_flags = "" }, value) 27 | ngx.say(transform) 28 | } 29 | } 30 | --- request 31 | GET /t 32 | --- error_code: 200 33 | --- response_body 34 | how are you doing? 35 | --- no_error_log 36 | [error] 37 | 38 | === TEST 2: remove_whitespace 39 | --- http_config eval: $::HttpConfig 40 | --- config 41 | location /t { 42 | content_by_lua_block { 43 | local lookup = require "resty.waf.transform" 44 | local value = "how are you doing?" 45 | local transform = lookup.lookup["remove_whitespace"]({ _pcre_flags = "" }, value) 46 | ngx.say(transform) 47 | } 48 | } 49 | --- request 50 | GET /t 51 | --- error_code: 200 52 | --- response_body 53 | howareyoudoing? 54 | --- no_error_log 55 | [error] 56 | 57 | -------------------------------------------------------------------------------- /t/unit/transform/03_comments.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: remove_comments 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local lookup = require "resty.waf.transform" 25 | local value = "UNI/*1*/ON SELECT" 26 | local transform = lookup.lookup["remove_comments"]({ _pcre_flags = "" }, value) 27 | ngx.say(transform) 28 | } 29 | } 30 | --- request 31 | GET /t 32 | --- error_code: 200 33 | --- response_body 34 | UNION SELECT 35 | --- no_error_log 36 | [error] 37 | 38 | === TEST 2: remove_comments_char (c-style comments) 39 | --- http_config eval: $::HttpConfig 40 | --- config 41 | location /t { 42 | content_by_lua_block { 43 | local lookup = require "resty.waf.transform" 44 | local value = "UNION/* */SELECT" 45 | local transform = lookup.lookup["remove_comments_char"]({ _pcre_flags = "" }, value) 46 | ngx.say(transform) 47 | } 48 | } 49 | --- request 50 | GET /t 51 | --- error_code: 200 52 | --- response_body 53 | UNION SELECT 54 | --- no_error_log 55 | [error] 56 | 57 | === TEST 3: remove_comments_char (mysql dash comments) 58 | --- http_config eval: $::HttpConfig 59 | --- config 60 | location /t { 61 | content_by_lua_block { 62 | local lookup = require "resty.waf.transform" 63 | local value = "; DROP TABLE bobby--" 64 | local transform = lookup.lookup["remove_comments_char"]({ _pcre_flags = "" }, value) 65 | ngx.say(transform) 66 | } 67 | } 68 | --- request 69 | GET /t 70 | --- error_code: 200 71 | --- response_body 72 | ; DROP TABLE bobby 73 | --- no_error_log 74 | [error] 75 | 76 | === TEST 4: remove_comments_char (octothorpe comments) 77 | --- http_config eval: $::HttpConfig 78 | --- config 79 | location /t { 80 | content_by_lua_block { 81 | local lookup = require "resty.waf.transform" 82 | local value = "; DROP TABLE bobby#" 83 | local transform = lookup.lookup["remove_comments_char"]({ _pcre_flags = "" }, value) 84 | ngx.say(transform) 85 | } 86 | } 87 | --- request 88 | GET /t 89 | --- error_code: 200 90 | --- response_body 91 | ; DROP TABLE bobby 92 | --- no_error_log 93 | [error] 94 | 95 | === TEST 5: replace_comments 96 | --- http_config eval: $::HttpConfig 97 | --- config 98 | location /t { 99 | content_by_lua_block { 100 | local lookup = require "resty.waf.transform" 101 | local value = "UNION/***/SELECT" 102 | local transform = lookup.lookup["replace_comments"]({ _pcre_flags = "" }, value) 103 | ngx.say(transform) 104 | } 105 | } 106 | --- request 107 | GET /t 108 | --- error_code: 200 109 | --- response_body 110 | UNION SELECT 111 | --- no_error_log 112 | [error] 113 | 114 | -------------------------------------------------------------------------------- /t/unit/transform/04_string.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: lowercase 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local lookup = require "resty.waf.transform" 25 | local value = "HELLO WORLD" 26 | local transform = lookup.lookup["lowercase"]({}, value) 27 | ngx.say(transform) 28 | } 29 | } 30 | --- request 31 | GET /t 32 | --- error_code: 200 33 | --- response_body 34 | hello world 35 | --- no_error_log 36 | [error] 37 | 38 | === TEST 2: string length 39 | --- http_config eval: $::HttpConfig 40 | --- config 41 | location /t { 42 | content_by_lua_block { 43 | local lookup = require "resty.waf.transform" 44 | local value = "hello world" 45 | local transform = lookup.lookup["length"]({}, value) 46 | ngx.say(transform) 47 | } 48 | } 49 | --- request 50 | GET /t 51 | --- error_code: 200 52 | --- response_body 53 | 11 54 | --- no_error_log 55 | [error] 56 | 57 | === TEST 3: string length (number) 58 | --- http_config eval: $::HttpConfig 59 | --- config 60 | location /t { 61 | content_by_lua_block { 62 | local lookup = require "resty.waf.transform" 63 | local value = 8001 64 | local transform = lookup.lookup["length"]({}, value) 65 | ngx.say(transform) 66 | } 67 | } 68 | --- request 69 | GET /t 70 | --- error_code: 200 71 | --- response_body 72 | 4 73 | --- no_error_log 74 | [error] 75 | 76 | === TEST 4: string length (boolean) 77 | --- http_config eval: $::HttpConfig 78 | --- config 79 | location /t { 80 | content_by_lua_block { 81 | local lookup = require "resty.waf.transform" 82 | local value = true 83 | local transform = lookup.lookup["length"]({}, value) 84 | ngx.say(transform) 85 | } 86 | } 87 | --- request 88 | GET /t 89 | --- error_code: 200 90 | --- response_body 91 | 4 92 | --- no_error_log 93 | [error] 94 | 95 | -------------------------------------------------------------------------------- /t/unit/transform/06_hex.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: sql_hex_decode 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local lookup = require "resty.waf.transform" 25 | local value = "0x48656c6c6f2c20776f726c6421" 26 | local transform = lookup.lookup["sql_hex_decode"]({}, value) 27 | ngx.say(transform) 28 | } 29 | } 30 | --- request 31 | GET /t 32 | --- error_code: 200 33 | --- response_body 34 | Hello, world! 35 | --- no_error_log 36 | [error] 37 | 38 | === TEST 2: invalid sql_hex_decode 39 | --- http_config eval: $::HttpConfig 40 | --- config 41 | location /t { 42 | content_by_lua_block { 43 | local lookup = require "resty.waf.transform" 44 | local value = "48656c6c6f2c20776f726c6421" 45 | local transform = lookup.lookup["sql_hex_decode"]({}, value) 46 | ngx.say(transform) 47 | } 48 | } 49 | --- request 50 | GET /t 51 | --- error_code: 200 52 | --- response_body 53 | 48656c6c6f2c20776f726c6421 54 | --- no_error_log 55 | [error] 56 | 57 | === TEST 3: hex_decode 58 | --- http_config eval: $::HttpConfig 59 | --- config 60 | location /t { 61 | content_by_lua_block { 62 | local lookup = require "resty.waf.transform" 63 | local value = "48656c6c6f2c20776f726c6421" 64 | local transform = lookup.lookup["hex_decode"]({}, value) 65 | ngx.say(transform) 66 | } 67 | } 68 | --- request 69 | GET /t 70 | --- error_code: 200 71 | --- response_body 72 | Hello, world! 73 | --- no_error_log 74 | [error] 75 | 76 | === TEST 5: invalid hex_decode 77 | --- http_config eval: $::HttpConfig 78 | --- config 79 | location /t { 80 | content_by_lua_block { 81 | local util = require "resty.waf.util" 82 | local value = "this is not hex" 83 | ngx.say(util.hex_decode(value)) 84 | } 85 | } 86 | --- request 87 | GET /t 88 | --- error_code: 200 89 | --- response_body 90 | this is not hex 91 | --- no_error_log 92 | [error] 93 | 94 | === TEST 6: hex_encode 95 | --- http_config eval: $::HttpConfig 96 | --- config 97 | location /t { 98 | content_by_lua_block { 99 | local lookup = require "resty.waf.transform" 100 | local value = "Hello, world!" 101 | local transform = lookup.lookup["hex_encode"]({}, value) 102 | ngx.say(transform) 103 | } 104 | } 105 | --- request 106 | GET /t 107 | --- error_code: 200 108 | --- response_body 109 | 48656c6c6f2c20776f726c6421 110 | --- no_error_log 111 | [error] 112 | 113 | -------------------------------------------------------------------------------- /t/unit/transform/08_hash.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: md5 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local lookup = require "resty.waf.transform" 25 | local util = require "resty.waf.util" 26 | local value = "hello world" 27 | local transform = lookup.lookup["md5"]({}, value) 28 | ngx.say(util.hex_encode(transform)) 29 | } 30 | } 31 | --- request 32 | GET /t 33 | --- error_code: 200 34 | --- response_body 35 | 5eb63bbbe01eeed093cb22bb8f5acdc3 36 | --- no_error_log 37 | [error] 38 | 39 | === TEST 2: hex-encoded md5 matches ngx.md5 40 | --- http_config eval: $::HttpConfig 41 | --- config 42 | location /t { 43 | content_by_lua_block { 44 | local lookup = require "resty.waf.transform" 45 | local util = require "resty.waf.util" 46 | local value = "hello world" 47 | local transform = lookup.lookup["md5"]({}, value) 48 | ngx.say(util.hex_encode(transform) == ngx.md5(value)) 49 | } 50 | } 51 | --- request 52 | GET /t 53 | --- error_code: 200 54 | --- response_body 55 | true 56 | --- no_error_log 57 | [error] 58 | 59 | === TEST 3: sha1 60 | --- http_config eval: $::HttpConfig 61 | --- config 62 | location /t { 63 | content_by_lua_block { 64 | local lookup = require "resty.waf.transform" 65 | local util = require "resty.waf.util" 66 | local value = "hello world" 67 | local transform = lookup.lookup["sha1"]({}, value) 68 | ngx.say(util.hex_encode(transform)) 69 | } 70 | } 71 | --- request 72 | GET /t 73 | --- error_code: 200 74 | --- response_body 75 | 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed 76 | --- no_error_log 77 | [error] 78 | 79 | -------------------------------------------------------------------------------- /t/unit/transform/10_decode.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: js_decode 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local lookup = require "resty.waf.transform" 25 | local value = [[hello \u0022world\u005C\u0026]] 26 | local transform = lookup.lookup["js_decode"]( 27 | { _pcre_flags = "" }, 28 | value 29 | ) 30 | ngx.say(transform) 31 | } 32 | } 33 | --- request 34 | GET /t 35 | --- error_code: 200 36 | --- response_body 37 | hello "world\& 38 | --- no_error_log 39 | [error] 40 | 41 | === TEST 2: js_decode (no alterations) 42 | --- http_config eval: $::HttpConfig 43 | --- config 44 | location /t { 45 | content_by_lua_block { 46 | local lookup = require "resty.waf.transform" 47 | local value = [[hello "world\\&]] 48 | local transform = lookup.lookup["js_decode"]( 49 | { _pcre_flags = "" }, 50 | value 51 | ) 52 | ngx.say(transform) 53 | } 54 | } 55 | --- request 56 | GET /t 57 | --- error_code: 200 58 | --- response_body 59 | hello "world\& 60 | --- no_error_log 61 | [error] 62 | 63 | === TEST 3: css_decode 64 | --- http_config eval: $::HttpConfig 65 | --- config 66 | location /t { 67 | content_by_lua_block { 68 | local lookup = require "resty.waf.transform" 69 | local value = [[hello \0022world\005C\0026]] 70 | local transform = lookup.lookup["css_decode"]( 71 | { _pcre_flags = "" }, 72 | value 73 | ) 74 | ngx.say(transform) 75 | } 76 | } 77 | --- request 78 | GET /t 79 | --- error_code: 200 80 | --- response_body 81 | hello "world\& 82 | --- no_error_log 83 | [error] 84 | 85 | === TEST 4: css_decode (no alterations) 86 | --- http_config eval: $::HttpConfig 87 | --- config 88 | location /t { 89 | content_by_lua_block { 90 | local lookup = require "resty.waf.transform" 91 | local value = [[hello "world\\&]] 92 | local transform = lookup.lookup["css_decode"]( 93 | { _pcre_flags = "" }, 94 | value 95 | ) 96 | ngx.say(transform) 97 | } 98 | } 99 | --- request 100 | GET /t 101 | --- error_code: 200 102 | --- response_body 103 | hello "world\& 104 | --- no_error_log 105 | [error] 106 | 107 | === TEST 4: css_decode (modsec example) 108 | --- http_config eval: $::HttpConfig 109 | --- config 110 | location /t { 111 | content_by_lua_block { 112 | local lookup = require "resty.waf.transform" 113 | local value = [[ja\vascript]] 114 | local transform = lookup.lookup["css_decode"]( 115 | { _pcre_flags = "" }, 116 | value 117 | ) 118 | ngx.say(transform) 119 | } 120 | } 121 | --- request 122 | GET /t 123 | --- error_code: 200 124 | --- response_body 125 | javascript 126 | --- no_error_log 127 | [error] 128 | 129 | -------------------------------------------------------------------------------- /t/unit/util/01_table_keys.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Table with a single k/v pair 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location = /t { 23 | content_by_lua_block { 24 | local util = require "resty.waf.util" 25 | local a = { foo = "bar" } 26 | local b = util.table_keys(a) 27 | table.sort(b) 28 | for i in ipairs(b) do 29 | ngx.say(b[i]) 30 | end 31 | } 32 | } 33 | --- request 34 | GET /t 35 | --- error_code: 200 36 | --- response_body 37 | foo 38 | --- no_error_log 39 | [error] 40 | 41 | === TEST 2: Table with multiple k/v pairs 42 | --- http_config eval: $::HttpConfig 43 | --- config 44 | location = /t { 45 | content_by_lua_block { 46 | local util = require "resty.waf.util" 47 | local a = { foo = "bar", baz = "bat", qux = "frob" } 48 | local b = util.table_keys(a) 49 | table.sort(b) 50 | for i in ipairs(b) do 51 | ngx.say(b[i]) 52 | end 53 | } 54 | } 55 | --- request 56 | GET /t 57 | --- error_code: 200 58 | --- response_body 59 | baz 60 | foo 61 | qux 62 | --- no_error_log 63 | [error] 64 | 65 | === TEST 3: Table with nested k/v pairs 66 | --- http_config eval: $::HttpConfig 67 | --- config 68 | location = /t { 69 | content_by_lua_block { 70 | local util = require "resty.waf.util" 71 | local a = { foo = { "bar", "baz", "bat" }, qux = { "frob" } } 72 | local b = util.table_keys(a) 73 | table.sort(b) 74 | for i in ipairs(b) do 75 | ngx.say(b[i]) 76 | end 77 | } 78 | } 79 | --- request 80 | GET /t 81 | --- error_code: 200 82 | --- response_body 83 | foo 84 | qux 85 | --- no_error_log 86 | [error] 87 | 88 | === TEST 4: Table with redundant keys 89 | --- http_config eval: $::HttpConfig 90 | --- config 91 | location = /t { 92 | content_by_lua_block { 93 | local util = require "resty.waf.util" 94 | local a = { foo = "bar", foo = "baz" } 95 | local b = util.table_keys(a) 96 | table.sort(b) 97 | for i in ipairs(b) do 98 | ngx.say(b[i]) 99 | end 100 | } 101 | } 102 | --- request 103 | GET /t 104 | --- error_code: 200 105 | --- response_body 106 | foo 107 | --- no_error_log 108 | [error] 109 | 110 | === TEST 5: Not a table 111 | --- http_config eval: $::HttpConfig 112 | --- config 113 | location = /t { 114 | content_by_lua_block { 115 | local util = require "resty.waf.util" 116 | local a = "foo, bar" 117 | local b = util.table_keys(a) 118 | table.sort(b) 119 | for i in ipairs(b) do 120 | ngx.say(b[i]) 121 | end 122 | } 123 | } 124 | --- request 125 | GET /t 126 | --- error_code: 500 127 | --- error_log 128 | fatal_fail 129 | was given to table_keys! 130 | 131 | -------------------------------------------------------------------------------- /t/unit/util/02_table_values.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Table with a single k/v pair 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location = /t { 23 | content_by_lua_block { 24 | local util = require "resty.waf.util" 25 | local a = { foo = "bar" } 26 | local b = util.table_values(a) 27 | table.sort(b) 28 | for i in ipairs(b) do 29 | ngx.say(b[i]) 30 | end 31 | } 32 | } 33 | --- request 34 | GET /t 35 | --- error_code: 200 36 | --- response_body 37 | bar 38 | --- no_error_log 39 | [error] 40 | 41 | === TEST 2: Table with multiple k/v pairs 42 | --- http_config eval: $::HttpConfig 43 | --- config 44 | location = /t { 45 | content_by_lua_block { 46 | local util = require "resty.waf.util" 47 | local a = { foo = "bar", baz = "bat", qux = "frob" } 48 | local b = util.table_values(a) 49 | table.sort(b) 50 | for i in ipairs(b) do 51 | ngx.say(b[i]) 52 | end 53 | } 54 | } 55 | --- request 56 | GET /t 57 | --- error_code: 200 58 | --- response_body 59 | bar 60 | bat 61 | frob 62 | --- no_error_log 63 | [error] 64 | 65 | === TEST 3: Table with nested k/v pairs 66 | --- http_config eval: $::HttpConfig 67 | --- config 68 | location = /t { 69 | content_by_lua_block { 70 | local util = require "resty.waf.util" 71 | local a = { foo = { "bar", "baz", "bat" }, qux = { "frob" } } 72 | local b = util.table_values(a) 73 | table.sort(b) 74 | for i in ipairs(b) do 75 | ngx.say(b[i]) 76 | end 77 | } 78 | } 79 | --- request 80 | GET /t 81 | --- error_code: 200 82 | --- response_body 83 | bar 84 | bat 85 | baz 86 | frob 87 | --- no_error_log 88 | [error] 89 | 90 | === TEST 4: Table with redundant keys 91 | # n.b. the ngx API will not present data in this fashion 92 | --- http_config eval: $::HttpConfig 93 | --- config 94 | location = /t { 95 | content_by_lua_block { 96 | local util = require "resty.waf.util" 97 | local a = { foo = "bar", foo = "baz" } 98 | local b = util.table_values(a) 99 | table.sort(b) 100 | for i in ipairs(b) do 101 | ngx.say(b[i]) 102 | end 103 | } 104 | } 105 | --- request 106 | GET /t 107 | --- error_code: 200 108 | --- response_body 109 | baz 110 | --- no_error_log 111 | [error] 112 | 113 | === TEST 5: Not a table 114 | --- http_config eval: $::HttpConfig 115 | --- config 116 | location = /t { 117 | content_by_lua_block { 118 | local util = require "resty.waf.util" 119 | local a = "foo, bar" 120 | local b = util.table_values(a) 121 | table.sort(b) 122 | for i in ipairs(b) do 123 | ngx.say(b[i]) 124 | end 125 | } 126 | } 127 | --- request 128 | GET /t 129 | --- error_code: 500 130 | --- error_log 131 | fatal_fail 132 | was given to table_values! 133 | 134 | -------------------------------------------------------------------------------- /t/unit/util/03_table_has_key.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Existing key (string) 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local util = require "resty.waf.util" 25 | local t = { foo = "bar", qux = "frob" } 26 | local val = util.table_has_key("foo", t) 27 | ngx.say(val) 28 | } 29 | } 30 | --- request 31 | GET /t 32 | --- error_code: 200 33 | --- response_body 34 | true 35 | --- no_error_log 36 | [error] 37 | 38 | === TEST 2: Existing key (integer) 39 | --- http_config eval: $::HttpConfig 40 | --- config 41 | location /t { 42 | content_by_lua_block { 43 | local util = require "resty.waf.util" 44 | local t = { "foo", "bar", "qux", "frob" } 45 | local val = util.table_has_key(1, t) 46 | ngx.say(val) 47 | } 48 | } 49 | --- request 50 | GET /t 51 | --- error_code: 200 52 | --- response_body 53 | true 54 | --- no_error_log 55 | [error] 56 | 57 | === TEST 3: Non existing key (string) 58 | --- http_config eval: $::HttpConfig 59 | --- config 60 | location /t { 61 | content_by_lua_block { 62 | local util = require "resty.waf.util" 63 | local t = { foo = "bar", qux = "frob" } 64 | local val = util.table_has_key("baz", t) 65 | ngx.say(val) 66 | } 67 | } 68 | --- request 69 | GET /t 70 | --- error_code: 200 71 | --- response_body 72 | false 73 | --- no_error_log 74 | [error] 75 | 76 | === TEST 4: Non existing key (integer) 77 | --- http_config eval: $::HttpConfig 78 | --- config 79 | location /t { 80 | content_by_lua_block { 81 | local util = require "resty.waf.util" 82 | local t = { "foo", "bar", "qux", "frob" } 83 | local val = util.table_has_key(0, t) 84 | ngx.say(val) 85 | } 86 | } 87 | --- request 88 | GET /t 89 | --- error_code: 200 90 | --- response_body 91 | false 92 | --- no_error_log 93 | [error] 94 | 95 | === TEST 5: Haystack is not a table 96 | --- http_config eval: $::HttpConfig 97 | --- config 98 | location /t { 99 | content_by_lua_block { 100 | local util = require "resty.waf.util" 101 | local t = "foo, bar" 102 | local val = util.table_has_key("foo", t) 103 | ngx.say(val) 104 | } 105 | } 106 | --- request 107 | GET /t 108 | --- error_code: 500 109 | --- error_log 110 | fatal_fail 111 | Cannot search for a needle when haystack is type string 112 | -------------------------------------------------------------------------------- /t/unit/util/04_table_has_value.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Existing value (string) 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local util = require "resty.waf.util" 25 | local t = { foo = "bar", qux = "frob" } 26 | local val = util.table_has_value("frob", t) 27 | ngx.say(val) 28 | } 29 | } 30 | --- request 31 | GET /t 32 | --- error_code: 200 33 | --- response_body 34 | true 35 | --- no_error_log 36 | [error] 37 | 38 | === TEST 2: Existing value (integer) 39 | --- http_config eval: $::HttpConfig 40 | --- config 41 | location /t { 42 | content_by_lua_block { 43 | local util = require "resty.waf.util" 44 | local t = { 4, 10, 7.2, 1, 99, "foo", "bar" } 45 | local val = util.table_has_value(1, t) 46 | ngx.say(val) 47 | } 48 | } 49 | --- request 50 | GET /t 51 | --- error_code: 200 52 | --- response_body 53 | true 54 | --- no_error_log 55 | [error] 56 | 57 | === TEST 3: Non existing value (string) 58 | --- http_config eval: $::HttpConfig 59 | --- config 60 | location /t { 61 | content_by_lua_block { 62 | local util = require "resty.waf.util" 63 | local t = { foo = "bar", qux = "frob" } 64 | local val = util.table_has_value("baz", t) 65 | ngx.say(val) 66 | } 67 | } 68 | --- request 69 | GET /t 70 | --- error_code: 200 71 | --- response_body 72 | false 73 | --- no_error_log 74 | [error] 75 | 76 | === TEST 4: Non existing value (integer) 77 | --- http_config eval: $::HttpConfig 78 | --- config 79 | location /t { 80 | content_by_lua_block { 81 | local util = require "resty.waf.util" 82 | local t = { 4, 10, 7.2, 1, 99, "foo", "bar" } 83 | local val = util.table_has_value(0, t) 84 | ngx.say(val) 85 | } 86 | } 87 | --- request 88 | GET /t 89 | --- error_code: 200 90 | --- response_body 91 | false 92 | --- no_error_log 93 | [error] 94 | 95 | === TEST 5: Haystack is not a table 96 | --- http_config eval: $::HttpConfig 97 | --- config 98 | location /t { 99 | content_by_lua_block { 100 | local util = require "resty.waf.util" 101 | local t = "foo, bar" 102 | local val = util.table_has_value("foo", t) 103 | ngx.say(val) 104 | } 105 | } 106 | --- request 107 | GET /t 108 | --- error_code: 500 109 | --- error_log 110 | fatal_fail 111 | Cannot search for a needle when haystack is type string 112 | -------------------------------------------------------------------------------- /t/unit/util/06_hex.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: hex_decode 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local util = require "resty.waf.util" 25 | local value = "48656c6c6f2c20776f726c6421" 26 | ngx.say(util.hex_decode(value)) 27 | } 28 | } 29 | --- request 30 | GET /t 31 | --- error_code: 200 32 | --- response_body 33 | Hello, world! 34 | --- no_error_log 35 | [error] 36 | 37 | === TEST 2: invalid hex_decode 38 | --- http_config eval: $::HttpConfig 39 | --- config 40 | location /t { 41 | content_by_lua_block { 42 | local util = require "resty.waf.util" 43 | local value = "this is not hex" 44 | ngx.say(util.hex_decode(value)) 45 | } 46 | } 47 | --- request 48 | GET /t 49 | --- error_code: 200 50 | --- response_body 51 | this is not hex 52 | --- no_error_log 53 | [error] 54 | 55 | === TEST 3: hex_encode 56 | --- http_config eval: $::HttpConfig 57 | --- config 58 | location /t { 59 | content_by_lua_block { 60 | local util = require "resty.waf.util" 61 | local value = "Hello, world!" 62 | ngx.say(util.hex_encode(value)) 63 | } 64 | } 65 | --- request 66 | GET /t 67 | --- error_code: 200 68 | --- response_body 69 | 48656c6c6f2c20776f726c6421 70 | --- no_error_log 71 | [error] 72 | 73 | -------------------------------------------------------------------------------- /t/unit/util/07_build_rbl_query.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: build a valid query 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location /t { 23 | content_by_lua_block { 24 | local util = require "resty.waf.util" 25 | local ip = "127.0.0.1" 26 | local rbl_srv = "sbl-xbl.spamhaus.org" 27 | 28 | ngx.say(util.build_rbl_query(ip, rbl_srv)) 29 | } 30 | } 31 | --- request 32 | GET /t 33 | --- error_code: 200 34 | --- response_body 35 | 1.0.0.127.sbl-xbl.spamhaus.org 36 | --- no_error_log 37 | [error] 38 | 39 | === TEST 2: build an invalid query (bad ip type) 40 | --- http_config eval: $::HttpConfig 41 | --- config 42 | location /t { 43 | content_by_lua_block { 44 | local util = require "resty.waf.util" 45 | local ip = { "127.0.0.1" } 46 | local rbl_srv = "sbl-xbl.spamhaus.org" 47 | 48 | ngx.say(util.build_rbl_query(ip, rbl_srv)) 49 | } 50 | } 51 | --- request 52 | GET /t 53 | --- error_code: 200 54 | --- response_body 55 | false 56 | --- no_error_log 57 | [error] 58 | 59 | === TEST 3: build an invalid query (invalid ip string) 60 | --- http_config eval: $::HttpConfig 61 | --- config 62 | location /t { 63 | content_by_lua_block { 64 | local util = require "resty.waf.util" 65 | local ip = "im.not.an.ip" 66 | local rbl_srv = "sbl-xbl.spamhaus.org" 67 | 68 | ngx.say(util.build_rbl_query(ip, rbl_srv)) 69 | } 70 | } 71 | --- request 72 | GET /t 73 | --- error_code: 200 74 | --- response_body 75 | false 76 | --- no_error_log 77 | [error] 78 | 79 | === TEST 4: build a valid query with an invalid IP 80 | # we don't check if we're given a -valid- IPv4 address, because our query to the rbl server should just return false 81 | --- http_config eval: $::HttpConfig 82 | --- config 83 | location /t { 84 | content_by_lua_block { 85 | local util = require "resty.waf.util" 86 | local ip = "999.999.999.999" 87 | local rbl_srv = "sbl-xbl.spamhaus.org" 88 | 89 | ngx.say(util.build_rbl_query(ip, rbl_srv)) 90 | } 91 | } 92 | --- request 93 | GET /t 94 | --- error_code: 200 95 | --- response_body 96 | 999.999.999.999.sbl-xbl.spamhaus.org 97 | --- no_error_log 98 | [error] 99 | 100 | -------------------------------------------------------------------------------- /t/unit/util/08_parse_ruleset.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Parse a JSON string successfully 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location = /t { 23 | content_by_lua_block { 24 | local util = require "resty.waf.util" 25 | local str = [=[{"foo":"bar","baz":["bat","qux"]}]=] 26 | 27 | local parse, err = util.parse_ruleset(str) 28 | 29 | ngx.say(parse.foo) 30 | ngx.say(parse.baz[1]) 31 | ngx.say(type(parse)) 32 | ngx.say(err) 33 | } 34 | } 35 | --- request 36 | GET /t 37 | --- error_code: 200 38 | --- response_body 39 | bar 40 | bat 41 | table 42 | nil 43 | --- no_error_log 44 | [error] 45 | 46 | === TEST 2: Parse a bad JSON string 47 | --- http_config eval: $::HttpConfig 48 | --- config 49 | location = /t { 50 | content_by_lua_block { 51 | local util = require "resty.waf.util" 52 | local str = [=[{"foo":"bar","baz":["bat","qux]}]=] 53 | 54 | local parse, err = util.parse_ruleset(str) 55 | 56 | ngx.say(type(parse)) 57 | ngx.say(err) 58 | } 59 | } 60 | --- request 61 | GET /t 62 | --- error_code: 200 63 | --- response_body 64 | nil 65 | could not decode {"foo":"bar","baz":["bat","qux]} 66 | --- no_error_log 67 | [error] 68 | 69 | -------------------------------------------------------------------------------- /t/unit/util/09_load_ruleset_file.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | repeat_each(3); 5 | plan tests => repeat_each() * 3 * blocks(); 6 | 7 | my $pwd = cwd(); 8 | 9 | our $HttpConfig = qq{ 10 | lua_package_path "$pwd/lib/?.lua;$pwd/t/?.lua;;"; 11 | lua_package_cpath "$pwd/lib/?.lua;;"; 12 | }; 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Load a ruleset file 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location = /t { 23 | content_by_lua_block { 24 | local util = require "resty.waf.util" 25 | 26 | local ruleset, err = util.load_ruleset_file("extra") 27 | 28 | ngx.say(type(ruleset)) 29 | ngx.say(err) 30 | } 31 | } 32 | --- request 33 | GET /t 34 | --- error_code: 200 35 | --- response_body 36 | table 37 | nil 38 | --- no_error_log 39 | [error] 40 | 41 | === TEST 2: Fail to load a non-existent ruleset 42 | --- http_config eval: $::HttpConfig 43 | --- config 44 | location = /t { 45 | content_by_lua_block { 46 | local util = require "resty.waf.util" 47 | 48 | local parse, err = util.load_ruleset_file("dne") 49 | 50 | ngx.say(type(parse)) 51 | ngx.say(err) 52 | } 53 | } 54 | --- request 55 | GET /t 56 | --- error_code: 200 57 | --- response_body 58 | nil 59 | could not find dne 60 | --- no_error_log 61 | [error] 62 | 63 | === TEST 3: Fail to load an invalid ruleset 64 | --- http_config eval: $::HttpConfig 65 | --- config 66 | location = /t { 67 | content_by_lua_block { 68 | local util = require "resty.waf.util" 69 | 70 | local parse, err = util.load_ruleset_file("extra_broken") 71 | 72 | ngx.say(type(parse)) 73 | ngx.print(err) 74 | } 75 | } 76 | --- request 77 | GET /t 78 | --- error_code: 200 79 | --- response_body 80 | nil 81 | could not decode { 82 | --- no_error_log 83 | [error] 84 | 85 | -------------------------------------------------------------------------------- /t/unit/util/12_table_append.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Append b with a few elements to an empty a 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location = /t { 23 | content_by_lua_block { 24 | local util = require "resty.waf.util" 25 | 26 | local a = {} 27 | local b = { "foo", "bar" } 28 | 29 | util.table_append(a, b) 30 | 31 | ngx.say(table.concat(a, "\n")) 32 | } 33 | } 34 | --- request 35 | GET /t 36 | --- error_code: 200 37 | --- response_body 38 | foo 39 | bar 40 | --- no_error_log 41 | [error] 42 | 43 | === TEST 2: Append b with a few elements to an existing a 44 | --- http_config eval: $::HttpConfig 45 | --- config 46 | location = /t { 47 | content_by_lua_block { 48 | local util = require "resty.waf.util" 49 | 50 | local a = { "baz", "bat" } 51 | local b = { "foo", "bar" } 52 | 53 | util.table_append(a, b) 54 | 55 | ngx.say(table.concat(a, "\n")) 56 | } 57 | } 58 | --- request 59 | GET /t 60 | --- error_code: 200 61 | --- response_body 62 | baz 63 | bat 64 | foo 65 | bar 66 | --- no_error_log 67 | [error] 68 | 69 | === TEST 3: Append b as a non-table to an empty a 70 | --- http_config eval: $::HttpConfig 71 | --- config 72 | location = /t { 73 | content_by_lua_block { 74 | local util = require "resty.waf.util" 75 | 76 | local a = {} 77 | local b = "foo" 78 | 79 | util.table_append(a, b) 80 | 81 | ngx.say(table.concat(a, "\n")) 82 | } 83 | } 84 | --- request 85 | GET /t 86 | --- error_code: 200 87 | --- response_body 88 | foo 89 | --- no_error_log 90 | [error] 91 | 92 | === TEST 3: Append b as a non-table to an existing a 93 | --- http_config eval: $::HttpConfig 94 | --- config 95 | location = /t { 96 | content_by_lua_block { 97 | local util = require "resty.waf.util" 98 | 99 | local a = { "bat", "baz"} 100 | local b = "foo" 101 | 102 | util.table_append(a, b) 103 | 104 | ngx.say(table.concat(a, "\n")) 105 | } 106 | } 107 | --- request 108 | GET /t 109 | --- error_code: 200 110 | --- response_body 111 | bat 112 | baz 113 | foo 114 | --- no_error_log 115 | [error] 116 | 117 | -------------------------------------------------------------------------------- /t/unit/util/13_rule_exception.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua; 2 | use Cwd qw(cwd); 3 | 4 | my $pwd = cwd(); 5 | 6 | our $HttpConfig = qq{ 7 | lua_package_path "$pwd/lib/?.lua;;"; 8 | lua_package_cpath "$pwd/lib/?.lua;;"; 9 | }; 10 | 11 | repeat_each(3); 12 | plan tests => repeat_each() * 3 * blocks(); 13 | 14 | no_shuffle(); 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: Build the lookup table though string matches 20 | --- http_config eval: $::HttpConfig 21 | --- config 22 | location = /t { 23 | content_by_lua_block { 24 | local util = require "resty.waf.util" 25 | 26 | local exception_table = { 27 | msgs = { 28 | foo = { 1, 2, 3 }, 29 | bar = { 4, 5, 6 }, 30 | }, 31 | tags = { 32 | baz = { 1, 2, 3 }, 33 | bat = { 4, 5, 6 }, 34 | }, 35 | meta_ids = {} 36 | } 37 | 38 | local rule = { 39 | id = 12345, 40 | exceptions = { "foo", "bar" } 41 | } 42 | 43 | util.rule_exception(exception_table, rule) 44 | 45 | ngx.say(table.concat(exception_table.meta_ids[12345], ", ")) 46 | } 47 | } 48 | --- request 49 | GET /t 50 | --- error_code: 200 51 | --- response_body 52 | 1, 2, 3, 4, 5, 6 53 | --- no_error_log 54 | [error] 55 | 56 | === TEST 2: Build the lookup table though regex (some duplicates)` 57 | --- http_config eval: $::HttpConfig 58 | --- config 59 | location = /t { 60 | content_by_lua_block { 61 | local util = require "resty.waf.util" 62 | 63 | local exception_table = { 64 | msgs = { 65 | foo = { 1, 2, 3 }, 66 | bar = { 4, 5, 6 }, 67 | }, 68 | tags = { 69 | baz = { 1, 2, 3 }, 70 | bat = { 4, 5, 6 }, 71 | }, 72 | meta_ids = {} 73 | } 74 | 75 | local rule = { 76 | id = 12345, 77 | exceptions = { "^b" } 78 | } 79 | 80 | util.rule_exception(exception_table, rule) 81 | 82 | ngx.say(table.concat(exception_table.meta_ids[12345], ", ")) 83 | } 84 | } 85 | --- request 86 | GET /t 87 | --- error_code: 200 88 | --- response_body 89 | 4, 5, 6, 4, 5, 6, 1, 2, 3 90 | --- no_error_log 91 | [error] 92 | 93 | === TEST 3: Do nothing if the rule has no exceptions 94 | --- http_config eval: $::HttpConfig 95 | --- config 96 | location = /t { 97 | content_by_lua_block { 98 | local util = require "resty.waf.util" 99 | 100 | local exception_table = { 101 | msgs = { 102 | foo = { 1, 2, 3 }, 103 | bar = { 4, 5, 6 }, 104 | }, 105 | tags = { 106 | baz = { 1, 2, 3 }, 107 | bat = { 4, 5, 6 }, 108 | }, 109 | meta_ids = {} 110 | } 111 | 112 | local rule = { 113 | id = 12345 114 | } 115 | 116 | util.rule_exception(exception_table, rule) 117 | 118 | ngx.say(type(exception_table.meta_ids[12345])) 119 | } 120 | } 121 | --- request 122 | GET /t 123 | --- error_code: 200 124 | --- response_body 125 | nil 126 | --- no_error_log 127 | [error] 128 | 129 | -------------------------------------------------------------------------------- /tools/debug-macro.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" == "clean" ]; then 4 | sed -i -r "s/([[:blank:]]+)if self\._debug == true then ngx\.log\(.*?'\] ', (.*)\) end/\1--_LOG_\2/g" ./lib/resty/waf.lua 5 | find ./lib/resty/waf/ -type f -exec sed -i -r "s/([[:blank:]]+)if waf\._debug == true then ngx\.log\(.*?'\] ', (.*)\) end/\1--_LOG_\2/g" {} \; 6 | else 7 | sed -i -r "s/--_LOG_(.*)/if self._debug == true then ngx.log\(self._debug_log_level, '[', self.transaction_id, '] ', \1\) end/g" ./lib/resty/waf.lua 8 | find ./lib/resty/waf/ -type f -exec sed -i -r "s/--_LOG_(.*)/if waf._debug == true then ngx.log\(waf._debug_log_level, '[', waf.transaction_id, '] ', \1\) end/g" {} \; 9 | fi 10 | -------------------------------------------------------------------------------- /tools/modsec2lua-resty-waf.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Getopt::Long qw(:config bundling no_ignore_case); 7 | use JSON; 8 | 9 | use lib 'tools'; 10 | use Modsec2LRW qw(:translate); 11 | 12 | sub usage { 13 | print <<"_EOF"; 14 | Usage $0 < [hqspP] 15 | Translate ModSecurity configs to lua-resty-waf rulesets, reading from standard input and writing to standard output. 16 | 17 | -h|--help Print this help 18 | -q|--quiet Be quite when translating (do not print imcompatible chains) 19 | -s|--silent Be silent when translating (do not print any information apart from translated rules) 20 | -p|--path Provide an optional path to search for *FromFile data files. If not given, the current dir will be used 21 | -P|--pretty Pretty-print translated rulesets 22 | -f|--force Do not die on failed collection translations 23 | 24 | _EOF 25 | exit 1; 26 | } 27 | 28 | 29 | sub main { 30 | my ($path, $quiet, $silent, $pretty, $force, @input); 31 | 32 | GetOptions( 33 | 'q|quiet' => \$quiet, 34 | 's|silent' => \$silent, 35 | 'p|path=s' => \$path, 36 | 'P|pretty' => \$pretty, 37 | 'f|force' => \$force, 38 | 'h|help' => sub { usage(); }, 39 | ) or usage(); 40 | 41 | # silent implies quiet 42 | $quiet = 1 if $silent; 43 | 44 | while (<>) { 45 | chomp; 46 | push @input, $_; 47 | } 48 | 49 | # ModSecurity ruleset parsing 50 | # clean the input and build an array of tokens 51 | my @parsed_lines = map { parse_tokens(tokenize($_)) } clean_input(@input); 52 | 53 | # ModSecurity knows where it lives in a chain 54 | # via pointer arithmetic and internal state handling 55 | # we need to be a little more obvious about chain 56 | # definitions for the purposes of translation 57 | my @modsec_chains = build_chains(@parsed_lines); 58 | 59 | # do the actual translation 60 | my $lua_resty_waf_chains = translate_chains({ 61 | chains => \@modsec_chains, 62 | path => $path, 63 | quiet => $quiet, 64 | silent => $silent, 65 | force => $force, 66 | }); 67 | 68 | printf "%s\n", 69 | to_json( 70 | $lua_resty_waf_chains, 71 | { 72 | pretty => $pretty ? 1 : 0, 73 | canonical => 1, 74 | } 75 | ); 76 | } 77 | 78 | main(); 79 | --------------------------------------------------------------------------------