reCAPTCHA v3
The v3 mode works by using JavaScript and asynchronously validating the user's response to the challenge. Instead of displaying a web page, the challenge can be displayed on a page you already have. This mode works well for displaying a reCAPTCHA to all users and then using each person's score to later determine whether to block or rate-limit them.
Installing
Run the following command to install the reCAPTCHA module.
$ # On Debian/Ubuntu
$ sudo apt-get install hapee-2.5r1-lb-recaptcha
$ # On CentOS/RedHat/Oracle/Photon OS
$ sudo yum install hapee-2.5r1-lb-recaptcha
$ # On SUSE
$ sudo zypper install hapee-2.5r1-lb-recaptcha
$ # On FreeBSD
$ sudo pkg install hapee-2.5r1-lb-recaptcha
-
Run the following command to install the
hapee-2.5r1-lb-htmldom
package.$ # On Debian/Ubuntu $ sudo apt-get install hapee-2.5r1-lb-htmldom
$ # On CentOS/RedHat/Oracle/Photon OS $ sudo yum install hapee-2.5r1-lb-htmldom
$ # On SUSE $ sudo zypper install hapee-2.5r1-lb-htmldom
$ # On FreeBSD $ sudo pkg install hapee-2.5r1-lb-htmldom
Go to https://www.google.com/recaptcha/admin and register your website for a Google reCAPTCHA. Choose reCAPTCHA v3.
-
Edit the file
/etc/hapee-2.5/hapee-lb-recaptcha.cfg
. Find the line that reads:recaptcha_private_v3 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Replace
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
with the secret key you got when you registered for a reCAPTCHA. Put a new random string in for
hmac_secret
to prevent someone else with this module from generating cookies for your site to get around the reCAPTCHA.-
Optionally, change the values for
ip_in_cookie
andvalid_duration
:The
ip_in_cookie
setting will put the client's IP address into the HMAC so that cookie sharing is more difficult.The
valid_duration
setting sets how long in seconds the cookie will be accepted.
-
Add the following line to the global section of your HAProxy Enterprise configuration:
global module-load hapee-lb-htmldom.so lua-load
/opt/hapee-2.5/modules/hapee-lb-recaptcha.luac -
Add a
frontend
section and abackend
section to your HAProxy Enterprise configuration to communicate with Google and verify reCAPTCHAs:frontend google_recaptcha_fe mode http bind 127.0.0.1:3859 option httpclose timeout client 5s default_backend google_recaptcha_be backend google_recaptcha_be mode http timeout server 5s server google www.google.com:443 ssl verify none maxconn 10 check inter 60s fall 2 rise 2
The LUA module connects locally to this frontend, and the backend forwards the challenge to Google reCAPTCHA.
-
Add the
reCAPTCHA logic
lines shown below inside anyfrontend
orlisten
section from which you want to send reCAPTCHA challenges:frontend www bind *:80 # Start of reCAPTCHA logic stick-table type string len 54 size 1m expire 1h store gpt0,http_req_rate(1h) http-request set-var(txn.captcha_version) int(3) acl is_captcha_callback path
-i /.well-known/haproxy/captcha_callbackhttp-request set-var(txn.captcha_resp) req.body_param(grecaptcharesp) if METH_POST is_captcha_callback http-request lua.get_captcha_score if METH_POST is_captcha_callback http-request track-sc0 lua.make_hmac_cookie if is_captcha_callback http-request capture var(txn.captcha_score) len 4 if METH_POST is_captcha_callback http-request sc-set-gpt0(0) 0 if { var(txn.captcha_score) -m int le 10 } is_captcha_callback http-request sc-set-gpt0(0) 1 if { var(txn.captcha_score) -m int gt 10 } is_captcha_callback http-request sc-set-gpt0(0) 2 if { var(txn.captcha_score) -m int gt 25 } is_captcha_callback http-request sc-set-gpt0(0) 3 if { var(txn.captcha_score) -m int gt 50 } is_captcha_callback http-request sc-set-gpt0(0) 4 if { var(txn.captcha_score) -m int gt 75 } is_captcha_callback http-request use-service lua.send_hmac_cookie if is_captcha_callback http-request track-sc0 req.cook(solved_captcha) if { req.cook(solved_captcha) -m found } !is_captcha_callback http-request capture sc_get_gpt0(0) len 1 filter htmldom mode strict head-append '<script src="https://www.google.com/recaptcha/api.js?render=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"></script><script type="text/javascript" defer async>if(document.cookie.indexOf("solved_captcha") == -1) { grecaptcha.ready(function() { action=location.pathname.substring(location.pathname.lastIndexOf("/") + 1); action="index"; grecaptcha.execute("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", {action: action}).then(function(token) { var captcha_resp_body = "grecaptcharesp=" + token + "&location=" + encodeURIComponent(document.URL); var makereport = new XMLHttpRequest(); makereport.open("POST", "/.well-known/haproxy/captcha_callback", true); makereport.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); makereport.onreadystatechange = function () { if(makereport.readyState == 4 && makereport.status == 200 ) { document.cookie = "solved_captcha="+makereport.responseText; }}; makereport.send(captcha_resp_body); }) }) }</script>' # End of reCAPTCHA logic default_backend webapp_be backend webapp_be server app1 192.168.56.33:8080 server app2 192.168.56.34:8080Replace
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
with the site key you got when you registered for a reCAPTCHA. -
On its own, the module only records a score between 0 and 4, where 0 is the worst score, and stores it in the stick table. Retrieve the current user's score with the
sc_get_gpt0(0)
fetch method. You can add logic that uses the score when deciding whether to take action on a client's request. For example, you could simply deny all requests that have a score less than 2, or you could enforce stricter rate limiting. In the example below, we deny requests that have a score less than 2:frontend www # reCAPTCHA logic from above not shown # deny requests that have a low score http-request deny if { sc_get_gpt0(0) lt 2 } default_backend webapp_be backend webapp_be server app1 192.168.56.33:8080 server app2 192.168.56.34:8080
Reload HAProxy Enterprise.
Make an HTTP request and see if it works.
Troubleshooting
If your request does not work:
-
Check the entries in the stick table using the Runtime API command
show table
.The key in the table is the user's reCAPTCHA cookie.
The score is the
gpt0
value.$ echo "show table www" | sudo socat stdio unix-connect:/var/run/hapee-2.5/hapee-lb.sock
# table: www, type: string, size:3072, used:1 0x1424020: key=1585339999-90656c7f6de906ea2f5ad97a7 use=0 exp=86383384 gpt0=4 http_req_rate(3600000)=1
On the page in question press
Ctrl-Shift-i
, which brings up the developer console on both Chrome and Firefox. Look at the Javascript "Console" and check for errors. They will usually be highlighted in red. If you see a message about an "unfulfilled promise" in the reCAPTCHA javascript files, check that the hostname setting is correct in your Google admin console for this reCAPTCHA site key. For example, did you set it toexample.com
but are visitinglocalhost/login.html
? The actual error is generated in the heavily obfuscated code that is reCAPTCHA so debugging this error other than checking settings like that is largely impossible.Check the HAProxy Enterprise request logs. If you see LUA errors appear above the request lines (notably above a request for
verify_recaptcha
that returned a 500 status code), they should indicate what is going wrong.-
Enable debug-level logging in HAProxy. The reCAPTCHA module will log the JSON that it gets from Google's verification service on this level. This approach may not be helpful for general production traffic, but for individual requests which are failing, examining the logs can help reveal the cause. For example, if the private site key is wrong, the JSON will say that.
To enable debug-level logging, update the
log
line in theglobal
section.Add
debug
to the end of thelog
line.log 127.0.0.1 local0 debug
If these approaches do not solve the problem, contact Support.
Next up
Reject