Haproxy ACL rules based on http server backend username

Hi there,

I am looking forward for some help on how to implement ACL rules based on server backend username login so I can share the same IP and port with several backends depending the authentication username of each back-end server. I am implementing SSL termination on Haproxy. I found what seems almost exactly the same case(link here) but the difference is they have a user list whereas I just want to provide the usernames in the ACL rule.

I had three failed attempts: :frowning:

In the following attempts, I have as the above figure 2 backends servers with login username of server 1 is “server1” and the counterpart in backend server 2 is “server2”:

1st attempt

I tried to do the rule in the frontend:

frontend one_ip_and_port_to_two_backends
    bind :8055 tfo ssl crt /etc/ssl/certs_self process 2 curves X25519:P-256:secp384r1
    bind abns@haproxy-clt3  accept-proxy tfo ssl crt /etc/ssl/certs_self process 2 curves X25519:P-256:secp384r1
    mode tcp
    option tcp-smart-accept
    acl rule1 req.fhdr(Authorization),regsub(^Basic\s+,,i),b64dec,regsub(:.+,) eq 'server1'
    acl rule2 req.fhdr(Authorization),regsub(^Basic\s+,,i),b64dec,regsub(:.+,) eq 'server2'
    use_backend server1 if rule1
    use_backend server2 if rule2
 
 backend server1
    mode http
    option tcp-smart-connect
    server server1 192.168.0.147:8091 check fall 5 rise 2 maxconn 50

 backend server2
    mode tcp
    option tcp-smart-connect
    server server2 192.168.0.62:88 check fall 5 rise 2 maxconn 50

2nd attempt

I tried to do the rule in the backend:

frontend one_ip_and_port_to_two_backends
    bind :8055 tfo ssl crt /etc/ssl/certs_self process 2 curves X25519:P-256:secp384r1
    bind abns@haproxy-clt3  accept-proxy tfo ssl crt /etc/ssl/certs_self process 2 curves X25519:P-256:secp384r1
    mode tcp
    option tcp-smart-accept
    default_backend server_seleccion_backend

backend server_seleccion_backend
    mode tcp
    option tcp-smart-connect
    acl rule1 req.fhdr(Authorization),regsub(^Basic\s+,,i),b64dec,regsub(:.+,) eq 'server1'
    acl rule2 req.fhdr(Authorization),regsub(^Basic\s+,,i),b64dec,regsub(:.+,) eq 'server2'
    use_backend server1 if rule1
    use_backend server2 if rule2
 
 backend server1
    mode http
    option tcp-smart-connect
    server server1 192.168.0.147:8091 check fall 5 rise 2 maxconn 50

 backend server2
    mode tcp
    option tcp-smart-connect
    server server2 192.168.0.62:88 check fall 5 rise 2 maxconn 50

3rd attempt

I tried a completly different approach by explicitly listing the usernames in Haproxy basic-http authentication groups:

userlist server-auth
    group is-server1 users server_username1
    user server1
    
    group is-server2 users server_username2
    user server2

frontend one_ip_and_port_to_two_backends
    bind :8055 tfo ssl crt /etc/ssl/certs_self process 2 curves X25519:P-256:secp384r1
    bind abns@haproxy-clt3  accept-proxy tfo ssl crt /etc/ssl/certs_self process 2 curves X25519:P-256:secp384r1
    mode tcp
    option tcp-smart-accept
    default_backend server_seleccion_backend

backend server_seleccion_backend
    mode tcp
    option tcp-smart-connect
    acl rule1 http_auth_group(server-auth) is-server2
    acl rule2 http_auth_group(server-auth) is-server1
    use_backend server1 if rule1
    use_backend server2 if rule2
 
 backend server1
    mode http
    option tcp-smart-connect
    server server1 192.168.0.147:8091 check fall 5 rise 2 maxconn 50

 backend server2
    mode tcp
    option tcp-smart-connect
    server server2 192.168.0.62:88 check fall 5 rise 2 maxconn 50

I restarted Haproxy without issues related to this being shown and tried to connect. Firefox shows the legged “Secure connection failed” and a substract of the log shows:

Apr 5 23:30:40 raspberrypi haproxy[25445]: 192.168.0.15:32844 [05/Apr/2020:23:30:40.607] one_ip_and_port_to_two_backends~ one_ip_and_port_to_two_backends/ -1/-1/13 0 SC 1/1/0/0/0 0/0 TLSv1.3 TLS_CHACHA20_POLY1305_SHA256
Apr 5 23:30:40 raspberrypi haproxy[25445]: 192.168.0.15:32846 [05/Apr/2020:23:30:40.677] one_ip_and_port_to_two_backends~ one_ip_and_port_to_two_backends/ -1/-1/12 0 SC 1/1/0/0/0 0/0 TLSv1.3 TLS_CHACHA20_POLY1305_SHA256
Apr 5 23:30:40 raspberrypi haproxy[25445]: 192.168.0.15:32848 [05/Apr/2020:23:30:40.724] one_ip_and_port_to_two_backends~ one_ip_and_port_to_two_backends/ -1/-1/10 0 SC 1/1/0/0/0 0/0 TLSv1.3 TLS_CHACHA20_POLY1305_SHA256

I suspect my issue is around the “eq ‘server1’”:

acl rule1 req.fhdr(Authorization),regsub(^Basic\s+,,i),b64dec,regsub(:.+,) eq 'server1'

Any pointers would be greatly appreciated!
HernĂĄn

As explained in the other thread, the requirement is that both backends use HTTP Authentication with the Basic method.

Your backends do not, what you are trying to achieve is therefor not possible.

I suggest you use different Hostnames instead, to distinguish between one and the other, so you can still use the same IP and port.

Thank you Lukas, from your answer, I think I understand my issue is I don’t have both backends using http since I have the client connecting with SSL to haproxy and this to the any of the backend servers which have HTTP . Now wouldn’t a haproxy recirculation fix that?
I mean:

  1. Client to connect to Haproxy with SSL termination which ends on backend1
  2. backend1 receives the http connection and sends it to frontend2
  3. frontend2 receives the connection as http and sends it to backend2
  4. backend2 connects to the final backend server.

If I am not wrong step 3) accomplishes the point of having both backends handling http. Is this possible or I am missing/misunderstanding something?

Here is my script approximation of what I was trying to describe above:

frontend one_ip_and_port_to_two_backends
    bind :8055 tfo ssl crt /etc/ssl/certs_self process 2 curves X25519:P-256:secp384r1
    mode tcp
    option tcp-smart-accept
    default_backend recir_client1

backend send_no_ssl
    server loopback-for-tls abns@haproxy-clientX send-proxy-v2
     
frontend one_ip_and_port_to_two_backends
    bind abns@haproxy-clientX  accept-proxy tfo
    mode http
    acl rule1 req.fhdr(Authorization),regsub(^Basic\s+,,i),b64dec,regsub(:.+,) eq 'user1'
    acl rule2 req.fhdr(Authorization),regsub(^Basic\s+,,i),b64dec,regsub(:.+,) eq 'user2'
    use_backend server1 if rule1
    use_backend server2 if rule2
     
 backend server1
    mode http
    option tcp-smart-connect
    server server1 192.168.0.147:8091 check fall 5 rise 2 maxconn 50

 backend server2
    mode tcp
    option tcp-smart-connect
    server server2 192.168.0.62:88 check fall 5 rise 2 maxconn 50

Thank you!
HernĂĄn

No, you are misinterpreting what I’m saying.

This is not about HTTP vs HTTPS. The fact that you are using HTTPS on the frontend is not a problem at all, and you certainly do not need recirculation . This is about HTTP(S) Authentication (with 401 Unauthorized responses, etc, like in the other thread) vs a custom application level authentication with cookies (probably).

The former can be done, the latter can’t be done.

Like I said my suggestion is to use different hostnames instead.

1 Like

Thank you! Finally I understood the the Basic Authentication thing.
For others whom might be looking for this, this is how I dectected which of my backend servers support Basic Authentication with 401 responses:

Just use curl -v SERVER:PORT you should see something related to 401 Unauthorized “HTTP/1.0 401 Unauthorized”

See the example below:

pi@raspberrypi:~ $ curl -v 192.168.0.113:8096
* Expire in 0 ms for 6 (transfer 0x5a4880)
*   Trying 192.168.0.113...
* TCP_NODELAY set
* Expire in 200 ms for 4 (transfer 0x5a4880)
* Connected to 192.168.0.113 (192.168.0.113) port 8096 (#0)
> GET / HTTP/1.1
> Host: 192.168.0.113:8096
> User-Agent: curl/7.64.0
> Accept: */*
> 
* HTTP 1.0, assume close after body
< HTTP/1.0 401 Unauthorized
< Content-type: text/plain
< Connection: close
< Server: MJPG-Streamer/0.2
< Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0
< Pragma: no-cache
< Expires: Mon, 3 Jan 2000 12:34:56 GMT
< WWW-Authenticate: Basic realm="MJPG-Streamer"
< 
401: Not Authenticated!
* Closing connection 0

And this is how I tested it with the backends which supported it:

frontend one_ip_and_port_to_two_backends
    bind :8055 tfo ssl crt /etc/ssl/certs_self curves X25519:P-256:secp384r1
    mode tcp
    option tcp-smart-accept
    #acl rule1 req.fhdr(Authorization),regsub(^Basic\s+,,i),b64dec,regsub(:.+,) -f /var/tmp/migrated_users.lst
    tcp-request inspect-delay 10s
    use_backend server1 if { req.fhdr(Authorization),regsub(^Basic\s+,,i),b64dec,regsub(:.+,) eq madmin }
    use_backend server2 if { req.fhdr(Authorization),regsub(^Basic\s+,,i),b64dec,regsub(:.+,) eq user2 }
     
 backend server1
    mode http
    option tcp-smart-connect
    server server1 127.0.0.1:8096
    
 backend server2
    mode http
    option tcp-smart-connect
    server server1 192.168.0.187:8098

In order to test it, the browser didn’t work for me because this logic seems to expect the username to acompany the url request at the same time. I expected to visit the site, then be asked for username and password and then Haproxy select the backend.

I could successfully test it again with curl by passing both parameters at the sametime; username and site address like this:

curl --user user:password https://192.168.0.113:8055
or

Hope this clarifies for others.

Thanks for your help @lukastribus! :slight_smile: