Can't authenticate with http basic auth to website behind HAProxy

Hey all,

I am using HAProxy 2.4.19 on OPNsense 22.7.9_3.

Today, I have a small problem. I have a Mikrotik switch that can only be accessed via Port 80 and itself requires HTTP basic auth.
I have setup my backend and map file like always, the site is reachable fine, however I cannot login. I enter my credentials and press enter, and the dialog for entering my credentials just shows back up infinitely. The credentials are correct, if I go to the switch via IP-address I can log in normally.

I’m pretty sure I just have to add an option somewhere to pass the auth header, however, I can’t figure it out. Googling for the problem just leads to info on how to setup HAProxy to do basic auth, which I don’t need… The logs also don’t seem to show anything useful.

Anyone has any ideas? Thanks in advance.

My haproxy.conf:

# Automatically generated configuration.
# Do not edit this file manually.

global
    uid                         80
    gid                         80
    chroot                      /var/haproxy
    daemon
    stats                       socket /var/run/haproxy.socket group proxy mode 775 level admin
    nbproc                      1
    nbthread                    4
    hard-stop-after             60s
    no strict-limits
    maxconn                     10000
    tune.ssl.default-dh-param   4096
    spread-checks               2
    tune.bufsize                16384
    tune.lua.maxmem             0
    log                         /var/run/log local0 info
    lua-prepend-path            /tmp/haproxy/lua/?.lua

defaults
    log     global
    option redispatch -1
    maxconn 5000
    timeout client 30s
    timeout connect 30s
    timeout server 30s
    retries 3
    default-server init-addr last,libc
    default-server maxconn 5000

# autogenerated entries for ACLs

# autogenerated entries for config in backends/frontends

# autogenerated entries for stats

# Frontend: 0_SNI_Frontend (Listening on 0.0.0.0:80, 0.0.0.0:443)
frontend 0_SNI_Frontend
    bind 0.0.0.0:443 name 0.0.0.0:443 
    bind 0.0.0.0:80 name 0.0.0.0:80 
    mode tcp
    default_backend SSL_Backend
    # tuning options
    timeout client 30s

    # logging options

# Frontend: 1_HTTP_Frontend (Listening on 10.11.12.13:80)
frontend 1_HTTP_Frontend
    bind 10.11.12.13:80 name 10.11.12.13:80 accept-proxy 
    mode http
    option http-keep-alive
    option forwardfor
    # tuning options
    timeout client 30s

    # logging options
    # ACL: No_SSL_Condition
    acl acl_61f0e69b774101.44572667 ssl_fc

    # ACTION: HTTP_to_HTTPS_redirect-rule
    http-request redirect scheme https code 301 if !acl_61f0e69b774101.44572667

# Frontend: 1_HTTPS_Frontend (Listening on 10.11.12.13:443)
frontend 1_HTTPS_Frontend
    http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    bind 10.11.12.13:443 name 10.11.12.13:443 accept-proxy ssl curves secp384r1  no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384 ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 alpn h2,http/1.1 crt-list /tmp/haproxy/ssl/61f1488b809469.11422827.certlist 
    mode http
    option http-keep-alive
    default_backend WWW_Backend
    option forwardfor
    # tuning options
    timeout client 15m

    # logging options
    # ACL: Local_Subnet_Condition
    acl acl_61f143f1b2bc74.96164672 src 10.10.10.0/24 10.10.11.0/24 10.10.12.0/24

    # ACTION: Local_subdomains_map-rule
    use_backend %[req.hdr(host),lower,map_dom(/tmp/haproxy/mapfiles/61f143408eb157.92527912.txt)] if acl_61f143f1b2bc74.96164672
    # ACTION: Public_subdomains_map-rule
    # NOTE: actions with no ACLs/conditions will always match
    use_backend %[req.hdr(host),lower,map_dom(/tmp/haproxy/mapfiles/61f0e7787a5363.74556424.txt)] 

# Backend: SSL_Backend ()
backend SSL_Backend
    # health checking is DISABLED
    mode tcp
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    server SSL_Server 10.11.12.13 send-proxy-v2 check-send-proxy

# Backend: Mail_Backend ()
backend Mail_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server mail 10.10.10.10 ssl alpn h2,http/1.1 verify none

# Backend: Nextcloud_Backend ()
backend Nextcloud_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server nextcloud 10.10.10.10:2443 ssl verify none

# Backend: Vaultwarden_Backend ()
backend Vaultwarden_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server vaultwarden 10.10.10.10:1443

# Backend: Cockpit_Backend ()
backend Cockpit_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server cockpit 10.10.10.10:9090 ssl verify none

# Backend: Brocade_Backend ()
backend Brocade_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server brocade 10.10.10.240:443 ssl verify none

# Backend: Draytek_Backend ()
backend Draytek_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server draytek 10.10.10.1:443 ssl verify none

# Backend: ILO_Backend ()
backend ILO_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server ilo 10.10.10.11:443 ssl verify none

# Backend: OPNsense_Backend ()
backend OPNsense_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server opnsense 10.10.10.254:1443 ssl verify none

# Backend: Print_Backend ()
backend Print_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server print 10.10.10.15:443 ssl verify none

# Backend: Autoconfig_Backend ()
backend Autoconfig_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server autoconfig 10.10.10.10 ssl verify none

# Backend: Autodiscover_Backend ()
backend Autodiscover_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server autoconfig 10.10.10.10 ssl verify none

# Backend: Collabora_Backend ()
backend Collabora_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server collabora 10.10.10.10:9980 ssl verify none

# Backend: APC_Backend ()
backend APC_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    # ACL: APC_Condition
    acl acl_61f869835bd219.58052927 path -i /

    # ACTION: Redirect_APC-rule
    http-request redirect code 301 location https://apc.mydomain.com/cgi-bin/apcupsd/multimon.cgi if acl_61f869835bd219.58052927
    http-reuse safe
    server apc 10.10.10.10:1080

# Backend: UniFi_Backend ()
backend UniFi_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server unifi 10.10.10.10:8443 ssl verify none

# Backend: Notify_Backend ()
backend Notify_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server notify 10.10.10.10:7867 source 10.10.10.254

# Backend: Element_Backend ()
backend Element_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server element 10.10.10.10:3443

# Backend: Matrix_Backend ()
backend Matrix_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    # WARNING: pass through options below this line
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    http-reuse safe
    server matrix 10.10.10.10:8008

# Backend: WWW_Backend ()
backend WWW_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server www 10.10.10.10:5443 ssl verify none

# Backend: Jellyfin_Backend ()
backend Jellyfin_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server jellyfin 10.10.10.10:8096

# Backend: Fritz_Backend ()
backend Fritz_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server fritz 10.10.11.1:80

# Backend: Freetz_Backend ()
backend Freetz_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server freetz 10.10.11.1:81

# Backend: Deluge_Backend ()
backend Deluge_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    # WARNING: pass through options below this line
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    http-reuse safe
    server deluge 10.10.10.10:8112

# Backend: QNAP_Backend ()
backend QNAP_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server qnap 10.10.10.235:9090 ssl verify none

# Backend: Mikrotik_Backend ()
backend Mikrotik_Backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server mikrotik 10.10.10.241:80

# statistics are DISABLED

HAProxy by default doesn’t remove any headers as far I know.

You can do simple test with netcat utlity:

  1. Sending basic auth a.k.a authorization header to netcat directly:
  • netcat is listening on locahost port 8080
$ nc -vl 127.0.0.1 8080
  • send request to the above port with curl
$ nc -vl 127.0.0.1 8080
  • received request in netcat
└──╼$ nc -vl 127.0.0.1 8080
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on 127.0.0.1:8080
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:46380.
GET / HTTP/1.1
Host: 127.0.0.1:8080
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
User-Agent: curl/7.85.0
Accept: */*

^C
                                                                                                                         
$ echo 'dXNlcm5hbWU6cGFzc3dvcmQ=' | base64 -d
username:password 
  1. I will repeat the same test but instead calling netcat directly I will proxy it via HAProxy e.g. netcat is set-up as HAProxy backend:
  • Sending request to HAProxy running locally in docker container:
$ curl -u username2:password2 http://127.0.0.1:9001
  • Request received in netcat:
$ nc -vl 127.0.0.1 8080
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on 127.0.0.1:8080
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:54950.
GET / HTTP/1.1
host: 127.0.0.1:9001
authorization: Basic dXNlcm5hbWUyOnBhc3N3b3JkMg==
user-agent: curl/7.85.0
accept: */*
connection: close

^C
                                                                                                                       
$ echo 'dXNlcm5hbWUyOnBhc3N3b3JkMg==' | base64 -d
username2:password2

HAProxy doesn’t strip Authorization header by default as you can see in the above example.

I would recommend to check the logs and possibly sniff the requests send to microtik via HAProxy and directly to mikrotik and compare them.