cs-haproxy-bouncer icon indicating copy to clipboard operation
cs-haproxy-bouncer copied to clipboard

[bug] | [feat] Cloudflare Turnstile is broken

Open mnbro opened this issue 8 months ago • 2 comments

Hi,

Configuring this bouncer with Cloudflare Turnstile will always return:

error verifying captcha: 503,<html><body><h1>503 Service Unavailable</h1>.No server is available to handle this request..</body></html>.; verifier: 104.18.95.41:443

The problem disappears when switching to hCaptcha.

The problem appears to be: Lua core.httpclient() is too weak for this task triggering Cloudflare to block its requests.

The solution would be to create a sidecar container to handle http request to Cloudflare something like:

from fastapi import FastAPI, Request, HTTPException
import httpx
import os

app = FastAPI()

CLOUDFLARE_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
CLOUDFLARE_SECRET = os.getenv("TURNSTILE_SECRET")

@app.post("/validate")
async def validate_captcha(request: Request):
    data = await request.json()

    # Extract and validate required fields
    captcha_response = data.get("response")
    remoteip = data.get("remoteip")
    if not captcha_response:
        raise HTTPException(status_code=400, detail="Missing captcha response")

    payload = {
        "secret": CLOUDFLARE_SECRET,
        "response": captcha_response,
        "remoteip": remoteip
    }

    # POST to Cloudflare
    async with httpx.AsyncClient(timeout=5) as client:
        try:
            cloudflare_resp = await client.post(CLOUDFLARE_URL, data=payload, headers={
                "Content-Type": "application/x-www-form-urlencoded",
                "User-Agent": "captcha-proxy/1.0"
            })
        except httpx.RequestError as e:
            raise HTTPException(status_code=503, detail=f"Cloudflare unreachable: {e}")

    if cloudflare_resp.status_code != 200:
        raise HTTPException(status_code=503, detail="Failed to verify captcha")

    result = cloudflare_resp.json()
    return result

and change the lua call to

local status, res = pcall(function()
    return core.httpclient():post{
        url = "http://localhost:8081/validate",
        body = json.encode({
            secret = M.SecretKey,
            response = captcha_res,
            remoteip = remote_ip
        }),
        headers = {
            ["Content-Type"] = {"application/json"}
        },
        timeout = 2000
    }
end)

mnbro avatar Mar 25 '25 12:03 mnbro

This is also probably the reason for #37

mnbro avatar Mar 25 '25 12:03 mnbro

Maybe. The linked issue #37 I closed because it went away very soon (hours maybe) after logging it on github. No changes from my side, perhaps a restart of one of the elements. So my setup was not changed and the issue dissappeared once I had pretty much finished all the testing I was doing. In other words, no code needed to change. Maybe it is another problem but seems (to me the untrained eye), that is not related.

cookiemonsteruk avatar Apr 01 '25 13:04 cookiemonsteruk