From f0a8655be3dfd629d61d5eff70028b13ecc9c1dd Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Fri, 17 Apr 2026 18:56:21 +0000 Subject: [PATCH] refactor(mysqlx): replace MysqlxCredentials with MysqlxResolvedIdentity The session's identity-lookup callback now returns the full MysqlxResolvedIdentity struct from the config store rather than a stripped-down MysqlxCredentials. Password-hash derivation moves into the session via a private derive_stored_hash helper (handles both "*HEX40" and cleartext forms). No behavior change; the existing credential-lookup guard semantics are preserved so sessions without a lookup still follow the open-proxy path exercised by the robustness tests. mysqlx_robustness_unit-t passes unchanged (36/36). --- plugins/mysqlx/include/mysqlx_session.h | 17 +++--- plugins/mysqlx/src/mysqlx_session.cpp | 58 +++++++++++++++---- plugins/mysqlx/src/mysqlx_thread.cpp | 24 ++------ test/tap/tests/unit/mysqlx_session_unit-t.cpp | 24 +++++--- 4 files changed, 74 insertions(+), 49 deletions(-) diff --git a/plugins/mysqlx/include/mysqlx_session.h b/plugins/mysqlx/include/mysqlx_session.h index 720b9c218..7e560d88e 100644 --- a/plugins/mysqlx/include/mysqlx_session.h +++ b/plugins/mysqlx/include/mysqlx_session.h @@ -3,22 +3,18 @@ #include "mysqlx_data_stream.h" #include "mysqlx_connection.h" +#include "mysqlx_config_store.h" #include #include #include #include +#include class Mysqlx_Thread; -struct MysqlxCredentials { - std::string password_hash; - bool x_enabled; - std::string allowed_auth; - std::string backend_password; -}; - -typedef std::function MysqlxCredentialLookup; +using MysqlxIdentityLookup = + std::function(const std::string& username)>; enum MysqlxResponseState { RESP_IDLE = 0, @@ -81,7 +77,7 @@ public: MysqlxDataStream& server_ds() { return server_ds_; } MysqlxConnection*& backend_conn() { return backend_conn_; } - void set_credential_lookup(MysqlxCredentialLookup lookup) { credential_lookup_ = lookup; } + void set_identity_lookup(MysqlxIdentityLookup lookup) { identity_lookup_ = std::move(lookup); } void set_tls_mode(MysqlxTlsMode mode) { tls_mode_ = mode; } MysqlxTlsMode get_tls_mode() const { return tls_mode_; } uint64_t get_start_time() const { return start_time_; } @@ -135,7 +131,8 @@ private: int target_hostgroup_; std::string target_address_; int target_port_; - MysqlxCredentialLookup credential_lookup_; + MysqlxIdentityLookup identity_lookup_; + std::optional identity_; uint64_t start_time_; uint64_t last_active_time_; MysqlxResponseState response_state_; diff --git a/plugins/mysqlx/src/mysqlx_session.cpp b/plugins/mysqlx/src/mysqlx_session.cpp index 52d02cd49..1636d6795 100644 --- a/plugins/mysqlx/src/mysqlx_session.cpp +++ b/plugins/mysqlx/src/mysqlx_session.cpp @@ -24,6 +24,25 @@ uint64_t monotonic_time_ms() { return static_cast(ts.tv_sec) * 1000 + static_cast(ts.tv_nsec) / 1000000; } +// Derive the 20-byte mysql_native_password hash from the stored form. +// Accepts either the "*HEX40" mysql_native_password format or a cleartext +// password. Returns false on any failure; in that case `out` is cleared. +bool derive_stored_hash(const std::string& stored, std::vector& out) { + out.clear(); + if (stored.empty()) return false; + if (stored[0] == '*') { + if (!mysqlx_hex_decode(stored.substr(1), out) || out.size() != 20) { + out.clear(); + return false; + } + return true; + } + auto hash = mysqlx_mysql41_hash(stored); + if (hash.size() != 20) return false; + out.assign(hash.begin(), hash.end()); + return true; +} + } MysqlxSession::MysqlxSession() @@ -60,6 +79,7 @@ void MysqlxSession::init(int fd, Mysqlx_Thread* thread_ptr) { target_hostgroup_ = 0; target_address_.clear(); target_port_ = 0; + identity_.reset(); start_time_ = monotonic_time_ms(); last_active_time_ = start_time_; } @@ -76,6 +96,7 @@ void MysqlxSession::reset() { target_hostgroup_ = 0; target_address_.clear(); target_port_ = 0; + identity_.reset(); } int MysqlxSession::handler() { @@ -253,17 +274,24 @@ void MysqlxSession::handle_auth_plain(const std::string& auth_data) { username_ = auth_data.substr(1, second_nul - 1); std::string password = auth_data.substr(second_nul + 1); - if (credential_lookup_) { - MysqlxCredentials creds = credential_lookup_(username_); - if (!creds.x_enabled || creds.password_hash.empty()) { + if (identity_lookup_) { + identity_ = identity_lookup_(username_); + if (!identity_ || !identity_->x_enabled) { send_error(1045, "Access denied for user"); healthy = false; return; } + + std::vector stored_hash; + if (!derive_stored_hash(identity_->password, stored_hash)) { + send_error(1045, "Access denied for user"); + healthy = false; + return; + } + std::vector input_hash_vec = mysqlx_mysql41_hash(password); if (input_hash_vec.size() != 20 || - CRYPTO_memcmp(input_hash_vec.data(), creds.password_hash.data(), - std::min(input_hash_vec.size(), creds.password_hash.size())) != 0) { + CRYPTO_memcmp(input_hash_vec.data(), stored_hash.data(), 20) != 0) { send_error(1045, "Access denied for user"); healthy = false; return; @@ -356,14 +384,21 @@ void MysqlxSession::handler_auth_challenge_response() { return; } - if (credential_lookup_) { - MysqlxCredentials creds = credential_lookup_(username_); - if (!creds.x_enabled || creds.password_hash.empty()) { + if (identity_lookup_) { + identity_ = identity_lookup_(username_); + if (!identity_ || !identity_->x_enabled) { + send_error(1045, "Access denied for user"); + healthy = false; + return; + } + + std::vector stored_hash; + if (!derive_stored_hash(identity_->password, stored_hash)) { send_error(1045, "Access denied for user"); healthy = false; return; } - std::vector stored_hash(creds.password_hash.begin(), creds.password_hash.end()); + if (!mysqlx_mysql41_verify_hash(auth_challenge_, scramble, stored_hash)) { send_error(1045, "Access denied for user"); healthy = false; @@ -715,9 +750,8 @@ void MysqlxSession::handler_connecting_server() { } } - if (credential_lookup_) { - MysqlxCredentials creds = credential_lookup_(username_); - backend_conn_->set_backend_password(creds.backend_password.c_str()); + if (identity_) { + backend_conn_->set_backend_password(identity_->backend_password.c_str()); } } diff --git a/plugins/mysqlx/src/mysqlx_thread.cpp b/plugins/mysqlx/src/mysqlx_thread.cpp index 6b92c0be8..e501a9f91 100644 --- a/plugins/mysqlx/src/mysqlx_thread.cpp +++ b/plugins/mysqlx/src/mysqlx_thread.cpp @@ -222,26 +222,12 @@ void Mysqlx_Thread::accept_new_connection(int listener_fd) { sess->to_process = true; const MysqlxConfigStore* store = config_store_; - sess->set_credential_lookup([store](const std::string& username) -> MysqlxCredentials { - MysqlxCredentials creds {}; - if (!store) return creds; - auto identity = store->resolve_identity(username); - if (!identity) return creds; - creds.x_enabled = identity->x_enabled; - creds.allowed_auth = identity->allowed_auth_methods; - creds.backend_password = identity->backend_password; - const std::string& pwd = identity->password; - if (!pwd.empty() && pwd[0] == '*') { - std::vector hash_bytes; - if (mysqlx_hex_decode(pwd.substr(1), hash_bytes) && hash_bytes.size() == 20) { - creds.password_hash.assign(hash_bytes.begin(), hash_bytes.end()); - } - } else if (!pwd.empty()) { - auto hash = mysqlx_mysql41_hash(pwd); - creds.password_hash.assign(hash.begin(), hash.end()); + sess->set_identity_lookup( + [store](const std::string& username) -> std::optional { + if (!store) return std::nullopt; + return store->resolve_identity(username); } - return creds; - }); + ); std::lock_guard lock(sessions_mutex_); sessions_.push_back(sess); diff --git a/test/tap/tests/unit/mysqlx_session_unit-t.cpp b/test/tap/tests/unit/mysqlx_session_unit-t.cpp index 5a9455de9..9cf48e263 100644 --- a/test/tap/tests/unit/mysqlx_session_unit-t.cpp +++ b/test/tap/tests/unit/mysqlx_session_unit-t.cpp @@ -255,12 +255,16 @@ static void test_mysql41_auth_with_credentials() { MysqlxSession sess; sess.init(fds[0], nullptr); - sess.set_credential_lookup([](const std::string& user) -> MysqlxCredentials { + sess.set_identity_lookup([](const std::string& user) -> std::optional { if (user == "testuser") { - std::vector hash = mysqlx_mysql41_hash("testpass"); - return { std::string(hash.begin(), hash.end()), true, "MYSQL41" }; + MysqlxResolvedIdentity id{}; + id.username = user; + id.x_enabled = true; + id.password = "testpass"; + id.allowed_auth_methods = "MYSQL41"; + return id; } - return { "", false, "" }; + return std::nullopt; }); sess.to_process = true; @@ -327,12 +331,16 @@ static void test_mysql41_auth_wrong_password() { MysqlxSession sess; sess.init(fds[0], nullptr); - sess.set_credential_lookup([](const std::string& user) -> MysqlxCredentials { + sess.set_identity_lookup([](const std::string& user) -> std::optional { if (user == "testuser") { - std::vector hash = mysqlx_mysql41_hash("testpass"); - return { std::string(hash.begin(), hash.end()), true, "MYSQL41" }; + MysqlxResolvedIdentity id{}; + id.username = user; + id.x_enabled = true; + id.password = "testpass"; + id.allowed_auth_methods = "MYSQL41"; + return id; } - return { "", false, "" }; + return std::nullopt; }); sess.to_process = true;