开源软件名称(OpenSource Name):p0pr0ck5/lua-resty-waf开源软件地址(OpenSource Url):https://github.com/p0pr0ck5/lua-resty-waf开源编程语言(OpenSource Language):Perl 73.8%开源软件介绍(OpenSource Introduction):Namelua-resty-waf - High-performance WAF built on the OpenResty stack Table of Contents
StatusNOTE: Descriptionlua-resty-waf is a reverse proxy WAF built using the OpenResty stack. It uses the Nginx Lua API to analyze HTTP request information and process against a flexible rule structure. lua-resty-waf is distributed with a ruleset that mimics the ModSecurity CRS, as well as a few custom rules built during initial development and testing, and a small virtual patchset for emerging threats. Additionally, lua-resty-waf is distributed with tooling to automatically translate existing ModSecurity rules, allowing users to extend lua-resty-waf implementation without the need to learn a new rule syntax. lua-resty-waf was initially developed by Robert Paprocki for his Master's thesis at Western Governor's University. Requirementslua-resty-waf requires several third-party resty lua modules, though these are all packaged with lua-resty-waf, and thus do not need to be installed separately. It is recommended to install lua-resty-waf on a system running the OpenResty software bundle; lua-resty-waf has not been tested on platforms built using separate Nginx source and Nginx Lua module packages. For optimal regex compilation performance, it is recommended to build Nginx/OpenResty with a version of PCRE that supports JIT compilation. If your OS does not provide this, you can build JIT-capable PCRE directly into your Nginx/OpenResty build. To do this, reference the path to the PCRE source in the # ./configure --with-pcre=/path/to/pcre/source --with-pcre-jit You can download the PCRE source from the PCRE website. See also this blog post for a step-by-step walkthrough on building OpenResty with a JIT-enabled PCRE library. Performancelua-resty-waf was designed with efficiency and scalability in mind. It leverages Nginx's asynchronous processing model and an efficient design to process each transaction as quickly as possible. Load testing has show that deployments implementing all provided rulesets, which are designed to mimic the logic behind the ModSecurity CRS, process transactions in roughly 300-500 microseconds per request; this equals the performance advertised by Cloudflare's WAF. Tests were run on a reasonable hardware stack (E3-1230 CPU, 32 GB RAM, 2 x 840 EVO in RAID 0), maxing at roughly 15,000 requests per second. See this blog post for more information. lua-resty-waf workload is almost exclusively CPU bound. Memory footprint in the Lua VM (excluding persistent storage backed by InstallationA simple Makefile is provided:
Alternatively, install via Luarocks:
lua-resty-waf makes use of the OPM package manager, available in modern OpenResty distributions. The client OPM tools requires that the Note that by default lua-resty-waf runs in SIMULATE mode, to prevent immediately affecting an application; users who wish to enable rule actions must explicitly set the operational mode to ACTIVE. Synopsishttp {
init_by_lua_block {
-- use resty.core for performance improvement, see the status note above
require "resty.core"
-- require the base module
local lua_resty_waf = require "resty.waf"
-- perform some preloading and optimization
lua_resty_waf.init()
}
server {
location / {
access_by_lua_block {
local lua_resty_waf = require "resty.waf"
local waf = lua_resty_waf:new()
-- define options that will be inherited across all scopes
waf:set_option("debug", true)
waf:set_option("mode", "ACTIVE")
-- this may be desirable for low-traffic or testing sites
-- by default, event logs are not written until the buffer is full
-- for testing, flush the log buffer every 5 seconds
--
-- this is only necessary when configuring a remote TCP/UDP
-- socket server for event logs. otherwise, this is ignored
waf:set_option("event_log_periodic_flush", 5)
-- run the firewall
waf:exec()
}
header_filter_by_lua_block {
local lua_resty_waf = require "resty.waf"
-- note that options set in previous handlers (in the same scope)
-- do not need to be set again
local waf = lua_resty_waf:new()
waf:exec()
}
body_filter_by_lua_block {
local lua_resty_waf = require "resty.waf"
local waf = lua_resty_waf:new()
waf:exec()
}
log_by_lua_block {
local lua_resty_waf = require "resty.waf"
local waf = lua_resty_waf:new()
waf:exec()
}
}
}
} Public Functionslua-resty-waf.load_secrules()Translate and initialize a ModSecurity SecRules file from disk. Note that this still requires the ruleset to be added via add_ruleset (the basename of the file must be given as the key). Example: http {
init_by_lua_block {
local lua_resty_waf = require "resty.waf"
-- this translates and calculates a ruleset called 'ruleset_name'
local ok, errs = pcall(function()
lua_resty_waf.load_secrules("/path/to/secrules/ruleset_name")
end)
-- errs is an array-like table
if errs then
for i = 1, #errs do
ngx.log(ngx.ERR, errs[i])
end
end
}
server {
location / {
access_by_lua_block {
local lua_resty_waf = require "resty.waf"
local waf = lua_resty_waf:new()
-- in order to use the loaded ruleset, it must be added via
-- the 'add_ruleset' option
waf:set_option("add_ruleset", "ruleset_name")
}
}
}
} Additionally,
This function can also take a third option as a table to catch translation errors, for later processing. If this option is not present or a not a table, translation errors will instead be logged to the error log. lua-resty-waf.init()Perform some pre-computation of rules and rulesets, based on what's been made available via the default distributed rulesets. It's recommended, but not required, to call this function (not doing so will result in a small performance penalty). This function should never be called outside this scope. Example: http {
init_by_lua_block {
local lua_resty_waf = require "resty.waf"
lua_resty_waf.init()
}
} Public Methodslua-resty-waf:new()Instantiate a new instance of lua-resty-waf. You must call this in every request handler phase you wish to run lua-resty-waf, and use the return result to call further object methods. Example: location / {
access_by_lua_block {
local lua_resty_waf = require "resty.waf"
local waf = lua_resty_waf:new()
}
} lua-resty-waf:set_option()Configure an option on a per-scope basis. Example: location / {
access_by_lua_block {
local lua_resty_waf = require "resty.waf"
local waf = lua_resty_waf:new()
-- enable debug logging only for this scope
waf:set_option("debug", true)
}
} lua-resty-waf:set_var()Define a transaction variable (stored in the Example: location / {
access_by_lua_block {
local lua_resty_waf = require "resty.waf"
local waf = lua_resty_waf:new()
waf:set_var("FOO", "bar")
}
} Note that as with any other ModSecurity rule, the existence of a variable bears no functional change to WAF processing; it is the responsibility of the rule author to understand and use lua-resty-waf:sieve_rule()Define a collection exclusion for a given rule. Example: location / {
access_by_lua_block {
local lua_resty_waf = require "resty.waf"
local waf = lua_resty_waf:new()
local sieves = {
{
type = "ARGS",
elts = "foo",
action = "ignore",
}
}
waf:sieve_rule("12345", sieves)
}
} See the rule sieves wiki page for details and advanced usage examples. lua-resty-waf:exec()Run the rule engine. By default, the engine is executed according to the currently running phase. An optional table may be passed, allowing users to "mock" execution of a different phase. Example: location / {
access_by_lua_block {
local lua_resty_waf = require "resty.waf"
local waf = lua_resty_waf:new()
-- execute according to access phase collections and rules
waf:exec()
}
content_by_lua_block {
local lua_resty_waf = require "waf"
local waf = lua_resty_waf:new()
-- execute header_filter rules, passing in a table of additional collections
-- this assumes the 'request_headers' and 'status' Lua variables were
-- declared and initialized elsewhere
local opts = {
phase = 'header_filter',
collections = {
REQUEST_HEADERS = request_headers,
STATUS = status,
}
}
waf:exec(opts)
}
} lua-resty-waf:write_log_events()Write any audit log entries that were generated from the transaction. This is only optional when Example: location / {
log_by_lua_block {
local lua_resty_waf = require "resty.waf"
local waf = lua_resty_waf:new()
-- write out any event log entries to the
-- configured target, if applicable
waf:write_log_events()
}
} Optionsadd_rulesetDefault: none Adds an additional ruleset to be used during processing. This allows users to implement custom rulesets without stomping over the included rules directory. Additional rulesets must reside within a folder called "rules" that lives within the Example: http {
-- the rule file 50000.json must live at
-- /path/to/extra/rulesets/rules/50000.json
lua_package_path '/path/to/extra/rulesets/?.lua;;';
server {
location / {
access_by_lua_block {
waf:set_option("add_ruleset", "50000_extra_rules")
}
}
}
} Multiple rulesets may be added by passing a table of values to add_ruleset_stringDefault: none Adds an additional ruleset to be used during processing. This allows users to implement custom rulesets without stomping over the included rules directory. Rulesets are defined inline as a Lua string, in the form of a translated ruleset JSON structure. Example: location / {
access_by_lua_block {
waf:set_option("add_ruleset_string", "70000_extra_rules", [=[{"access":[{"action":"DENY","id":73,"operator":"REGEX","opts":{},"pattern":"foo","vars":[{"parse":{"values":1},"type":"REQUEST_ARGS"}]}],"body_filter":[],"header_filter":[]}]=])
}
} Note that ruleset names are sorted before processing, and must be given as strings. Rulesets are processed in a low-to-high sorted order. allow_unknown_content_typesDefault: false Instructs lua-resty-waf to continue processing the request when a Content-Type header has been sent that is not in the Example: location / {
access_by_lua_block {
waf:set_option("allow_unknown_content_types", true)
}
} allowed_content_typesDefault: none Defines one or more Content-Type headers that will be allowed, in addition to the default Content-Types Example: location / {
access_by_lua_block {
-- define a single allowed Content-Type value
waf:set_option("allowed_content_types", "text/xml")
-- defines multiple allowed Content-Type values
waf:set_option("allowed_content_types", { "text/html", "text/json", "application/json" })
}
} Note that mutiple debugDefault: false Disables/enables debug logging. Debug log statements are printed to the error_log. Note that debug logging is very expensive and should not be used in production environments. Example: location / {
access_by_lua_block {
waf:set_option("debug", true)
}
} debug_log_levelDefault: ngx.INFO Sets the nginx log level constant used for debug logging. Example: location / {
access_by_lua_block {
waf:set_option("debug_log_level", ngx.DEBUG)
}
} deny_statusDefault: ngx.HTTP_FORBIDDEN Sets the status to use when denying requests. Example: location / {
access_by_lua_block {
waf:set_option("deny_status", ngx.HTTP_NOT_FOUND)
}
} disable_pcre_optimizationDefault: false Removes the Example: location / {
access_by_lua_block {
waf:set_option("disable_pcre_optimization", true)
}
} Note: This behavior is deprecated and will be removed in future versions. event_log_altered_onlyDefault: true Determines whether to write log entries for rule matches in a transaction that was not altered by lua-resty-waf. "Altered" is defined as lua-resty-waf acting on a rule whose action is Example: location / {
access_by_lua_block {
waf:set_option("event_log_altered_only", false)
}
} Note that event_log_buffer_sizeDefault: 4096 Defines the threshold size, in bytes, of the buffer to be used to hold event logs. The buffer will be flushed when this threshold is met. Example: location / {
access_by_lua_block {
-- 8 KB event log message buffer
waf:set_option("event_log_buffer_size", 8192)
}
} |