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).
feature/mysqlx-route-identity
Rene Cannao 1 month ago
parent ca6ddb860c
commit f0a8655be3

@ -3,22 +3,18 @@
#include "mysqlx_data_stream.h"
#include "mysqlx_connection.h"
#include "mysqlx_config_store.h"
#include <cstdint>
#include <string>
#include <vector>
#include <functional>
#include <optional>
class Mysqlx_Thread;
struct MysqlxCredentials {
std::string password_hash;
bool x_enabled;
std::string allowed_auth;
std::string backend_password;
};
typedef std::function<MysqlxCredentials(const std::string& username)> MysqlxCredentialLookup;
using MysqlxIdentityLookup =
std::function<std::optional<MysqlxResolvedIdentity>(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<MysqlxResolvedIdentity> identity_;
uint64_t start_time_;
uint64_t last_active_time_;
MysqlxResponseState response_state_;

@ -24,6 +24,25 @@ uint64_t monotonic_time_ms() {
return static_cast<uint64_t>(ts.tv_sec) * 1000 + static_cast<uint64_t>(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<uint8_t>& 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<uint8_t> stored_hash;
if (!derive_stored_hash(identity_->password, stored_hash)) {
send_error(1045, "Access denied for user");
healthy = false;
return;
}
std::vector<uint8_t> 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<uint8_t> stored_hash;
if (!derive_stored_hash(identity_->password, stored_hash)) {
send_error(1045, "Access denied for user");
healthy = false;
return;
}
std::vector<uint8_t> 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());
}
}

@ -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<uint8_t> 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<MysqlxResolvedIdentity> {
if (!store) return std::nullopt;
return store->resolve_identity(username);
}
return creds;
});
);
std::lock_guard<std::mutex> lock(sessions_mutex_);
sessions_.push_back(sess);

@ -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<MysqlxResolvedIdentity> {
if (user == "testuser") {
std::vector<uint8_t> 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<MysqlxResolvedIdentity> {
if (user == "testuser") {
std::vector<uint8_t> 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;

Loading…
Cancel
Save