From 2527d9dcd198a5ead99b6c4eaf7dd3856d7adf20 Mon Sep 17 00:00:00 2001 From: Hyeonggeun Oh Date: Mon, 2 Feb 2026 22:31:33 +0900 Subject: [PATCH] MEDIUM: tcpcheck: add post-80 option for mysql-check to support MySQL 8.x This patch adds a new 'post-80' option that sets the CLIENT_PLUGIN_AUTH (0x00080000) capability flag and explicitly specifies mysql_native_password as the authentication plugin in the handshake response. This patch also addes documentation content for post-80 option support in MySQL 8.x version. Which handles new default auth plugin caching_sha2_password. MySQL 8.0 changed the default authentication plugin from mysql_native_password to caching_sha2_password. The current mysql-check implementation only supports pre-41 and post-41 client auth protocols, which lack the CLIENT_PLUGIN_AUTH capability flag. When HAProxy sends a post-41 authentication packet to a MySQL 8.x server, the server responds with error 1251: "Client does not support authentication protocol requested by server". The new client capabilities for post-80 are: - CLIENT_PROTOCOL_41 (0x00000200) - CLIENT_SECURE_CONNECTION (0x00008000) - CLIENT_PLUGIN_AUTH (0x00080000) Usage example: backend mysql_servers option mysql-check user haproxy post-80 server db1 192.168.1.10:3306 check The health check user must be created with mysql_native_password: CREATE USER 'haproxy'@'%' IDENTIFIED WITH mysql_native_password BY ''; This addresses https://github.com/haproxy/haproxy/issues/2934. --- doc/configuration.txt | 8 +++++++- src/tcpcheck.c | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 52c2bf60b..260fa021b 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -10821,7 +10821,7 @@ no option logasap logging. -option mysql-check [ user [ { post-41 | pre-41 } ] ] +option mysql-check [ user [ { post-41 | pre-41 | post-80 } ] ] Use MySQL health checks for server testing May be used in the following contexts: tcp @@ -10834,6 +10834,12 @@ option mysql-check [ user [ { post-41 | pre-41 } ] ] server. post-41 Send post v4.1 client compatible checks (the default) pre-41 Send pre v4.1 client compatible checks + post-80 Send post v8.0 client compatible checks with CLIENT_PLUGIN_AUTH + capability set and mysql_native_password as the authentication + plugin. Use this option when connecting to MySQL 8.0+ servers + where the health check user is created with mysql_native_password + authentication. Example: + CREATE USER 'haproxy'@'%' IDENTIFIED WITH mysql_native_password BY ''; If you specify a username, the check consists of sending two MySQL packet, one Client Authentication packet, and one QUIT packet, to correctly close diff --git a/src/tcpcheck.c b/src/tcpcheck.c index a51d0017c..3d99dfd2a 100644 --- a/src/tcpcheck.c +++ b/src/tcpcheck.c @@ -4943,6 +4943,35 @@ int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, c "01" /* COM_QUIT command */ }; + /* MySQL >=8.0 client Authentication packet with CLIENT_PLUGIN_AUTH capability. + * MySQL 8.0 changed the default authentication plugin from mysql_native_password + * to caching_sha2_password. By setting CLIENT_PLUGIN_AUTH and specifying + * mysql_native_password as the auth plugin, we can still perform health checks + * against MySQL 8.x servers when the health check user is configured with + * mysql_native_password authentication. + * + * Client capabilities: 0x00088200 (little-endian: 00820800) + * - CLIENT_PROTOCOL_41 (0x00000200) + * - CLIENT_SECURE_CONNECTION (0x00008000) + * - CLIENT_PLUGIN_AUTH (0x00080000) + */ + static char mysql80_rsname[] = "*mysql80-check"; + static char mysql80_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) */ + "6d7973716c5f6e61746976655f" /* auth plugin name: "mysql_native_password\0" */ + "70617373776f726400" + "010000" /* packet length */ + "00" /* sequence ID */ + "01" /* COM_QUIT command */ + }; + struct tcpcheck_ruleset *rs = NULL; struct tcpcheck_rules *rules = &curpx->tcpcheck_rules; struct tcpcheck_rule *chk; @@ -4999,8 +5028,14 @@ int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, c mysql_req = mysql40_req; mysql_rsname = mysql40_rsname; } + else if (strcmp(args[cur_arg+2], "post-80") == 0) { + /* post-80: CLIENT_PLUGIN_AUTH + mysql_native_password (22 bytes) */ + packetlen = userlen + 7 + 27 + 22; + mysql_req = mysql80_req; + mysql_rsname = mysql80_rsname; + } else { - ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n", + ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41', 'pre-41' and 'post-80' (got '%s').\n", file, line, args[cur_arg], args[cur_arg+2]); goto error; }