diff --git a/config_example.conf b/config_example.conf index f1e7c44..e99e4b2 100644 --- a/config_example.conf +++ b/config_example.conf @@ -17,9 +17,11 @@ BAN_TEMPLATE_PATH=/var/lib/crowdsec/lua/templates/ban.html REDIRECT_LOCATION= RET_CODE= #those apply for "captcha" action -# ReCaptcha Secret Key +#valid providers are recaptcha, hcaptcha, turnstile +CAPTCHA_PROVIDER= +# Captcha Secret Key SECRET_KEY= -# Recaptcha Site key +# Captcha Site key SITE_KEY= CAPTCHA_TEMPLATE_PATH=/var/lib/crowdsec/lua/templates/captcha.html CAPTCHA_EXPIRATION=3600 diff --git a/lib/crowdsec.lua b/lib/crowdsec.lua index cf62d31..bc8c81d 100644 --- a/lib/crowdsec.lua +++ b/lib/crowdsec.lua @@ -4,7 +4,7 @@ local config = require "plugins.crowdsec.config" local iputils = require "plugins.crowdsec.iputils" local http = require "resty.http" local cjson = require "cjson" -local recaptcha = require "plugins.crowdsec.recaptcha" +local captcha = require "plugins.crowdsec.captcha" local utils = require "plugins.crowdsec.utils" local ban = require "plugins.crowdsec.ban" @@ -43,9 +43,9 @@ function csmod.init(configFile, userAgent) end local captcha_ok = true - local err = recaptcha.New(runtime.conf["SITE_KEY"], runtime.conf["SECRET_KEY"], runtime.conf["CAPTCHA_TEMPLATE_PATH"]) + local err = captcha.New(runtime.conf["SITE_KEY"], runtime.conf["SECRET_KEY"], runtime.conf["CAPTCHA_TEMPLATE_PATH"], runtime.conf["CAPTCHA_PROVIDER"]) if err ~= nil then - ngx.log(ngx.ERR, "error loading recaptcha plugin: " .. err) + ngx.log(ngx.ERR, "error loading captcha plugin: " .. err) captcha_ok = false end local succ, err, forcible = runtime.cache:set("captcha_ok", captcha_ok) @@ -89,8 +89,8 @@ function csmod.init(configFile, userAgent) end -function csmod.validateCaptcha(g_captcha_res, remote_ip) - return recaptcha.Validate(g_captcha_res, remote_ip) +function csmod.validateCaptcha(captcha_res, remote_ip) + return captcha.Validate(captcha_res, remote_ip) end @@ -364,9 +364,12 @@ end function csmod.GetCaptchaTemplate() - return recaptcha.GetTemplate() + return captcha.GetTemplate() end +function csmod.GetCaptchaBackendKey() + return captcha.GetCaptchaBackendKey() +end function csmod.SetupStream() -- if it stream mode and startup start timer @@ -464,7 +467,7 @@ function csmod.Allow(ip) local captcha_ok = runtime.cache:get("captcha_ok") if runtime.fallback ~= "" then - -- if we can't use recaptcha, fallback + -- if we can't use captcha, fallback if remediation == "captcha" and captcha_ok == false then remediation = runtime.fallback end @@ -478,17 +481,17 @@ function csmod.Allow(ip) if captcha_ok then -- if captcha can be use (configuration is valid) -- we check if the IP need to validate its captcha before checking it against crowdsec local API local previous_uri, state_id = ngx.shared.crowdsec_cache:get("captcha_"..ngx.var.remote_addr) - if previous_uri ~= nil and state_id == recaptcha.GetStateID(recaptcha._VERIFY_STATE) then + if previous_uri ~= nil and state_id == captcha.GetStateID(captcha._VERIFY_STATE) then ngx.req.read_body() - local recaptcha_res = ngx.req.get_post_args()["g-recaptcha-response"] or 0 - if recaptcha_res ~= 0 then - local valid, err = csmod.validateCaptcha(recaptcha_res, ngx.var.remote_addr) + local captcha_res = ngx.req.get_post_args()[csmod.GetCaptchaBackendKey()] or 0 + if captcha_res ~= 0 then + local valid, err = csmod.validateCaptcha(captcha_res, ngx.var.remote_addr) if err ~= nil then ngx.log(ngx.ERR, "Error while validating captcha: " .. err) end if valid == true then -- captcha is valid, we redirect the IP to its previous URI but in GET method - local succ, err, forcible = ngx.shared.crowdsec_cache:set("captcha_"..ngx.var.remote_addr, previous_uri, runtime.conf["CAPTCHA_EXPIRATION"], recaptcha.GetStateID(recaptcha._VALIDATED_STATE)) + local succ, err, forcible = ngx.shared.crowdsec_cache:set("captcha_"..ngx.var.remote_addr, previous_uri, runtime.conf["CAPTCHA_EXPIRATION"], captcha.GetStateID(captcha._VALIDATED_STATE)) if not succ then ngx.log(ngx.ERR, "failed to add key about captcha for ip '" .. ngx.var.remote_addr .. "' in cache: "..err) end @@ -516,7 +519,7 @@ function csmod.Allow(ip) if remediation == "captcha" and captcha_ok and ngx.var.uri ~= "/favicon.ico" then local previous_uri, state_id = ngx.shared.crowdsec_cache:get("captcha_"..ngx.var.remote_addr) -- we check if the IP is already in cache for captcha and not yet validated - if previous_uri == nil or state_id ~= recaptcha.GetStateID(recaptcha._VALIDATED_STATE) then + if previous_uri == nil or state_id ~= captcha.GetStateID(captcha._VALIDATED_STATE) then ngx.header.content_type = "text/html" ngx.say(csmod.GetCaptchaTemplate()) local uri = ngx.var.uri @@ -529,7 +532,7 @@ function csmod.Allow(ip) end end end - local succ, err, forcible = ngx.shared.crowdsec_cache:set("captcha_"..ngx.var.remote_addr, uri , 60, recaptcha.GetStateID(recaptcha._VERIFY_STATE)) + local succ, err, forcible = ngx.shared.crowdsec_cache:set("captcha_"..ngx.var.remote_addr, uri , 60, captcha.GetStateID(captcha._VERIFY_STATE)) if not succ then ngx.log(ngx.ERR, "failed to add key about captcha for ip '" .. ngx.var.remote_addr .. "' in cache: "..err) end diff --git a/lib/plugins/crowdsec/recaptcha.lua b/lib/plugins/crowdsec/captcha.lua similarity index 64% rename from lib/plugins/crowdsec/recaptcha.lua rename to lib/plugins/crowdsec/captcha.lua index cfe0fc1..8968b07 100644 --- a/lib/plugins/crowdsec/recaptcha.lua +++ b/lib/plugins/crowdsec/captcha.lua @@ -6,7 +6,20 @@ local utils = require "plugins.crowdsec.utils" local M = {_TYPE='module', _NAME='recaptcha.funcs', _VERSION='1.0-0'} -local recaptcha_verify_url = "https://www.google.com/recaptcha/api/siteverify" +local captcha_backend_url = {} +captcha_backend_url["recaptcha"] = "https://www.recaptcha.net/recaptcha/api/siteverify" +captcha_backend_url["hcaptcha"] = "https://hcaptcha.com/siteverify" +captcha_backend_url["turnstile"] = "https://challenges.cloudflare.com/turnstile/v0/siteverify" + +local captcha_frontend_js = {} +captcha_frontend_js["recaptcha"] = "https://www.recaptcha.net/recaptcha/api.js" +captcha_frontend_js["hcaptcha"] = "https://js.hcaptcha.com/1/api.js" +captcha_frontend_js["turnstile"] = "https://challenges.cloudflare.com/turnstile/v0/api.js" + +local captcha_frontend_key = {} +captcha_frontend_key["recaptcha"] = "g-recaptcha" +captcha_frontend_key["hcaptcha"] = "h-captcha" +captcha_frontend_key["turnstile"] = "cf-turnstile" M._VERIFY_STATE = "to_verify" M._VALIDATED_STATE = "validated" @@ -32,7 +45,7 @@ end -function M.New(siteKey, secretKey, TemplateFilePath) +function M.New(siteKey, secretKey, TemplateFilePath, captcha_provider) if siteKey == nil or siteKey == "" then return "no recaptcha site key provided, can't use recaptcha" @@ -57,8 +70,12 @@ function M.New(siteKey, secretKey, TemplateFilePath) return "Template file " .. TemplateFilePath .. "not found." end + M.CaptchaProvider = captcha_provider + local template_data = {} - template_data["recaptcha_site_key"] = M.SiteKey + template_data["captcha_site_key"] = M.SiteKey + template_data["captcha_frontend_js"] = captcha_frontend_js[M.CaptchaProvider] + template_data["captcha_frontend_key"] = captcha_frontend_key[M.CaptchaProvider] local view = template.compile(captcha_template, template_data) M.Template = view @@ -70,6 +87,9 @@ function M.GetTemplate() return M.Template end +function M.GetCaptchaBackendKey() + return captcha_frontend_key[M.CaptchaProvider] .. "-response" +end function table_to_encoded_url(args) local params = {} @@ -77,17 +97,17 @@ function table_to_encoded_url(args) return table.concat(params, "&") end -function M.Validate(g_captcha_res, remote_ip) +function M.Validate(captcha_res, remote_ip) local body = { secret = M.SecretKey, - response = g_captcha_res, + response = captcha_res, remoteip = remote_ip } local data = table_to_encoded_url(body) local httpc = http.new() httpc:set_timeout(2000) - local res, err = httpc:request_uri(recaptcha_verify_url, { + local res, err = httpc:request_uri(captcha_backend_url[M.CaptchaProvider], { method = "POST", body = data, headers = { @@ -114,4 +134,4 @@ function M.Validate(g_captcha_res, remote_ip) end -return M \ No newline at end of file +return M diff --git a/lib/plugins/crowdsec/config.lua b/lib/plugins/crowdsec/config.lua index ac0f9c2..54f423f 100644 --- a/lib/plugins/crowdsec/config.lua +++ b/lib/plugins/crowdsec/config.lua @@ -40,7 +40,7 @@ function config.loadConfig(file) return nil, "File ".. file .." doesn't exist" end local conf = {} - local valid_params = {'ENABLED','API_URL', 'API_KEY', 'BOUNCING_ON_TYPE', 'MODE', 'SECRET_KEY', 'SITE_KEY', 'BAN_TEMPLATE_PATH' ,'CAPTCHA_TEMPLATE_PATH', 'REDIRECT_LOCATION', 'RET_CODE', 'EXCLUDE_LOCATION', 'FALLBACK_REMEDIATION'} + local valid_params = {'ENABLED','API_URL', 'API_KEY', 'BOUNCING_ON_TYPE', 'MODE', 'SECRET_KEY', 'SITE_KEY', 'BAN_TEMPLATE_PATH' ,'CAPTCHA_TEMPLATE_PATH', 'REDIRECT_LOCATION', 'RET_CODE', 'EXCLUDE_LOCATION', 'FALLBACK_REMEDIATION', 'CAPTCHA_PROVIDER'} local valid_int_params = {'CACHE_EXPIRATION', 'CACHE_SIZE', 'REQUEST_TIMEOUT', 'UPDATE_FREQUENCY', 'CAPTCHA_EXPIRATION'} local valid_bouncing_on_type_values = {'ban', 'captcha', 'all'} local valid_truefalse_values = {'false', 'true'} @@ -53,7 +53,8 @@ function config.loadConfig(file) ['CAPTCHA_EXPIRATION'] = 3600, ['REDIRECT_LOCATION'] = "", ['EXCLUDE_LOCATION'] = {}, - ['RET_CODE'] = 0 + ['RET_CODE'] = 0, + ['CAPTCHA_PROVIDER'] = "recaptcha" } for line in io.lines(file) do local isOk = false @@ -130,4 +131,4 @@ function config.loadConfig(file) end return conf, nil end -return config \ No newline at end of file +return config diff --git a/templates/ban.html b/templates/ban.html index 8110e2f..7c13af1 100644 --- a/templates/ban.html +++ b/templates/ban.html @@ -3,137 +3,94 @@ CrowdSec Ban - + + - -
-
-

- -

CrowdSec Access Forbidden

-

You are unable to visit the website.

- -

- This security check has been powered by -

+
+
+
+ +

CrowdSec Access Forbidden

+

You are unable to visit the website.

+
+
+

+ This security check has been powered by +

+ + - + width="33.92" + height="33.76" + viewBox="0 0 254.4 253.2" + > + + + + + + + + - - - - - - CrowdSec -

+ + + + + + + + + + CrowdSec + + +
+
-
+ diff --git a/templates/captcha.html b/templates/captcha.html index 6e6068a..b01f5e1 100644 --- a/templates/captcha.html +++ b/templates/captcha.html @@ -1,144 +1,103 @@ - CrowdSec ReCaptcha + CrowdSec Captcha - - + + + - - -
-
-

- - CrowdSec ReCaptcha -
-
-
- -
-

-

+ +

+
+
+
+ +

CrowdSec Captcha

+
+
+
+
+ + + + + + CrowdSec + +
- +
+
+ +