With my NGinx setup I was able to intercept OPTIONS requests from ajax preflight and respond with the correct CORS headers and a 200 response so the request could continue onwards. I am attempting to consolidate my frontend proxies into HAProxy and am having some issues getting this piece of the puzzle working.
My particular issue is that while I am able to add the proper CORS options when there is a server able to respond properly to an OPTIONS request, a few of the backends cannot handle/respond with a 405 error when the preflight request is issued. My haproxy.cfg included the following lines for adding the headers:
capture request header origin len 128
http-response add-header Access-Control-Allow-Origin %[capture.req.hdr(0)] if { capture.req.hdr(0) -m found }
rspadd Access-Control-Allow-Credentials:\ true if { capture.req.hdr(0) -m found }
rspadd Access-Control-Allow-Headers:\ Origin,\ X-Requested-With,\ Content-Type,\ Origin,\ User-Agent,\ If-Modified-Since,\ Cache-Control,\ Accept if { capture.req.hdr(0) -m found }
rspadd Access-Control-Allow-Methods:\ GET,\ POST,\ PUT,\ DELETE,\ OPTIONS if { capture.req.hdr(0) -m found }
rspadd Access-Control-Max-Age:\ 1728000 if { capture.req.hdr(0) -m found }
The solution given in:
How to send a response with HAProxy without passing the request to web servers works when you set all of the correct headers from a client's request, but is not dynamic which is not an ideal solution.
Any help would be appreciated!
Based on the great answer from anine.io I came up with the following solution which allows to define a list of allowed origins and it also adds the missing Acccess-Control-Allow-Origin
Header for all HTTP requests. The answer from anine.io only showed the CORS preflight, but didn't consider the normal requests.
In haproxy.cfg
load the cors.lua
file (adapt the path if necessary) in the global section
global
lua-load /usr/local/etc/haproxy/cors.lua
Add the CORS configuration to your frontend definition
frontend http-in
# CORS configuration
# capture origin HTTP header
capture request header origin len 128
# add Access-Control-Allow-Origin HTTP header to response if origin matches the list of allowed URLs
http-response add-header Access-Control-Allow-Origin %[capture.req.hdr(0)] if !METH_OPTIONS { capture.req.hdr(0) -m reg -f /usr/local/etc/haproxy/cors-origins.lst }
# if a preflight request is made, use CORS preflight backend
http-request use-service lua.cors-response if METH_OPTIONS { capture.req.hdr(0) -m reg -f /usr/local/etc/haproxy/cors-origins.lst }
Create a file called cors.lua
and store it under the path you specified above. The file contains the CORS preflight and if there's no good reason, don't restrict Methods or Headers because you would have to include any restrictions regarding methods or headers in the ACLs defined in the CORS configuration in haproxy.conf
. Note: Currently Browsers do not support wildcard *
for the Access-Control-Allow-Methods
header. The cors.lua
file should contain the following content
core.register_service("cors-response", "http", function(applet)
applet:set_status(200)
applet:add_header("Content-Length", "0")
applet:add_header("Access-Control-Allow-Origin", applet.headers["origin"][0])
applet:add_header("Access-Control-Allow-Credentials", "true")
applet:add_header("Access-Control-Allow-Headers", "*")
applet:add_header("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, PATCH, OPTIONS")
applet:add_header("Access-Control-Max-Age", "1728000")
applet:start_response()
end)
Create a file called cors-origins.lst
and store it under the path you specified above in the CORS configuration. The file should contain regular expressions (or just simple strings). If the client sends an Origin header, it will be validated against these regular expressions and only if they match, the CORS Preflight from cors.lua
will be returned (for HTTP OPTIONS
requests) or the Access-Control-Allow-Origin
with the value of the origin header of the client request will be added to the response. An example of the content of cors-origins.lst
could be
example.com
localhost.*
.*\.mydomain\.com:[8080|8443]
Test the configuration with http://test-cors.org/. For GET requests there should be no CORS Preflight. For requests other than GET, a CORS Preflight request should be done by the client first (e.g. an HTTP OPTIONS call) to check if the intended method, headers and authorization is allowed.
See HTTP access control (CORS) for further details regarding CORS.
You can use Lua, but you need to make sure HAproxy is built with USE_LUA
, by checking haproxy -vv
.
This is an example configuration, I haven't tried it myself, but it will give you an idea of what you can do:
# haproxy.cfg
global
lua-load cors.lua
frontend foo
...
http-request use-service lua.cors-response if METH_OPTIONS { req.hdr(origin) -m found } { ... }
# cors.lua
core.register_service("cors-response", "http", function(applet)
applet:set_status(200)
applet:add_header("Content-Length", "0")
applet:add_header("Access-Control-Allow-Origin", applet.headers["origin"][0])
applet:add_header("Access-Control-Allow-Credentials", "true")
applet:add_header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Origin, User-Agent, If-Modified-Since, Cache-Control, Accept")
applet:add_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
applet:add_header("Access-Control-Max-Age", "1728000")
applet:start_response()
end)
I want to throw my own answer in the ring here. We've gone through quite some pain to arrive at a working setup. The other answers to this question here were very helpful, but didn't yield a fully working configuration for us.
We wanted to allow any origin. If you need whitelisted origins, see the answer from @Florian Feldhaus for a useful regex trick. Instead of using a whitelist, we echo back the location header:
http-request set-header Access-Control-Allow-Origin %[capture.req.hdr(0)] if { capture.req.hdr(0) -m found }
We also needed to explicitly set Access-Control-Allow-Headers
and Access-Control-Expose-Headers
. Browser support for wildcards in theses headers is not quite there yet.
So here's what our configuration does:
http-response set-header
to add Access-Control-Allow-* headerstune.maxrewrite
to fit our CORS headers (which are > 1 KB)The steps for 1) and 2) are explained in the other answers here, but step 3) took us a long time to figure out. I have documented the full config and the journey that took us there in this blog post. The post contains links to the gist on github.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With