Skip to main content
  1. 2024/
  2. Posts from May/

Serving Tiny files from haproxy

Is this a good idea? Turns out 🤔…
🤷 prolly not… ?

On today’s episode of
“What the fuck were you thinking, wolf?!”
We’d like to present:

How to abuse haproxy for fun and profit #

HAProxy, the fun-loving, workhorse of a traffic distributor….
Is pretty spartan, but packed full o functionality…
… If you can figure out how the fuck to use it.

Fortunately, I’ve flattened my forehead today, for your benefit….

So…
  Have you ever wanted to serve a bundle of files directly from yer loadbalancer?

As opposed to having them live alongside all the rest of your web content?
  … You… didn’t?
   huh… Yeah…
      I HADN’T EITHER

But some asshole SOMEONE on The Internet decided using URIs like
/.well-known/my-head/hurts
was a better service discovery mechanism than …. oh….
say…. FUCKING DNS SRV RECORDS
(which is precisely what they were invented for in the first place, but I digress…. )

It turns out that NOT ONLY did this genius decide that using these ‘service records’ was the best way to programatically determine where a service’s endpoint lives…
They ALSO decided they should canonically reside … at the top of your domain…

IE mytoys.com versus stopbreaking.mytoys.com which…. okay admittedly that’s reasonable.

Nevertheless, This causes the problem of needing to serve content

“for” someservice.mydomain.com
“from” mydomain.com

The pain of this expectation is worsened when when mydomain.com belongs to an entirely different systematic or organizational context..
That being said…

Is haproxy a webserver?
NO, Not Really

So…
SHOULD YOU DO THIS?!
  …🤷 Prolly not? ….



…But…
(there’s always an exception)

In MY case… HAProxy is the front door to all of the involved services.
While I DID get nginx functional to serve the well-known assets as needed, the requisite configuration contortions were complex enough to make the tradeoff worthwhile…

So… With no further adeu: #

HAProxy Config to serve files as a return request: #

http-request return content-type application/json file /srv/staticfiles/matrix/server

Yeah.
Kinda straightforward.

This is pretty typical of haproxy. It’s pretty badass. The docs1 relevant are included here; and linked below.

http-request return [status <code>] [content-type <type>]  
[ { default-errorfiles | errorfile <file> | errorfiles <name> | file <file> | lf-file <file> | string <str> | lf-string <fmt> } ]  
[ hdr <name> <fmt> ]*  [ { if | unless } <condition> ]

This stops the evaluation of the rules and immediately returns a response. The default status code used for the response is 200. It can be optionally specified as an arguments to “status”. The response content-type may also be specified as an argument to “content-type”. Finally the response itself may be defined. It can be a full HTTP response specifying the errorfile to use, or the response payload specifying the file or the string to use. These rules are followed to create the response :

  • If neither the errorfile nor the payload to use is defined, a dummy response is returned. Only the “status” argument is considered. It can be any code in the range [200, 599]. The “content-type” argument, if any, is ignored.

  • If “default-errorfiles” argument is set, the proxy’s errorfiles are considered. If the “status” argument is defined, it must be one of the status code handled by HAProxy (200, 400, 403, 404, 405, 408, 410, 413, 425, 429, 500, 501, 502, 503, and 504). The “content-type” argument, if any, is ignored.

  • If a specific errorfile is defined, with an “errorfile” argument, the corresponding file, containing a full HTTP response, is returned. Only the “status” argument is considered. It must be one of the status code handled by HAProxy (200, 400, 403, 404, 405, 408, 410, 413, 425, 429, 500, 501, 502, 503, and 504). The “content-type” argument, if any, is ignored.

  • If an http-errors section is defined, with an “errorfiles” argument, the corresponding file in the specified http-errors section, containing a full HTTP response, is returned. Only the “status” argument is considered. It must be one of the status code handled by HAProxy (200, 400, 403, 404, 405, 408, 410, 413, 425, 429, 500, 501, 502, 503, and 504). The “content-type” argument, if any, is ignored.

  • If a “file” or a “lf-file” argument is specified, the file’s content is used as the response payload. If the file is not empty, its content-type must be set as argument to “content-type”. Otherwise, any “content-type” argument is ignored. With a “lf-file” argument, the file’s content is evaluated as a log-format string. With a “file” argument, it is considered as a raw content.

  • If a “string” or “lf-string” argument is specified, the defined string is used as the response payload. The content-type must always be set as argument to “content-type”. With a “lf-string” argument, the string is evaluated as a log-format string. With a “string” argument, it is considered as a raw string.

When the response is not based on an errorfile, it is possible to append HTTP header fields to the response using “hdr” arguments. Otherwise, all “hdr” arguments are ignored. For each one, the header name is specified in and its value is defined by which follows the log-format rules.

Note that the generated response must be smaller than a buffer. And to avoid any warning, when an errorfile or a raw file is loaded, the buffer space reserved for the headers rewriting should also be free.

No further “http-request” rules are evaluated. Example:

http-request return errorfile /etc/haproxy/errorfiles/200.http \
    if { path /ping }
http-request return content-type image/x-icon file /var/www/favicon.ico  \
    if { path /favicon.ico }
http-request return status 403 content-type text/plain    \
    lf-string "Access denied. IP %[src] is blacklisted."  \
    if { src -f /etc/haproxy/blacklist.lst }