Search code examples
luadnscorshaproxy

HaProxy dynamic lists/maps


I'm trying to get CORS working with a whitelist of domains based on the customers that exist in our system. We have a whitelabelled product that let's a company CNAME a custom domain to our system. We need to be able to on the fly allow Origin's that come from any of these custom domains by adding in a Access-Control-Allow-Origin: http://some.custom.domain header to the response from HaProxy.

I've been reading about different ways of loading lists/maps from haproxy but I'm just not settled on the best implementation. I've come up with roughly 3-ish possible ways of doing this but I wanted to see if anyone else had some insight as well. My options thus far:

  1. Store a map of available domains that I can look for when adding headers. How this map file gets populated is the question at the moment. I suppose I could do an API call through LUA on boot to create the file? Or it could be an NFS mounted file? We'd have to reload each time a new domain is added to our system, either through the same api call or directly to the maps api on the socket.

  2. Use LUA to make live requests to an api on every quests through haproxy to validate if the domain passed in on the Origin is legit and add the header in. Could potentially use Memcached for minimal overhead assuming we can find a LUA client lib for memcached.

  3. Possibly implement some sort of DNS based solution where we run our own DNS server that resolves these custom domains and have HaProxy do a lookup on that. I don't know if this is possible, I jsut know haproxy has DNS capabilities. The weird thing is that we don't actually want to resolve to an IP, we just want a "yes" or "no" answer.

Does anyone else know of an obvious solution to this problem? I'm looking for ease of implementation, but ultimately minimal overhead on the requests themselves since this will need to occur on EVERY request with an Origin header.

Any insight appreciated!


Solution

  • 1/ Map solution:
    I think the simplest and most efficient solution would be to store the available domains in a map. Then you can update the map via HAProxy socket using set map, add map,.. Whenever the domains change, and no need to reload here.

    2/ LUA solution:
    It is possible also to do it with LUA using a function like this:

    core.register_action("validate_origin", {"http-req"}, function(txn)
       local origin= txn.http:req_get_headers()headers["origin"][0]
       local sock = core.tcp()
       sock:connect("<API-IP>", <API-PORT>)
       sock:send("GET /api?validate=".. origin .. " HTTP/1.1\r\n Host:www\r\n ")
       domain = sock:receive("*a")
       sock:close()
       txn:set_var("txn.allow_origin", domain)
    end)
    

    HAProxy conf in the frontend in question:

    http-request capture hdr("Origin") len 128
    http-request lua.validate_origin if  { capture.req.hdr(0) -m found }
    http-response add-header Access-Control-Allow-Origin %[var(txn.allow_origin)] if  { capture.req.hdr(0) -m found }
    

    This solution introduces more overhead since HAProxy has to rely on a external resource (and possibly wait for it) for each CORS request.