6

I discovered recently that you can match a backend dynamically, based on the request hostheader, like this:

 use_backend %[req.hdr(host),lower]

However, does anyone know any way I could use the subdomain of the request hostheader to match the backend?

E.g. something along these lines:

backend one
backend two

use_backend %[<SUBDOMAIN OF HOSTHEADER>,lower]

which would match like this:

 one.example.com -> backend one
 two.example.com -> backend two

2 Answers 2

7

Adding and removing DNS entries allows you to route subdomains to various backends on the fly, buy you still need to define those backends so there's still a service restart. As such, I'm not entirely sure of the usefulness of this config.

In any event, here's how you'd do it.

We know we can find the contents of the host header, by using req.hdr (req.hdr(host)), but that gives us the FQDN of the request, not the subdomain.

Thankfully, there's a regsub converter we should be able to apply to the req.hdr sample to clip off the base domain and TLD.

regsub(<regex>,<subst>[,<flags>])
Applies a regex-based substitution to the input string. It does the same
operation as the well-known "sed" utility with "s/<regex>/<subst>/". By
default it will replace in the input string the first occurrence of the
largest part matching the regular expression <regex> with the substitution
string <subst>. It is possible to replace all occurrences instead by adding
the flag "g" in the third argument <flags>. It is also possible to make the
regex case insensitive by adding the flag "i" in <flags>. Since <flags> is a
string, it is made up from the concatenation of all desired flags. Thus if
both "i" and "g" are desired, using "gi" or "ig" will have the same effect.
It is important to note that due to the current limitations of the
configuration parser, some characters such as closing parenthesis or comma
are not possible to use in the arguments.
The first use of this converter is
to replace certain characters or sequence of characters with other ones.

The emphasis in that quote is mine and aims to show that in this case, where the regex you'd need is ^(.*)(?:\..*){2}$, it won't work because of the parenthesis.

Thus, you'll need to use the field converter.

field(<index>,<delimiters>)
Extracts the substring at the given index considering given delimiters from
an input string. Indexes start at 1 and delimiters are a string formatted
list of chars.

field(1,'.')

If we put the whole sample pipline together, the use_backend line looks like:

use_backend BE:subs-%[req.hdr(host),lower,field(1,'.')]

Now, this opens up the fact that one.*.* will go to the same backend, and could lead to some very weird situations.

It might make some sense to check the base domain and TLD to ensure they're what you expect. Assuming you've only got two (example.com and foo.com) of them, you'd use req.hdr_end(host) to check for them, making the ACL look like:

 acl is_valid_base_domain  req.hdr_end(host) -i example.com foo.com

And if we put it all together, the whole config would look something like this:

frontend FE:subs
  ...
  acl is_valid_base_domain  req.hdr_end(host) -i example.com foo.com
  use_backend BE:subs-%[req.hdr(host),lower,field(1,'.')] if is_valid_base_domain

  default_backend BE:subs:default

backend BE:subs-one
  #matches one.example.com, one.foo.com
  ...

backend BE:subs-two
  #matches two.example.com, two.foo.com
  ...

backend BE:subs-three
  #matches three.example.com, three.foo.com
  ...

backend BE:subs:default
  #matches *.example.com, *.foo.com 
  ...

You can get even fancier if you want by having different "dynamic" backends for each sub-domain, per base domain; you'd just need to use the pieces above to work that out.

7
  • 1
    Thanks, this looks great. However I'm getting an error on the use_backend directive: sample fetch <req.hdr(host),lower,regsub(^(.*)(?:\..*){2}$,\1)> failed with : invalid arg 2 in conv method 'regsub' : missing arguments (got 1/2), type 'string' expected - any ideas?
    – UpTheCreek
    Nov 25, 2015 at 14:54
  • Yeah... I didn't actually test it. It was just based on theory. You might have to wrap the arguments in quotes: regsub('^(.*)(?:\..*){2}$','\1')
    – GregL
    Nov 25, 2015 at 15:00
  • I already tried that (since it was complaining about string in error), but the error remains the same. I'll read more about this regsub converter. Thank you.
    – UpTheCreek
    Nov 25, 2015 at 15:04
  • I think it might be this limitation (closing parenthesis) It is important to note that due to the current limitations of the configuration parser, some characters such as closing parenthesis or comma are not possible to use in the arguments. Doesn't seem to be an obvious way around that :(
    – UpTheCreek
    Nov 25, 2015 at 15:08
  • 2
    @UpTheCreek, Fixed the answer to reflect what you discovered. If it's to your liking, feel free to throw me a Check.
    – GregL
    Dec 2, 2015 at 1:46
5

As far as I know, HAProxy doesn't have regex support to extract specific part of the subdomain from the Host header and then assign that value to a variable, which is later used to form complete backend name.

However, you one way to solve your problem would be to use mapping:

frontend frontend_main
...
use_backend %[req.hdr(host),lower,map(/etc/haproxy/subdomains.map,backend_main)]

The content of /etc/haproxy/subdomains.map would look like this:

#domainname  backendname
one.example.com backend_one
two.example.com backend_two
etc.domain1.com backend_etc

All requests that don't match any of the subdomains in that file will go to backend_main backend.

3
  • Thanks. I want to try and keep it dynamic, to reduce the number of config changes necessary. I'll certainly consider this option though, if it turns out dynamic is not possible.
    – UpTheCreek
    Nov 23, 2015 at 16:33
  • Be sure to use map_dom instead of map. That's because, according to RFC specification, the host header MAY contain port number, e.g. example.com:443 or example.com:8080. map_dom extracts only the domain portion for comparison. Apr 7, 2016 at 16:32
  • One other caveat is that map_dom will search the file from top to bottom so ordering is important. If you have a match at the top "a.com" and a match at the bottom "b.a.com", and the domain comes in as "b.a.com" you would think it would choose the last. Instead, it chooses "a.com" which technically matches, although it's clearly not the desired or intended match. Sep 13, 2016 at 17:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .