Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config_example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ CACHE_EXPIRATION=1
# bounce for all type of remediation that the bouncer can receive from the local API
BOUNCING_ON_TYPE=all
FALLBACK_REMEDIATION=ban
# severity of the "denied" log line: debug, info, notice, warn, error, crit, alert or emerg
BOUNCING_LOG_LEVEL=alert
REQUEST_TIMEOUT=3000
UPDATE_FREQUENCY=10
# By default internal requests are ignored, such as any path affected by rewrite rule.
Expand Down
21 changes: 19 additions & 2 deletions lib/crowdsec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ local APPSEC_TRANSFER_ENCODING_HEADER = "x-crowdsec-appsec-transfer-encoding"
local REMEDIATION_API_KEY_HEADER = 'x-api-key'
local METRICS_PERIOD = 900

-- maps the BOUNCING_LOG_LEVEL config values to the ngx.log severity constants.
-- names are spelled as in nginx's own `error_log` directive.
local LOG_LEVELS = {
debug = ngx.DEBUG,
info = ngx.INFO,
notice = ngx.NOTICE,
warn = ngx.WARN,
error = ngx.ERR,
crit = ngx.CRIT,
alert = ngx.ALERT,
emerg = ngx.EMERG,
}

local METHODS_WITH_BODY = {
POST = true,
PUT = true,
Expand Down Expand Up @@ -117,6 +130,10 @@ function csmod.init(configFile, userAgent)
runtime.userAgent = userAgent
runtime.cache = ngx.shared.crowdsec_cache
runtime.fallback = runtime.conf["FALLBACK_REMEDIATION"]
-- severity used for the "denied" line emitted on every bounced request.
-- config.loadConfig() already rejected unknown names, so the lookup cannot miss;
-- the fallback keeps us safe if the bouncer is initialised with a hand-built conf table.
runtime.bouncing_log_level = LOG_LEVELS[runtime.conf["BOUNCING_LOG_LEVEL"]] or ngx.ALERT

if runtime.conf["ENABLED"] == "false" then
return "Disabled", nil
Expand Down Expand Up @@ -899,7 +916,7 @@ function csmod.Allow(ip)

if not ok then
if remediation == "ban" then
ngx.log(ngx.ALERT, "[Crowdsec] denied '" .. ip .. "' with '"..remediation.."' (by " .. flag.Flags[remediationSource] .. ")")
ngx.log(runtime.bouncing_log_level, "[Crowdsec] denied '" .. ip .. "' with '"..remediation.."' (by " .. flag.Flags[remediationSource] .. ")")
ban.apply(ret_code)
return
end
Expand Down Expand Up @@ -940,7 +957,7 @@ function csmod.Allow(ip)
if forcible then
ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config")
end
ngx.log(ngx.ALERT, "[Crowdsec] denied '" .. ip .. "' with '"..remediation.."'")
ngx.log(runtime.bouncing_log_level, "[Crowdsec] denied '" .. ip .. "' with '"..remediation.."'")
captcha.apply()
return
end
Expand Down
11 changes: 10 additions & 1 deletion lib/plugins/crowdsec/config.lua
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
local config = {}

local valid_params = {'ENABLED', 'ENABLE_INTERNAL', 'API_URL', 'API_KEY', 'BOUNCING_ON_TYPE', 'MODE', 'SECRET_KEY', 'SITE_KEY', 'BAN_TEMPLATE_PATH' ,'CAPTCHA_TEMPLATE_PATH', 'REDIRECT_LOCATION', 'RET_CODE', 'CAPTCHA_RET_CODE', 'EXCLUDE_LOCATION', 'FALLBACK_REMEDIATION', 'CAPTCHA_PROVIDER', 'APPSEC_URL', 'APPSEC_FAILURE_ACTION', 'ALWAYS_SEND_TO_APPSEC', 'APPSEC_DROP_UNREADABLE_BODY', 'SSL_VERIFY', 'USE_TLS_AUTH', 'TLS_CLIENT_CERT', 'TLS_CLIENT_KEY', 'SCENARIOS_CONTAINING', 'SCENARIOS_NOT_CONTAINING'}
local valid_params = {'ENABLED', 'ENABLE_INTERNAL', 'API_URL', 'API_KEY', 'BOUNCING_ON_TYPE', 'MODE', 'SECRET_KEY', 'SITE_KEY', 'BAN_TEMPLATE_PATH' ,'CAPTCHA_TEMPLATE_PATH', 'REDIRECT_LOCATION', 'RET_CODE', 'CAPTCHA_RET_CODE', 'EXCLUDE_LOCATION', 'FALLBACK_REMEDIATION', 'CAPTCHA_PROVIDER', 'APPSEC_URL', 'APPSEC_FAILURE_ACTION', 'ALWAYS_SEND_TO_APPSEC', 'APPSEC_DROP_UNREADABLE_BODY', 'SSL_VERIFY', 'USE_TLS_AUTH', 'TLS_CLIENT_CERT', 'TLS_CLIENT_KEY', 'SCENARIOS_CONTAINING', 'SCENARIOS_NOT_CONTAINING', 'BOUNCING_LOG_LEVEL'}
local valid_int_params = {'CACHE_EXPIRATION', 'CACHE_SIZE', 'REQUEST_TIMEOUT', 'UPDATE_FREQUENCY', 'CAPTCHA_EXPIRATION', 'APPSEC_CONNECT_TIMEOUT', 'APPSEC_SEND_TIMEOUT', 'APPSEC_PROCESS_TIMEOUT', 'STREAM_REQUEST_TIMEOUT'}
-- CACHE_SIZE is not used in the code, but as is was valid parameter for the configuration file, not removing it now
local valid_bouncing_on_type_values = {'ban', 'captcha', 'all'}
local valid_truefalse_values = {'false', 'true'}
-- nginx error_log severities, spelled as in the `error_log` directive.
local valid_log_level_values = {'debug', 'info', 'notice', 'warn', 'error', 'crit', 'alert', 'emerg'}
local default_values = {
['ENABLED'] = "true",
['ENABLE_INTERNAL'] = "false",
Expand Down Expand Up @@ -34,6 +36,7 @@ local default_values = {
['TLS_CLIENT_KEY'] = "",
['SCENARIOS_CONTAINING'] = "",
['SCENARIOS_NOT_CONTAINING'] = "",
['BOUNCING_LOG_LEVEL'] = "alert",
}


Expand Down Expand Up @@ -125,6 +128,12 @@ function config.loadConfig(file, default)
ngx.log(ngx.ERR, "unsupported value '" .. value .. "' for variable '" .. key .. "'. Using default value 'stream' instead")
value = "stream"
end
elseif key == "BOUNCING_LOG_LEVEL" then
value = value:lower()
if not has_value(valid_log_level_values, value) then
ngx.log(ngx.ERR, "unsupported value '" .. value .. "' for variable '" .. key .. "'. Using default value 'alert' instead")
value = "alert"
end
elseif key == "FALLBACK_REMEDIATION" then
if not has_value({'captcha', 'ban'}, value) then
ngx.log(ngx.ERR, "unsupported value '" .. value .. "' for variable '" .. key .. "'. Using default value 'ban' instead")
Expand Down
67 changes: 67 additions & 0 deletions t/25_bouncing_log_level.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use Test::Nginx::Socket 'no_plan';

run_tests();

__DATA__

=== TEST 1: BOUNCING_LOG_LEVEL lowers the severity of the "denied" line

--- main_config
load_module /usr/share/nginx/modules/ndk_http_module.so;
load_module /usr/share/nginx/modules/ngx_http_lua_module.so;

--- http_config

lua_package_path './lib/?.lua;;';
lua_shared_dict crowdsec_cache 50m;
lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;

init_by_lua_block
{
cs = require "crowdsec"
local ok, err = cs.init("./t/conf_t/25_notice_crowdsec_nginx_bouncer.conf", "crowdsec-nginx-bouncer/v1.0.8")
if ok == nil then
ngx.log(ngx.ERR, "[Crowdsec] " .. err)
error()
end
}

access_by_lua_block {
local cs = require "crowdsec"
cs.Allow(ngx.var.remote_addr)
}

server {
listen 8081;

location = /v1/decisions {
content_by_lua_block {
local args, err = ngx.req.get_uri_args()
if args.ip == "1.1.1.1" then
ngx.say('[{"duration":"1h00m00s","id":4091593,"origin":"CAPI","scenario":"crowdsecurity/vpatch-CVE-2024-4577","scope":"Ip","type":"ban","value":"1.1.1.1"}]')
else
ngx.print('null')
end
}
}
}

--- config

location = /t {
set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
content_by_lua_block {
ngx.say("Hello, world")
}
}

--- log_level: info
--- more_headers
X-Forwarded-For: 1.1.1.1
--- request
GET /t
--- error_code: 403
--- error_log eval
qr/\[notice\] .*\[Crowdsec\] denied '1\.1\.1\.1' with 'ban'/
9 changes: 9 additions & 0 deletions t/conf_t/25_notice_crowdsec_nginx_bouncer.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ENABLED=true
API_URL=http://127.0.0.1:8081
API_KEY=LFrdL+aiecMTSxpGE9vLkx5sGMwdIpgVovpVMfXp3J0
MODE=live
BOUNCING_ON_TYPE=all
BAN_TEMPLATE_PATH=./t/conf_t/ban
# the mock LAPI below is served by the same nginx, so it must not bounce itself
EXCLUDE_LOCATION=/v1/decisions
BOUNCING_LOG_LEVEL=notice