QUIC bind fails, TCP is OK

Hello, haproxy users,

I am trying to setup haproxy as a frontend instead of IPVS. I am not sure what is the recommended way of hardening the environment in which haproxy runs - whether to let systemd drop privileges and switch to a non-root user, or let haproxy itself to do the work. Systemd list of hardening features is much larger, so I prefer that. So far it works for me except for the QUIC socket. I am getting the following error:

Jan  9 21:44:22 localhost haproxy[1585673]: Permission error on QUIC socket binding for proxy myservice.fqdn. Consider using setcap cap_net_bind_service (Linux only) or running as root.

The problem is that I get this error only when the first HTTP3 request arrives, and that TCP sockets (HTTP 1 and 2) :80 and :443 work without problem. It seems that haproxy binds the TCP sockets before dropping privileges, but QUIC socket is created only later (I have verified this using strace).

One set of options which works for me is to let systemd switch to a non-root user, and add the CAP_NET_BIND_SERVICE capability to the process (not a file):

[Service]
...
Type=notify
User=haproxy
Group=haproxy
AmbientCapabilities=CAP_NET_BIND_SERVICE

So - why does the QUIC socket get bound so late? And what is the recommended way of dropping privileges of haproxy process?

Thanks,

-Yenya

You can find the suggested systemd unit file in the tarball at admin / systemd / haproxy.service.in.
Haproxy must be startet as root and will drop privileges based on the user/group haproxy configuration.

Dropping privileges in systemd is not supported.

As you may know, TCP and UDP does not have the same POSIX API. For TCP, each new connection is assigned to its owned socket thanks to accept() syscall on the server side. This syscall is not available for UDP, traditionnaly multiple connections are multiplexed over a single socket.

However, this is not scalable, in particular if there is a lot of parallel connections. For this reason, haproxy by default assign a new socket for each QUIC connection. This requires manual invocation of socket() + bind() at runtime, which is the reason why the capability net_admin is required if a privileged port is used. Note that this behavior can be changed with the following option to only use the listener socket for every connections :
https://docs.haproxy.org/dev/configuration.html#tune.quic.socket-owner

However, as I already mentionned, this is not scalable if the number of threads and/or parallel connections is important.