Setting Up Access Control Lists (ACLs)

The purpose in using Access Control Lists (ACL) is to provide a flexible solution to make decisions based on content extracted from the request, the response, or any environmental status.

Its principle is the following:

  • Extract a data sample from a stream, table, or the environment
  • Apply optionally some format conversion to the extracted sample
  • Apply one or multiple pattern matching methods on this sample

ACL Syntax

ACLs are defined using the keyword acl. The syntax is:

acl <aclname> <criterion>[,<converter>] [flags] [operator] [<pattern>] …

ACLs require the following parameters:

<aclname>

Name of the ACL to describe it as much as possible.
It must have upper and lower case letters, digits, ‘-‘ (dash), ‘_’ (underscore) , ‘.’ (dot) and ‘:’ (colon).
It is case sensitive; hence my_acl and My_Acl are two different ACLs.

<criterion> Based on sample fetches, it describes the portion of the request or response where this ACL applies.
[<converter>] One or several <converter> can be specified, separated by a comma ‘,’. They can be used to manipulate the data provided by the <criterion>
[flags] (optional) Completes the <criterion> to make it more accurate on where and how to apply the ACL.
[operator] (optional) It is possible to apply an <operator> when matching the data provided by the <criterion> against <pattern>.
[<pattern>] (optional)

Data provided by <criterion> is compared to a<pattern> list.
It is as the same type as the data provided by the <criterion> or the result of the latest <converter>.
Matching results will define the result of the whole ACL.

An ACL can return two values:

TRUE when the data from <criterion> matches at least one of the <pattern>
FALSE when the data from <criterion> does not match any of the <pattern>

ACL Rules

For ACLs sharing the same name, the following rules apply:

  • It is possible to use the same <aclname> for many ACLs, even if they do not have the same matching criterion
  • A logical OR applies between all of them

ACL Criterion

The criterion is generally the name of a sample fetch method, or one of the Sample fetch ACL derivatives declinations.

The sample fetch methods are the only ones that support a conversion.

The ACL declinations can describe alternate matching methods for a same sample fetch method.

As a reminder, sample fetch methods can return the following data types:

  • boolean
  • integer (signed or unsigned)
  • IPv4 or IPv6 address
  • string
  • binary data block

Criterion converters

Converters transform any supported data type any other data type.

The resulting sample’s type is the same as the type of the last converter applied to the list, which defaults to the type of the sample fetch method.

Sample fetch ACL derivatives

All ACL-specific criteria imply a default matching method. Most often, these criteria are built by concatenating the name of the original sample fetch method and the matching method.

For example, hdr_beg applies the beg match to the sample retrieved using the hdr fetch.

Since all ACL-specific criteria rely on a sample fetch method, you can use the original sample fetch method and the explicit matching method with -m.

If an alternate match is specified using “-m” on an ACL-specific criterion, the matching method is applied to the underlying sample fetch method.

For example, all ACLs below are exactly equivalent:

acl short_form  hdr_beg(host)        www.
acl alternate1  hdr_beg(host) -m beg www.
acl alternate2  hdr_dom(host) -m beg www.
acl alternate3  hdr(host)     -m beg www.

The table below presents ACL derivatives from their respective sample fetch:

Sample fetch Derivative ACLs
req.rdp_cookie([<name>]) req_rdp_cookie([<name>]): exact string match
req.rdp_cookie_cnt([name]) req_rdp_cookie_cnt([<name>]): integer match
req.ssl_sni req_ssl_sni: exact string match
req.ssl_ver req_ssl_ver: decimal match
base base: exact string match

base_beg: prefix match

base_dir: subdir match

base_dom: domain match

base_end: suffix match

base_len: length match

base_reg: regex match

base_sub: substring match
req.cook([<name>]) cook([<name>]): exact string match

cook_beg([<name>]): prefix match

cook_dir([<name>]): subdir match

cook_dom([<name>]): domain match

cook_end([<name>]): suffix match

cook_len([<name>]): length match

cook_reg([<name>]): regex match

cook_sub([<name>]): substring match
req.hdr([<name>[,<occ>]]) hdr([<name>[,<occ>]]): exact string match

hdr_beg([<name>[,<occ>]]): prefix match

hdr_dir([<name>[,<occ>]]): subdir match

hdr_dom([<name>[,<occ>]]): domain match

hdr_end([<name>[,<occ>]]): suffix match

hdr_len([<name>[,<occ>]]): length match

hdr_reg([<name>[,<occ>]]): regex match

hdr_sub([<name>[,<occ>]]): substring match
http_auth_group(<userlist>) http_auth_group(<userlist>): group … Returns true when the user extracted from the request and whose password isvalid according to the specified userlist belongs to at least one of the groups.
method method: case insensitive method match
path path: exact string match

path_beg: prefix match

path_dir: subdir match

path_dom: domain match

path_end: suffix match

path_len: length match

path_reg: regex match

path_sub: substring match
req.ver req_ver: exact string match
res.cook([<name>]) scook([<name>]: exact string match
res.hdr([<name>[,<occ>]]) shdr([<name>[,<occ>]]): exact string match

shdr_beg([<name>[,<occ>]]): prefix match

shdr_dir([<name>[,<occ>]]): subdir match

shdr_dom([<name>[,<occ>]]): domain match

shdr_end([<name>[,<occ>]]): suffix match

shdr_len([<name>[,<occ>]]): length match

shdr_reg([<name>[,<occ>]]): regex match

shdr_sub([<name>[,<occ>]]): substring match
res.ver resp_ver: exact string match
url

url: exact string match

url_beg: prefix match

url_dir: subdir match

url_dom: domain match

url_end: suffix match

url_len: length match

url_reg: regex match

url_sub: substring match

urlp(<name>[,<delim>]) urlp(<name>[,<delim>]): exact string match

urlp_beg(<name>[,<delim>]): prefix match

urlp_dir(<name>[,<delim>]): subdir match

urlp_dom(<name>[,<delim>]): domain match

urlp_end(<name>[,<delim>]): suffix match

urlp_len(<name>[,<delim>]): length match

urlp_reg(<name>[,<delim>]): regex match

urlp_sub(<name>[,<delim>]): substring match

ACL Flags

ACLs support the following flags:

  • -i: ignores case during matching of all subsequent patterns
  • -f </path/tp/file>: loads patterns from /path/to/file
  • -m <method>: uses a specific pattern matching method
  • -n: forbids DNS resolution
  • -M: loads the file pointed by -f like a map file
  • -u: forces the ACL unique id
  • : forces end of flags. Useful when a pattern looks like one of the flags.

acl ‘-f’ flag

If you use -m in conjunction with -f, you must place it first.

The -f flag must be followed by the name of a file from which all lines will be read as individual pattern. It is possible to pass multiple -f arguments if the patterns must be loaded from multiple files.

The following actions happen when HAProxy loads an ACL pattern list from a file:

  • Empty lines are ignored
  • Lines starting with a sharp (‘#’) are ignored
  • All leading spaces and tabs are stripped
If it is absolutely necessary to insert a valid pattern that begins with a sharp character, you can prefix it with a space character so it is not read as a comment.

acl ‘-M’ flag

The -M flag allows an ACL to use a map file. With this flag set, the file is parsed as a two column file:

The first column contains the patterns used by the ACL, and the second column contain the samples. The samples can be used later by a map. This can be useful in some cases where an ACL would be used to check for a pattern in a map before it applies mapping.

acl ‘-u’ flag

The -u flag forces the unique ID of the ACL.

This unique ID is used with the stats socket interface to identify an ACL and dynamically change its values.

An ACL that uses a file is always identified by its file name even if an ID is set.

acl ‘-m’ flag

If you use -m in conjunction with -f, you must place it first.

The -m flag is used to select a specific matching method on the input sample.

All ACL-specific criteria imply a matching method and generally do not need this flag. However, this flag is useful with generic sample fetch methods to describe how they get matched against the patterns.

This is required for sample fetches which return data type for which there is no obvious matching method (e.g. string or binary).

When -m is specified and followed by a matching method name, this method is used instead of the default one for the criterion. This makes it possible to match contents in ways that were not initially planned, or with sample fetch methods which return a string. The matching method also affects the way the patterns are parsed.

There are some restrictions. Not all methods can be used with all sample fetch methods.

The matching <methods> must be one of the following:

found Only checks if the requested sample could be found in the stream, but does not compare it against any pattern. It is recommended not to pass any pattern to avoid confusion. This matching method is useful to detect the presence of certain contents such as headers, cookies, etc. even if they are empty, without comparing or counting them.
bool Checks the sample as a boolean. This method only applies to fetches which return a boolean or integer value, and takes no pattern.
Value zero or false does not match, all other values match.
int Matches the sample as an integer. Can apply to integer and boolean samples. Boolean false is integer 0, true is integer 1.
ip Matches the sample as an IPv4 or IPv6 address. It is compatible with IP address sample only.
bin Matches the sample against an hexadecimal string representing a binary sequence. Can apply to binary or string samples.
len Matches the sample’s length as an integer. Can apply to binary or string samples.
str Exact string match. can apply to binary or string samples.
sub Substring match: checks that the sample contains at least one of the provided string patterns. Can apply to binary or string samples.
reg Regex match: matches the sample against a list of regular expressions. This may be used with binary or string samples.
beg Prefix match: checks that the sample begins like any of the provided pattern. Can apply to binary or string samples.
end Suffix match: checks that the sample finishes like any of the provided pattern. Can apply to binary or string samples.
dir Subdir match: checks that a slash-delimited portion of the sample exactly matches one of the provided patterns. Can apply to binary or string samples.
dom Domain match: checks that a dot-delimited portion of the sample exactly matches one of the provided pattern. Can apply to binary or string samples.

acl ‘-n’ flag

The -n flag forbids DNS resolutions. It is used when loading IPs from a file. By default, when the parser can not parse an IP address, it considers that the parsed string is a domain name and tries to resolve it using DNS.

If the DNS server is not reachable, the HAProxy configuration parsing can take several minutes waiting for DNS timeouts. During this time, it does not display error messages.

Data types and matching between samples and patterns

The default test method is implied by the output data type of this sample fetch method.

Each sample fetch or converter returns a specific data type, specified with its keyword.

When an ACL is declared using a standard sample fetch method, certain types automatically default to a matching method which are summarized in the table below:

Sample or converter output type Default matching method
boolean bool
integer int
ip ip
string str
binary none, use “-m”
In order to match a binary sample, it is mandatory to specify a matching method. See below.

The ACL engine can match these types against patterns of the following types:

  • boolean
  • integer or integer range
  • IP address / network
  • string (exact, substring, suffix, prefix, subdir, domain)
  • regular expression
  • hex block

The table below summarizes the compatibility matrix between pattern and samples or converter . It indicates the name of the matching method to use for each compatible combination.

The method in bold is the default and works without any -m flag.

pattern type boolean integer ip string binary
none (presence only) found found found found found
none (boolean value) bool bool   bool  
integer (value) int int int int  
integer (length) len len len len len
IP address     ip ip ip
exact string str str str str str
prefix beg beg beg beg beg
suffix end end end end end
substring sub sub sub sub sub
subdir dir dir dir dir dir
domain dom dom dom dom dom
regex reg reg reg reg reg
hex block       bin bin

Matching booleans

Boolean matching applies by default to boolean fetch methods or converters.

In order to match a Boolean, no pattern is required and all patterns are ignored. When using Boolean matching, the criterion returns the sample “as is” (hence the fetch), which means that a Boolean true will always match and a Boolean false will never match.

You can enforce Boolean matching by using the flag -m bool on fetch method returning an integer type. In such cases, 0 is converted to a Boolean false and all other values are converted to Boolean true.

Matching integers

Integer matching only applies to positive values.

Integer matching applies by default to integer fetch methods or converters.

You can also enforce it on boolean fetches using the flag -m int. In such cases, false is converted to the integer 0, and true is converted to the integer 1.

Integer matching also supports integer ranges and operators.

Using operators with ranges is not recommended.

Integer ranges

A range is a value expressed with a lower and an upper bound separated by a colon, which can both be omitted.

When one of the bound is omitted, it means than the range has either no start or no end. Basically, it means you want to match anything higher than or anything lower than the specified lower or upper bound specified.

Example:

  • 1024:65535 or 1024: is a valid range to represent a range of unprivileged ports.
  • 0:1023 or:1023 is a valid representation of privileged ports.

Integer operators

For easier use, comparison operators are also supported.

Available operators for integer matching are:

  • eq: true if the sample equals at least one pattern
  • ge: true if the sample is greater than or equal to at least one pattern
  • gt: true if the sample is greater than at least one pattern
  • le: true if the sample is less than or equal to at least one pattern
  • lt: true if the sample is less than at least one pattern

Decimal numbers

As a special case, some ACL functions support decimal numbers, which are two integers separated by a period. For example, this is used with version checks.

All integer properties apply to decimal numbers, including ranges and operators.

Matching strings

By default, string matching applies to string or binary fetch methods or converters , and exists in 6 formats.

They use the -m flag:

exact matchm str The sample string must match exactly any of the patterns
substring match -m sub The patterns are looked up inside the sample string, and the ACL makes a match if any is found.
prefix matchm beg The patterns are compared with the beginning of the sample string, and the ACL makes a match if any is found.
suffix match -m end The patterns are compared with the end of the sample string, and the ACL makes a match if any is found.
subdir match -m sub The patterns are looked up inside the sample string, delimited with slashes (“/”), and the ACL makes a match if any is found.
domain match -m dom The patterns are looked up inside the sample string, delimited with periods (”.”), and the ACL makes a match if any is found.

String matching applies to verbatim strings as they are passed, with the exception of the backslash (“”). This enables you to avoid characters such as the space.

When the flag -i is passed before the first string, then the matching performed is not case-sensitive.

To match the pattern -i, you can either set it after, or pass the specific flag before the first pattern. The same applies to match the pattern ”.

Matching regular expressions

Regex matching applies to verbatim samples as they are passed, with the exception of the backslash (“”). This enables you to avoid characters such as the space.

When the flag -i is passed before the first regex, then the matching performed is not case-sensitive.

To match the pattern -i, you can either set it after, or pass the specific flag before the first pattern. The same applies to match the pattern ”.

Matching arbitrary data blocks

It is possible to match samples against a binary block which may not be safely represented as a string. For this, the patterns must be passed as a series of hexadecimal digits in an even number.

This works when the match method is set to binary. Each sequence of two digits represents a byte. The hexadecimal digits can either be in upper or lower case.

Matching IPv4 and IPv6 addresses

You can specify a pattern of IPv4 addresses either as plain addresses or with an appended netmask, in which case the IPv4 address matches whenever it is within the network.

You can also replace plain addresses with a resolvable host name using DNS, but this practice is generally not recommended as it makes it more difficult to read and debug configurations.

If you use host names, ensure that they are present in /etc/hosts so that the configuration does not depend on any random DNS match at the moment the configuration is parsed.

You can enter IPv6 addresses in their usual form, with or without an appended netmask . Only bit counts are accepted for IPv6 netmasks.

To avoid any risk of problems with randomly resolved IP addresses, host names are never allowed for IPv6 patterns.

HAProxy can also match IPv4 addresses with IPv6 addresses in the following situations:

Sample address family Pattern address family Match
IPv4 IPv4 IPv4 using the supplied mask.
IPv6 IPv6 IPv6 using the supplied mask.
IPv6 IPv4 IPv4 using the pattern’s mask if the IPv6 address matches with 2002:IPV4::, ::IPV4 or ::ffff:IPV4; otherwise it fails.
IPv4 IPv6 IPv4 is first converted to IPv6 by prefixing it with ::ffff: Then the match is applied in IPv6 using the supplied IPv6 mask.

Predefined ACLs

Certain predefined ACLs are hard-coded in HAProxy, so they don’t need to be declared in every section that requires them. Their names are in upper case to avoid confusion.

Below is the list of predefined ACLs with their equivalence:

ACL name Equivalent to Usage
FALSE always_false Never match
HTTP req_proto_http Match if protocol is valid HTTP
HTTP_1.0 req_ver 1.0 Match HTTP version 1.0
HTTP_1.1 req_ver 1.1 Match HTTP version 1.1
HTTP_CONTENT hdr_val(content-length) gt 0 Match an existing content-length
HTTP_URL_ABS url_reg ^[^/:]*:// Match absolute URL with scheme
HTTP_URL_SLASH url_beg / Match URL beginning with “/”
HTTP_URL_STAR url * Match URL equal to “*”
LOCALHOST src 127.0.0.1/8 Match connection from local host
METH_CONNECT method CONNECT Match HTTP CONNECT method
METH_GET method GET HEAD Match HTTP GET or HEAD method
METH_HEAD method HEAD Match HTTP HEAD method
METH_OPTIONS method OPTIONS Match HTTP OPTIONS method
METH_POST method POST Match HTTP POST method
METH_TRACE method TRACE Match HTTP TRACE method
RDP_COOKIE req_rdp_cookie_cnt gt 0 Match presence of an RDP cookie
REQ_CONTENT req_len gt 0 Match data in the request buffer
TRUE always_true Always match
WAIT_END wait_end Wait for end of content analysis

ACL Examples

  • To detect quickly the presence of a cookie JSESSIONID in an HTTP request:
acl jsess_present req.cook(JSESSIONID) -m found
  • Apply a regular expression over the first 500 bytes of data in the request buffer:
acl script_tag req.payload(0,500) -m reg -i <script>
  • Apply both case-sensitive and non-case-sensitve matches, using the ‘i flag:
acl valid-ua hdr(user-agent) -f exact-ua.lst -i -f generic-ua.lst test

The following happens:

  • each line of exact-ua.lst is matched exactly against the user-agent header of the request.
  • each line of generic-ua is matched without case-sensitivity.
  • the word test is also matched without case-sensitivity.
  • Match any negative Content-Length header:
acl negative-length hdr_val(content-length) lt 0
  • Match any SSL version between 3.0 and 3.1 (inclusive):
acl sslv3 req_ssl_ver 3:3.1
  • Look for the string -i in the User-Agent header:
acl hdr_sub -- -i
  • Match the string Hello at the beginning of the input stream (Hexa values: x48 x65 x6c x6c x6f x0a):
acl hello payload(0,6) -m bin 48656c6c6f0a
  • Convert the X-Forwarded-For header into IP addresses and match for private IPs:
acl req.fhdr(X-Forwarded-For) -m ip 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
  • Match static content either in the Host header or in the URL path:
acl static hdr_dom(Host) -i static.domain.com assets.domain.com
acl static path_beg -i /static/ /images/ /css/