Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add other captcha providers / update templates #39

Merged
Merged
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
6 changes: 4 additions & 2 deletions config_example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
31 changes: 17 additions & 14 deletions lib/crowdsec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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

Expand All @@ -70,24 +87,27 @@ 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 = {}
for k, v in pairs(args) do table.insert(params, k .. '=' .. v) end
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 = {
Expand All @@ -114,4 +134,4 @@ function M.Validate(g_captcha_res, remote_ip)
end


return M
return M
7 changes: 4 additions & 3 deletions lib/plugins/crowdsec/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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'}
Expand All @@ -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
Expand Down Expand Up @@ -130,4 +131,4 @@ function config.loadConfig(file)
end
return conf, nil
end
return config
return config
Loading