logging.
-option mysql-check [ user <username> [ { post-41 | pre-41 | post-80 } ] ]
+option mysql-check [ user <username> [ { post-41 | pre-41 | post-80 | post-80-sha2 } ] ]
Use MySQL health checks for server testing
May be used in the following contexts: tcp
where the health check user is created with mysql_native_password
authentication. Example:
CREATE USER 'haproxy'@'%' IDENTIFIED WITH mysql_native_password BY '';
+ post-80-sha2
+ Send post v8.0 client compatible checks with CLIENT_PLUGIN_AUTH
+ capability set and caching_sha2_password as the authentication
+ plugin. Use this option when connecting to MySQL 8.0+ servers
+ where the health check user is created with caching_sha2_password
+ authentication, which is the default plugin since MySQL 8.0 and
+ the only one enabled by default since MySQL 8.4. Example:
+ CREATE USER 'haproxy'@'%' IDENTIFIED WITH caching_sha2_password BY '';
+
+ The authentication plugin announced by the check must match the one of the
+ check user. Otherwise the server either rejects the handshake (error 1251
+ with pre-41 and post-41) or replies with an authentication method switch
+ request that the check cannot answer (post-80, in which case the check is
+ still reported as passed). In both cases the server aborts the handshake and
+ counts it as a connection error for the HAProxy host. Once the host reaches
+ MySQL "max_connect_errors", the server refuses all connections from it,
+ including proxied client connections, until "FLUSH HOSTS" is issued.
If you specify a username, the check consists of sending two MySQL packet,
one Client Authentication packet, and one QUIT packet, to correctly close
sendhex "67000002FFE304436C69656E7420646F6573206E6F7420737570706F72742061757468656E7469636174696F6E2070726F746F636F6C20726571756573746564206279207365727665723B20636F6E736964657220757067726164696E67204D7953514C20636C69656E74"
} -start
+server s6 {
+ sendhex "4A0000000A382E302E3139000A0000006F3C025E6249410D00FFFFFF0200FFC715000000000000000000007C182159106E2761144322200063616368696E675F736861325F70617373776F726400"
+ recv 69
+ sendhex "0700000200000002000000"
+} -start
+
syslog S1 -level notice {
recv
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be1/srv succeeded, reason: Layer7 check passed.+info: \"8.0.19\".+check duration: [[:digit:]]+ms, status: 1/1 UP."
expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be5/srv failed, reason: Layer7 wrong status.+code: 1251.+info: \"Client does not support authentication protocol requested by server; consider upgrading MySQL client\".+check duration: [[:digit:]]+ms, status: 0/1 DOWN."
} -start
+syslog S6 -level notice {
+ recv
+ expect ~ "[^:\\[ ]\\[${h1_pid}\\]: Health check for server be6/srv succeeded, reason: Layer7 check passed.+info: \"8.0.19\".+check duration: [[:digit:]]+ms, status: 1/1 UP."
+} -start
+
haproxy h1 -conf {
global
option mysql-check user pouet post-41
server srv ${s5_addr}:${s5_port} check inter 1s rise 1 fall 1
+ backend be6
+ log ${S6_addr}:${S6_port} daemon
+ option log-health-checks
+ option mysql-check user user post-80-sha2
+ server srv ${h1_mysql3_addr}:${h1_mysql3_port} check inter 1s rise 1 fall 1
+
listen mysql1
bind "fd@${mysql1}"
tcp-request inspect-delay 100ms
tcp-request content reject
server srv ${s3_addr}:${s3_port}
+ listen mysql3
+ bind "fd@${mysql3}"
+ tcp-request inspect-delay 100ms
+ tcp-request content accept if { req.len eq 69 } { req.payload(0,69) -m bin "3C000001008208000080000121000000000000000000000000000000000000000000000075736572000063616368696E675F736861325F70617373776F7264000100000001" }
+ tcp-request content reject
+ server srv ${s6_addr}:${s6_port}
+
} -start
syslog S1 -wait
syslog S3 -wait
syslog S4 -wait
syslog S5 -wait
+syslog S6 -wait
"01" /* COM_QUIT command */
};
+ /* MySQL >=8.0 client Authentication packet with CLIENT_PLUGIN_AUTH capability
+ * and caching_sha2_password as the auth plugin. This is the default
+ * authentication plugin since MySQL 8.0, and the only one enabled by default
+ * since MySQL 8.4 (mysql_native_password is disabled and removed in 9.0).
+ *
+ * When the client does not announce the plugin used by the account, the
+ * server answers the handshake response with an AuthSwitchRequest. Since
+ * the COM_QUIT packet is pipelined right after the handshake response, the
+ * server then reads it as the auth switch reply, fails the handshake with a
+ * "packets out of order" error and counts it as a connection error for the
+ * client host in the host cache. After max_connect_errors such checks, the
+ * server blocks the HAProxy host (ER_HOST_IS_BLOCKED) and every connection
+ * from it, including proxied client traffic, is refused.
+ *
+ * Announcing caching_sha2_password with an empty auth response lets the
+ * server authenticate a password-less caching_sha2_password account in a
+ * single round trip: it answers with an OK packet and the pipelined COM_QUIT
+ * closes the session cleanly, so no connection error is recorded.
+ *
+ * Client capabilities: 0x00088200 (little-endian: 00820800)
+ * - CLIENT_PROTOCOL_41 (0x00000200)
+ * - CLIENT_SECURE_CONNECTION (0x00008000)
+ * - CLIENT_PLUGIN_AUTH (0x00080000)
+ */
+ static char mysql80_sha2_req[] = {
+ "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
+ "00820800" /* client capabilities with CLIENT_PLUGIN_AUTH */
+ "00800001" /* max packet */
+ "21" /* character set (UTF-8) */
+ "000000000000000000000000" /* 23 bytes, all zeroes */
+ "0000000000000000000000"
+ "%[var(check.username),hex]00" /* the username */
+ "00" /* auth response length (0 = no password) */
+ "63616368696e675f736861325f" /* auth plugin name: "caching_sha2_password\0" */
+ "70617373776f726400"
+ "010000" /* packet length */
+ "00" /* sequence ID */
+ "01" /* COM_QUIT command */
+ };
+
struct tcpcheck *tc = &curpx->tcpcheck;
struct tcpcheck_rule *chk;
struct tcpcheck_var *var = NULL;
packetlen = userlen + 7 + 27 + 22;
mysql_req = mysql80_req;
}
+ else if (strcmp(args[cur_arg+2], "post-80-sha2") == 0) {
+ /* post-80-sha2: CLIENT_PLUGIN_AUTH + caching_sha2_password (22 bytes) */
+ packetlen = userlen + 7 + 27 + 22;
+ mysql_req = mysql80_sha2_req;
+ }
else {
- ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41', 'pre-41' and 'post-80' (got '%s').\n",
+ ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41', 'pre-41', 'post-80' and 'post-80-sha2' (got '%s').\n",
file, line, args[cur_arg], args[cur_arg+2]);
goto error;
}
static char mysql40_rsname[] = "*mysql40-check";
static char mysql41_rsname[] = "*mysql41-check";
static char mysql80_rsname[] = "*mysql80-check";
+ static char mysql80_sha2_rsname[] = "*mysql80-sha2-check";
char *mysql_rsname = "*mysql-check";
struct tcpcheck_ruleset *rs = NULL;
int err_code = 0;
mysql_rsname = mysql40_rsname;
else if (strcmp(args[cur_arg+2], "post-80") == 0)
mysql_rsname = mysql80_rsname;
+ else if (strcmp(args[cur_arg+2], "post-80-sha2") == 0)
+ mysql_rsname = mysql80_sha2_rsname;
else {
- ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41', 'pre-41' and 'post-80' (got '%s').\n",
+ ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41', 'pre-41', 'post-80' and 'post-80-sha2' (got '%s').\n",
file, line, args[cur_arg], args[cur_arg+2]);
err_code |= ERR_ALERT | ERR_ABORT;
goto out;