Hey,
If you’ve ever wondered whether you can tie Access Control Lists (ACLs) with maps in HAProxy, the answer is: yes.
Let’s tailor a scenario here:
- based on a map, decide whether a request to a given domain should be answered by the current frontend or not - in the negative case, forward the request to a different frontend.
One application that can be thought from this is the case where some domains are meant to be served by both a plain HTTP frontend and a HTTPS frontend too, but some other domains must be redirected to HTTPS when a request is made to HTTP.
For instance:
http://test.example.com
andhttps://test.example.com
OK;https://prod.example.com
is OK but nohttp://prod.example.com
such that when a request is made tohttp://prod.example.com
, this should be redirected to the HTTPS frontend.
That said, this is how our configuration would look like:
Let’s write it then.
# Load the `./script.lua` script that defines some services
# that are meant to respond with `SERVICE_N OK` when a request
# is made to them.
#
# Having these services defined allows us to not need a real
# server behind the scenes.
global
lua-load ./script.lua
defaults
mode http
timeout client 10000
timeout server 10000
timeout connect 1000
# The frontend `fe1` is the one that clients are meant to
# make requests to in order to access the backends as mapped
# by the `./domain-map` file.
#
# The contents of the `domain-map` file are:
#
# foo.com backend_1
# bar.com backend_2
#
# This way, requests that are not for `foo.com` or `bar.com`
# are sent to the other frontend (fe2) based on an ACL, otherwise,
# the respective backend is used.
frontend fe1
bind 127.0.0.1:8000
acl has_domain hdr(Host),map_str(./domain-map) -m found
redirect prefix http://127.0.0.1:8001 if ! has_domain
use_backend %[req.hdr(host),lower,map_str(./domain-map)]
# A sample frontend that always responds with a specific
# service (demonstration purposes).
frontend fe2
bind 127.0.0.1:8001
http-request use-service lua.service3
backend backend_1
http-request use-service lua.service1
backend backend_2
http-request use-service lua.service2
Being service1
, service2
and service3
:
core.register_service("service1", "http", function (applet)
local response = "SERVICE 1 OK"
applet:set_status(200)
applet:start_response()
applet:send(response)
end)
core.register_service("service2", "http", function (applet)
local response = "SERVICE 2 OK"
applet:set_status(200)
applet:start_response()
applet:send(response)
end)
core.register_service("service3", "http", function (applet)
local response = "SERVICE 3 OK"
applet:set_status(200)
applet:start_response()
applet:send(response)
end)
We can see it in practice:
# Make a request with `foo.com` as the destination host.
# Given that `foo.com` is a mapped backend, the request
# will get directed to a backend controlled by the frontend
# itself (backend_1)
curl \
--location \
--header "Host: foo.com" \
--verbose \
localhost:8000/
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: foo.com
> User-Agent: curl/7.59.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Transfer-encoding: chunked
<
SERVICE 1 OK
# Just like in the case above, `bar.com` is also a
# domain controlled by this frontend.
curl \
--location \
--header "Host: bar.com" \
--verbose \
localhost:8000/
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: bar.com
> User-Agent: curl/7.59.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Transfer-encoding: chunked
<
* Connection #0 to host localhost left intact
SERVICE 2 OK
# Differently from the two cases above, `test.com`
# is not included in the frontend1 map.
#
# This means that the ACL we placed there will evaluate
# `has_domain` to false and then will redirect us to
# the second frontend (which always responds with
# SERVICE 3).
#
# Check out how we get redirected to the other frontend.
curl \
--location \
--header "Host: test.com" \
--verbose \
localhost:8000/
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: test.com
> User-Agent: curl/7.59.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Cache-Control: no-cache
< Content-length: 0
< Location: http://127.0.0.1:8001/ # << REDIRECTION
< # sends us to another frontend
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://127.0.0.1:8001/'
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8001 (#1)
> GET / HTTP/1.1
> Host: 127.0.0.1:8001
> User-Agent: curl/7.59.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Transfer-encoding: chunked
<
* Connection #1 to host 127.0.0.1 left intact
SERVICE 3 OK
That’s it!
If you have any questions or spotted something off, please let me know! I’m cirowrc on Twitter.
Have a good one!
finis