Source Hash Algorithm
I wondered what algorithm is used for the "source" load balancing option? The reason for the question is due to wanting to load balance a service that uses both TCP and UDP traffic... where the TCP and UDP must go to the same back-end. For TCP we are currently using HAproxy and simply drop the UDP traffic as this is optional for the application (Microsoft RD/TS Gateway), but UDP can improve interactive performance for remote RDP connections passed to the RD Gateway. My plan was to use LVS/keepalived for the UDP traffic but I was unsure if the "source" option in HAProxy and the "sh" (source hash) scheduling algorithm in LVS would yield consistent results. The documentation for the "sh" algorithm in LVS notes the following pseudo-code: n <- servernode[src_ip]; if (n is dead) OR (n is overloaded, such as n.conns>2*n.weight) then return NULL; return n; With the servernode array describe as "a 256-bucket hash table that maps the hash index derived from packet source IP address to the current server array". Based on this would I expect the "source" balance option in HAProxy and the "lh" selection algorithm in LVS to return a consistent back end? Or should I simply use LVS for both connections (TCP and UDP)...although HAProxy does other things on this device already...and is reasonably well understood by our team...hence the desire to use HAProxy where possible. -- Andrew Heberle
Re: HTX mode causes problems with VMWare Horizon View Zero Clients
On Tue, 15 Oct 2019 at 4:45 pm, Christopher Faulet wrote: > Le 14/10/2019 à 05:25, Andrew Heberle a écrit : > > Hi All, > > > > We have a virtual desktop deployment under VMWare Horizon View that > > uses PCoIP Zero clients that stopped working after upgrading the load > > balancers in front of the internal Connection Servers (the virtual > > desktop broker) from 1.8.12 to 2.0.7. > > > > Hi, > > When you said your client stopped working, what does it mean exactly ? > What did > you observe to said the request failed ? Looking at the logs you provided, > it > seems to be ok from the HAProxy point of view. The error is reported by the client with a horribly cryptic error that suggests it’s not parsing/understanding the XML payload that is retrieved initially. > > > Just a question. It if happens for a specific client application, are you > sure > this client is able to handle HTTP headers in a case-insensitive manner ? > In > HAProxy 2.0, HTTP headers names are sent in lower case when the HTX is > enabled. > It may be a problem for bogus applications. To be honest that was my initial thought as other clients using the same service but on different platforms are fine, so it’s a client/device specific interaction with HTX mode...which I’d bet HAProxy is more likely to be “compliant” than these particular Zero clients. I am happy to gather some additional traces though, to rule out some strange edge case however, but as you say, the initial requests seem fine so it’s questionable if traces will show anything useful. Do you think it’s worthwhile collection debug logs? > > -- > Christopher Faulet > -- Andrew Heberle
HTX mode causes problems with VMWare Horizon View Zero Clients
tion:\ Close\r\n\r\n option log-health-checks http-check expect string clientlaunch-default # View Connection servers server viewcs01 172.16.0.55:443 ssl check ca-file RootCA.pem server viewcs02 172.16.0.56:443 ssl check ca-file RootCA.pem Logs from a unsuccessful connection: Oct 14 10:31:13 lb-01 local2.info haproxy[3142]: 172.19.4.80:50835 [14/Oct/2019:10:31:13.348] fe_viewcs~ be_viewcs/viewcs01 0/0/80/24/104 200 1108 - - 2884/1/0/0/0 0/0 "POST /broker/xml HTTP/1.1" Oct 14 10:31:13 lb-01 local2.info haproxy[3142]: 172.19.4.80:62416 [14/Oct/2019:10:31:13.504] fe_viewcs~ be_viewcs/viewcs02 0/0/4/8/12 200 574 - - 2884/1/0/0/0 0/0 "POST /broker/xml HTTP/1.1" And a successful one: Oct 14 10:59:44 lb-01 local2.info haproxy[7100]: 172.19.4.80:59025 [14/Oct/2019:10:59:44.294] fe_viewcs~ be_viewcs/viewcs02 0/0/4/13/17 200 1120 - - 55/1/0/1/0 0/0 "POST /broker/xml HTTP/1.1" Oct 14 10:59:44 lb-01 local2.info haproxy[7100]: 172.19.4.80:57390 [14/Oct/2019:10:59:44.828] fe_viewcs~ be_viewcs/viewcs01 0/0/3/49/52 200 1120 - - 55/1/0/1/0 0/0 "POST /broker/xml HTTP/1.1" Oct 14 10:59:45 lb-01 local2.info haproxy[7100]: 172.19.4.80:53001 [14/Oct/2019:10:59:44.977] fe_viewcs~ be_viewcs/viewcs01 0/0/9/57/66 200 905 - - 47/1/0/1/0 0/0 "POST /broker/xml HTTP/1.1" Oct 14 10:59:45 lb-01 local2.info haproxy[7100]: 172.19.4.80:61455 [14/Oct/2019:10:59:45.065] fe_viewcs~ be_viewcs/viewcs01 0/0/1/7/8 200 446 - - 47/1/0/1/0 0/0 "POST /broker/xml HTTP/1.1" Oct 14 10:59:45 lb-01 local2.info haproxy[7100]: 172.19.4.80:49178 [14/Oct/2019:10:59:45.127] fe_viewcs~ be_viewcs/viewcs01 0/0/2/52/54 200 964 - - 48/1/0/1/0 0/0 "POST /broker/xml HTTP/1.1" Oct 14 10:59:46 lb-01 local2.info haproxy[7100]: 172.19.4.80:49434 [14/Oct/2019:10:59:45.251] fe_viewcs~ be_viewcs/viewcs01 0/0/75/1550/1625 200 1570 - - 41/1/0/1/0 0/0 "POST /broker/xml HTTP/1.1" -- Andrew Heberle
[PATCH] MEDIUM: config: Add user/group options to program section
This patch adds "user" and "group" config options to the "program" section so the configured command can be run as a different user. --- doc/configuration.txt | 8 ++ include/types/global.h | 2 ++ src/mworker-prog.c | 70 ++ 3 files changed, 80 insertions(+) diff --git a/doc/configuration.txt b/doc/configuration.txt index a46384bf..98940a0e 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -2232,6 +2232,14 @@ command [arguments*] mandatory option of the program section. Arguments containing spaces must be enclosed in quotes or double quotes or be prefixed by a backslash. +user + Changes the executed command user ID to the from /etc/passwd. + See also "group". + +group + Changes the executed command group ID to the from /etc/group. + See also "user". + option start-on-reload no option start-on-reload Start (or not) a new instance of the program upon a reload of the master. diff --git a/include/types/global.h b/include/types/global.h index df0111c7..b6ba6737 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -215,6 +215,8 @@ struct mworker_proc { int timestamp; struct server *srv; /* the server entry in the master proxy */ struct list list; + int uid; + int gid; }; extern struct global global; diff --git a/src/mworker-prog.c b/src/mworker-prog.c index ba52406e..1d401a3c 100644 --- a/src/mworker-prog.c +++ b/src/mworker-prog.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -91,6 +92,23 @@ int mworker_ext_launch_all() mworker_cleanlisteners(); mworker_cleantasks(); + /* setgid / setuid */ + if (child->gid != -1) { + if (getgroups(0, NULL) > 0 && setgroups(0, NULL) == -1) + ha_warning("[%s.main()] Failed to drop supplementary groups. Using 'gid'/'group'" + " without 'uid'/'user' is generally useless.\n", child->command[0]); + + if (setgid(child->gid) == -1) { + ha_alert("[%s.main()] Cannot set gid %d.\n", child->command[0], child->gid); + exit(1); + } + } + + if (child->uid != -1 && setuid(child->uid) == -1) { + ha_alert("[%s.main()] Cannot set uid %d.\n", child->command[0], child->gid); + exit(1); + } + execvp(child->command[0], child->command); ha_alert("Cannot execute %s: %s\n", child->command[0], strerror(errno)); @@ -143,6 +161,8 @@ int cfg_parse_program(const char *file, int linenum, char **args, int kwm) ext_child->ipc_fd[0] = -1; ext_child->ipc_fd[1] = -1; ext_child->options |= PROC_O_START_RELOAD; /* restart the programs by default */ + ext_child->uid = -1; + ext_child->gid = -1; LIST_INIT(_child->list); list_for_each_entry(child, _list, list) { @@ -219,6 +239,56 @@ int cfg_parse_program(const char *file, int linenum, char **args, int kwm) err_code |= ERR_ALERT | ERR_FATAL; goto error; } + } else if (!strcmp(args[0], "user")) { + struct passwd *ext_child_user; + if (*(args[1]) == '\0') { + ha_alert("parsing [%s:%d]: '%s' expects a user name.\n", +file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto error; + } + + if (alertif_too_many_args(1, file, linenum, args, _code)) + goto error; + + if (ext_child->uid != -1) { + ha_alert("parsing [%s:%d] : user/uid already specified. Continuing.\n", file, linenum); + err_code |= ERR_ALERT; + goto out; + } + + ext_child_user = getpwnam(args[1]); + if (ext_child_user != NULL) { + ext_child->uid = (int)ext_child_user->pw_uid; + } else { + ha_alert("parsing [%s:%d] : cannot find user id for '%s' (%d:%s)\n", file, linenum, args[1], errno, strerror(errno)); + err_code |= ERR_ALERT | ERR_FATAL; + } + } else if (!strcmp(args[0], "group")) { + struct group *ext_child_group; + if (*(args[1]) ==
Re: [PATCH] MEDIUM: config: Add user/group options to program section
Hi Willy, It looks like my mailer was mangling the tabs so I'm hoping my (first) attempt at using git send-patch is more successful. I have also updated the commit message. Thanks. Regards, Andrew Heberle
[PATCH] MEDIUM: config: Add user/group options to program section
This patch adds "user" and "group" config options to the "program" section so the configured command can be run as a different user. I re-used the setuid/setgid code from "haproxy.c" for this so I'm hoping there are not terrible bugs I've introduced :) Regards, Andrew Heberle >From 571715863738524e3f01fa842f8816f181777b89 Mon Sep 17 00:00:00 2001 From: Andrew Heberle Date: Thu, 11 Jul 2019 10:57:19 +0800 Subject: [PATCH] MEDIUM: config: Add user/group options to program section --- doc/configuration.txt | 8 ++ include/types/global.h | 2 ++ src/mworker-prog.c | 70 ++ 3 files changed, 80 insertions(+) diff --git a/doc/configuration.txt b/doc/configuration.txt index a46384bf..98940a0e 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -2232,6 +2232,14 @@ command [arguments*] mandatory option of the program section. Arguments containing spaces must be enclosed in quotes or double quotes or be prefixed by a backslash. +user + Changes the executed command user ID to the from /etc/passwd. + See also "group". + +group + Changes the executed command group ID to the from /etc/group. + See also "user". + option start-on-reload no option start-on-reload Start (or not) a new instance of the program upon a reload of the master. diff --git a/include/types/global.h b/include/types/global.h index df0111c7..b6ba6737 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -215,6 +215,8 @@ struct mworker_proc { int timestamp; struct server *srv; /* the server entry in the master proxy */ struct list list; + int uid; + int gid; }; extern struct global global; diff --git a/src/mworker-prog.c b/src/mworker-prog.c index ba52406e..1d401a3c 100644 --- a/src/mworker-prog.c +++ b/src/mworker-prog.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -91,6 +92,23 @@ int mworker_ext_launch_all() mworker_cleanlisteners(); mworker_cleantasks(); + /* setgid / setuid */ + if (child->gid != -1) { + if (getgroups(0, NULL) > 0 && setgroups(0, NULL) == -1) + ha_warning("[%s.main()] Failed to drop supplementary groups. Using 'gid'/'group'" + " without 'uid'/'user' is generally useless.\n", child->command[0]); + + if (setgid(child->gid) == -1) { + ha_alert("[%s.main()] Cannot set gid %d.\n", child->command[0], child->gid); + exit(1); + } + } + + if (child->uid != -1 && setuid(child->uid) == -1) { + ha_alert("[%s.main()] Cannot set uid %d.\n", child->command[0], child->gid); + exit(1); + } + execvp(child->command[0], child->command); ha_alert("Cannot execute %s: %s\n", child->command[0], strerror(errno)); @@ -143,6 +161,8 @@ int cfg_parse_program(const char *file, int linenum, char **args, int kwm) ext_child->ipc_fd[0] = -1; ext_child->ipc_fd[1] = -1; ext_child->options |= PROC_O_START_RELOAD; /* restart the programs by default */ + ext_child->uid = -1; + ext_child->gid = -1; LIST_INIT(_child->list); list_for_each_entry(child, _list, list) { @@ -219,6 +239,56 @@ int cfg_parse_program(const char *file, int linenum, char **args, int kwm) err_code |= ERR_ALERT | ERR_FATAL; goto error; } + } else if (!strcmp(args[0], "user")) { + struct passwd *ext_child_user; + if (*(args[1]) == '\0') { + ha_alert("parsing [%s:%d]: '%s' expects a user name.\n", +file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto error; + } + + if (alertif_too_many_args(1, file, linenum, args, _code)) + goto error; + + if (ext_child->uid != -1) { + ha_alert("parsing [%s:%d] : user/uid already specified. Continuing.\n", file, linenum); + err_code |= ERR_ALERT; + goto out; + } + + ext_child_user = getpwnam(args[1]); + if (ext_child_user != NULL) { + ext_child->uid = (int)ext_child_user->pw_uid; + } else { + ha_alert("parsing [%s:%d] : cannot find user id for '%s' (%d:%s)\n", file, linenum, args[1], errno, strerror(errno)); + err_code |= ERR_ALERT | ERR_FATAL; + } + } else if (!strcmp(args[0], "group")) { + struct group *ext_child_group; + if (*(args[1]) == '\0') { + ha_alert("parsing [%s:%d]: '%s' expects a group name.\n", +file, linenum, args[0]); + err_code |=
Proof of concept SPOE based SSO solution
Hi All, I have put together a Go based proof of concept SPOE agent that also implements a SAML 2 Service Provider (SP) in order to do "SSO" in HAProxy. The code is located here: https://gitlab.com/andrewheberle/go-http-auth-sso The basic process is that SPOA is used to check if the user is logged in or not and then based on the set variables you can make decisions via "http-request" rules. This originally started out without the SPOE part and was using the Lua http-auth-request script (https://github.com/TimWolla/haproxy-auth-request), however with the release of the Go SPOE package (https://github.com/Aestek/haproxy-connect/tree/master/spoe) I rewrote it based on that. Our use case is to have the SP pointed to a IdP in Azure so we can do single-sign-on to Office 365 and we have "http-request" rules in place to set some custom headers that our application uses for authentication/authorisation. These are set based on the variables that come back from the SPOA, which come from the claims in the authentication process. Hopefully this is of some use to people. Any feedback and constructive criticism is welcome. -- Andrew Heberle
PATCH: s6 readiness notification support
I have added working (for me at least) s6 notification support to haproxy so it can signal that it's ready when running under the s6 supervision suite. The process used under s6 is that the daemon signals readiness by writing a line to an arbitrary FD (that the supervision suite has been told about), then closing the FD as documented here: https://skarnet.org/software/s6/notifywhenup.html This signals the transition from started to ready. At this stage the FD written to and closed is hard coded as fd@3 as I have reached the end of my C expertise getting this far... Although it seems like it's not something that many people are hanging out for based on the responses to a previous email to the list, I thought I'd send the patch through as-is anyway. Regards, Andrew Heberle >From 9668e699b85bc5494d1a27e550e987ce43cb Mon Sep 17 00:00:00 2001 From: Andrew Heberle Date: Wed, 1 May 2019 09:40:41 +0800 Subject: [PATCH] Add s6 notification support --- include/types/global.h | 1 + src/haproxy.c | 12 2 files changed, 13 insertions(+) diff --git a/include/types/global.h b/include/types/global.h index ba3738b..9481d64 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -72,6 +72,7 @@ #define GTUNE_BUSY_POLLING (1<<11) #define GTUNE_LISTENER_MQ (1<<12) #define GTUNE_SET_DUMPABLE (1<<13) +#define GTUNE_USE_S6_NOTIFY (1<<14) /* Access level for a stats socket */ #define ACCESS_LVL_NONE 0 diff --git a/src/haproxy.c b/src/haproxy.c index 4c5c839..99a6f67 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -446,6 +446,7 @@ static void usage(char *name) #if defined(USE_SYSTEMD) " -Ws master-worker mode with systemd notify support.\n" #endif + " -Wn master-worker mode with s6 notify support.\n" " -q quiet mode : don't display messages\n" " -c check mode : only check config files and exit\n" " -n sets the maximum total # of connections (uses ulimit -n)\n" @@ -652,6 +653,13 @@ static void mworker_loop() if (global.tune.options & GTUNE_USE_SYSTEMD) sd_notifyf(0, "READY=1\nMAINPID=%lu", (unsigned long)getpid()); #endif + /* if -Wn mode was requested then open fd 3, write an arbritrary line and close the fd */ + if (global.tune.options & GTUNE_USE_S6_NOTIFY) { + FILE *notifyfd; + notifyfd = fdopen(3, "w"); + fprintf(notifyfd, "UP\n"); + fclose(notifyfd); + } /* Busy polling makes no sense in the master :-) */ global.tune.options &= ~GTUNE_BUSY_POLLING; @@ -1427,6 +1435,10 @@ static void init(int argc, char **argv) usage(progname); #endif } + else if (*flag == 'W' && flag[1] == 'n') { + arg_mode |= MODE_MWORKER | MODE_FOREGROUND; + global.tune.options |= GTUNE_USE_S6_NOTIFY; + } else if (*flag == 'W') arg_mode |= MODE_MWORKER; else if (*flag == 'q') -- 2.9.0.windows.1
Readiness notification support under s6
Hi All, In addition to the current readiness notification when running under systemd via "sd_notify", I was hoping to have the daemon readiness notification supported by "s6" built into HAProxy. The mechanism used by s6, which amounts to the daemon writing to, then closing an arbitrary file descriptor, is covered here: https://skarnet.org/software/s6/notifywhenup.html The supervision suite waits for this before transitioning the service from up (ie daemon has been spawned) to ready (ie daemon is ready to do useful work). I've got a quick and dirty (and relatively untested) patch for this already, but wanted to see if this was something that held any interest to anyone apart from myself. -- Andrew Heberle
Certificate bundles seem to be non-functional
I am attempting to utilise certificate bundles so we can have multi-type certs in haproxy however this seems non-functional. I have a two cert bundles as follows (only testing with RSA certs at the moment): /etc/haproxy/ssl # ls -l /etc/haproxy/ssl/ total 16 -rw-r--r-- 1 root root 1184 Dec 20 01:39 test1.pem.issuer.rsa -rw-r--r-- 1 root root 2888 Dec 20 01:26 test1.pem.rsa -rw-r--r-- 1 root root 1184 Dec 20 01:40 test2.pem.issuer.rsa -rw-r--r-- 1 root root 2888 Dec 20 01:30 test2.pem.rsa With the following config of my two front-ends: frontend test1 bind *:5000 ssl crt test1.pem default_backend app1 frontend test2 bind *:5001 ssl crt test2.pem default_backend app2 But this then fails: /etc/haproxy/ssl # haproxy -f /etc/haproxy/haproxy.cfg -c [ALERT] 353/014339 (59) : parsing [/etc/haproxy/haproxy.cfg:34] : 'bind *:5000' : unable to stat SSL certificate from fi le '/etc/haproxy/ssl/test1.pem' : No such file or directory. [ALERT] 353/014339 (59) : parsing [/etc/haproxy/haproxy.cfg:38] : 'bind *:5001' : unable to stat SSL certificate from fi le '/etc/haproxy/ssl/test2.pem' : No such file or directory. [ALERT] 353/014339 (59) : Error(s) found in configuration file : /etc/haproxy/haproxy.cfg [ALERT] 353/014339 (59) : Fatal errors found in configuration. Build Options: HA-Proxy version 1.7.9 2017/08/18 Copyright 2000-2017 Willy Tarreau <wi...@haproxy.org> Build options : TARGET = linux2628 CPU = generic CC = gcc CFLAGS = -Os -fomit-frame-pointer OPTIONS = USE_ZLIB=1 USE_OPENSSL=1 USE_LUA=1 USE_PCRE=1 Default settings : maxconn = 2000, bufsize = 16384, maxrewrite = 1024, maxpollevents = 200 Encrypted password support via crypt(3): yes Built with zlib version : 1.2.11 Running on zlib version : 1.2.11 Compression algorithms supported : identity("identity"), deflate("deflate"), raw-deflate("deflate"), gzip("gzip") Built with OpenSSL version : LibreSSL 2.6.3 Running on OpenSSL version : LibreSSL 2.6.3 OpenSSL library supports TLS extensions : yes OpenSSL library supports SNI : yes OpenSSL library supports prefer-server-ciphers : yes Built with PCRE version : 8.41 2017-07-05 Running on PCRE version : 8.41 2017-07-05 PCRE library supports JIT : no (USE_PCRE_JIT not set) Built with Lua version : Lua 5.3.4 Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT IP_FREEBIND Available polling systems : epoll : pref=300, test result OK poll : pref=200, test result OK select : pref=150, test result OK Total: 3 (3 usable), will use epoll. Available filters : [COMP] compression [TRACE] trace [SPOE] spoe -- Andrew Heberle