This is an automated email from the ASF dual-hosted git repository. Aias00 pushed a commit to branch fix/002-r2dbc-mysql-ssl-downgrade in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
commit cebb7c150272ac7167037248372ff74a7a0d5ccf Author: liuhy <[email protected]> AuthorDate: Tue Jul 28 07:23:14 2026 -0700 [fix] stop silent SSL-to-plaintext downgrade in R2DBC MySQL route The R2DBC MySQL query route retried a failed connection with SslMode.DISABLED whenever the first (PREFERRED) attempt errored with a handshake-style failure (handshake_failure, ssl/tls handshake, ClosedChannelException, connection unexpectedly closed). A TLS handshake failure is ambiguous: it can be induced by a MitM that disrupts the handshake specifically to trigger this fallback. Retrying with SSL disabled then authenticates the monitoring user to the (possibly tampered) endpoint over plaintext, exposing the credentials. SslMode.PREFERRED already handles the legitimate plaintext case: when the server advertises no SSL capability in the initial handshake, the driver falls back to plaintext through protocol negotiation, without error. So the explicit DISABLED retry only ever fires on errors, which is exactly the unsafe path. Drop it and surface the failure instead; the caching_sha2_password / must-require-ssl diagnostic is preserved. Co-Authored-By: Claude <[email protected]> --- .../mysql/r2dbc/MysqlR2dbcQueryExecutor.java | 33 ++++++++-------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/hertzbeat-collector/hertzbeat-collector-mysql-r2dbc/src/main/java/org/apache/hertzbeat/collector/mysql/r2dbc/MysqlR2dbcQueryExecutor.java b/hertzbeat-collector/hertzbeat-collector-mysql-r2dbc/src/main/java/org/apache/hertzbeat/collector/mysql/r2dbc/MysqlR2dbcQueryExecutor.java index d8a5e9698e..41f5ed36a7 100644 --- a/hertzbeat-collector/hertzbeat-collector-mysql-r2dbc/src/main/java/org/apache/hertzbeat/collector/mysql/r2dbc/MysqlR2dbcQueryExecutor.java +++ b/hertzbeat-collector/hertzbeat-collector-mysql-r2dbc/src/main/java/org/apache/hertzbeat/collector/mysql/r2dbc/MysqlR2dbcQueryExecutor.java @@ -46,21 +46,23 @@ public class MysqlR2dbcQueryExecutor implements MysqlQueryExecutor { @Override public QueryResult execute(String sql, QueryOptions options) { String normalizedSql = sqlGuard.normalizeAndValidate(sql); - QueryResult firstAttempt = executeOnce(normalizedSql, options, SslMode.PREFERRED); - if (!firstAttempt.hasError() || !shouldRetryWithoutSsl(firstAttempt.getError())) { - return firstAttempt; - } - QueryResult fallbackAttempt = executeOnce(normalizedSql, options, SslMode.DISABLED); - if (!fallbackAttempt.hasError()) { - return fallbackAttempt; + // A single PREFERRED attempt negotiates TLS when the server supports it and + // falls back to plaintext only through the protocol-level handshake when the + // server advertises no SSL capability. It must NOT be followed by a second + // SslMode.DISABLED attempt on handshake *failures*: a handshake failure is + // ambiguous (a MitM can induce it to force a downgrade), and retrying with + // SSL disabled would then ship the monitoring credentials in the clear. + QueryResult result = executeOnce(normalizedSql, options, SslMode.PREFERRED); + if (!result.hasError()) { + return result; } - if (requiresSslCompatibleAuth(fallbackAttempt.getError())) { + if (requiresSslCompatibleAuth(result.getError())) { return QueryResult.builder() - .error(fallbackAttempt.getError() + .error(result.getError() + ". This route currently needs a TLS-compatible runtime or a mysql_native_password monitoring user.") .build(); } - return fallbackAttempt; + return result; } private QueryResult executeOnce(String sql, QueryOptions options, SslMode sslMode) { @@ -128,17 +130,6 @@ public class MysqlR2dbcQueryExecutor implements MysqlQueryExecutor { return false; } - private boolean shouldRetryWithoutSsl(String error) { - if (error == null) { - return false; - } - String normalized = error.toLowerCase(Locale.ROOT); - return normalized.contains("handshake_failure") - || normalized.contains("ssl/tls handshake") - || normalized.contains("closedchannelexception") - || normalized.contains("connection unexpectedly closed"); - } - private boolean requiresSslCompatibleAuth(String error) { if (error == null) { return false; --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
