HAProxy Enterprise Documentation 2.2r1

reCAPTCHA

HAProxy Enterprise's Google reCAPTCHA module allows you to present a reCAPTCHA to a user. It supports both reCAPTCHA v2 and v3. In v2 mode, the load balancer displays a web page that shows the challenge. The user will not be able to proceed until they've solved it. This works well for stopping users that you have already flagged as suspicious, but you will typically only display it for specific users.

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 or not to block or rate limit them.

Installation

Run the following command to install the reCAPTCHA module.

$ # On Debian/Ubuntu
$ sudo apt-get install hapee-2.2r1-lb-recaptcha
$ # On CentOS/RedHat/Oracle/Photon OS
$ sudo yum install hapee-2.2r1-lb-recaptcha
$ # On SUSE
$ sudo zypper install hapee-2.2r1-lb-recaptcha
$ # On FreeBSD
$ sudo pkg install hapee-2.2r1-lb-recaptcha

The sections below contain additional steps depending on whether you wish to use Google reCAPTCHA v2 or v3.

reCAPTCHA v2

  1. Go to https://www.google.com/recaptcha/admin and register your website for a Google reCAPTCHA. Choose reCAPTCHA v2.

  2. Edit the file: /etc/hapee-2.2/hapee-lb-recaptcha.cfg. Find the line that reads:

    recaptcha_private XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    and replace the value with the private key you got when you registered for a reCAPTCHA.

  3. Search the file for data-sitekey and replace XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX with the corresponding public key you got when you registered for a reCAPTCHA.

  4. 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.

  5. Optionally, change the values for ip_in_cookie and valid_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.

  6. Add the following line to the global section of your HAProxy Enterprise configuration:

    global
       lua-load /opt/hapee-2.2/modules/hapee-lb-recaptcha.luac
  7. Add a new listen section to your HAProxy Enterprise configuration that will communicate with Google to verify reCAPTCHAs, as follows:

    listen google_recaptcha
       mode http
       bind 127.0.0.1:3859
       option httpclose
       timeout server 5s
       timeout client 5s
       server google www.google.com:443 ssl verify none maxconn 10 check inter 60s fall 2 rise 2
  8. Add the following tcp-request inspect-delay and http-request use-service lines inside any frontend or listen section from which you want to send reCAPTCHA challenges:

    frontend www
       bind :80
       tcp-request inspect-delay 1s
       http-request use-service lua.verify_recaptcha if { path /.well-known/haproxy/captcha_callback }
       http-request use-service lua.request_recaptcha unless { lua.verify_solved_captcha "ok" }

    You can add your conditions, such as detecting scrapers, to this rule if you don't want to require all visitors to solve a Captcha

  9. Reload HAProxy Enterprise.

  10. Make an HTTP request and see if it works. A client will need to solve the challenge before they can continue past the reCAPTCHA web page.

  11. Check your logs carefully. In case of problems, multiple messages about it may appear but only one can point to the real problem.

reCAPTCHA v3

  1. Install the required package hapee-2.2r1-lb-htmldom

    $ # On Debian/Ubuntu
    $ sudo apt-get install hapee-2.2r1-lb-htmldom
    $ # On CentOS/RedHat/Oracle/Photon OS
    $ sudo yum install hapee-2.2r1-lb-htmldom
    $ # On SUSE
    $ sudo zypper install hapee-2.2r1-lb-htmldom
    $ # On FreeBSD
    $ sudo pkg install hapee-2.2r1-lb-htmldom
  2. Go to https://www.google.com/recaptcha/admin and register your website for a Google reCAPTCHA. Choose reCAPTCHA v3.

  3. Edit the file: /etc/hapee-2.2/hapee-lb-recaptcha.cfg. Find the line that reads:

    "recaptcha_private_v3 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    and replace the value with the private key you got when you registered for a reCAPTCHA.

  4. 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.

  5. Optionally, change the values for ip_in_cookie and valid_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.

  6. 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.2/modules/hapee-lb-recaptcha.luac
  7. Add the following lines inside any frontend or listen section from which you want to send reCAPTCHA challenges:

    frontend www
       # 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_callback
       http-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>'

    Replace "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" with the public key you got when you registered for a reCAPTCHA.

  8. 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 }
  9. Reload HAProxy Enterprise.

  10. Make an HTTP request and see if it works.

Troubleshoot reCAPTCHA v2

If your request does not work:

  • Check the logs for Lua error messages that may indicate the reason.

  • Try adding debugging code to the Lua script to print the full response from Google/etc.

Troubleshoot reCAPTCHA v3

If your request does not work:

  • Check the entries in the stick table using the Runtime API command show table. Th key in the table is the user's reCAPTCHA cookie. The score is the gpt0 value.

    $ echo "show table www" |  socat stdio /var/run/hapee-2.2/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 to "example.com" but are visiting "localhost/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, though if you aren't sure what the cause is you may have to e-mail support@haproxy.com for us to examine them.

  • Enable debug-level logging in HAProxy. The reCAPTCHA module will log the JSON that it gets from Google's verification service on this level. Mostly useless for production traffic, but for individual requests which are failing, examining that can explain why. For example, if the private site key is wrong the JSON will say that.


Next up

Single Sign-on