feat(passthrough-auth): unknown-user dispatch path

Extends the cache-lookup/dispatch block in PPHR_verify_password to
cover the unknown-user case in addition to the empty-password row
case (spec §3.1, §3.5).

Eligibility split:

  empty_pw_case    : vars1.password != NULL && strlen == 0
                     gated by passthrough_auth_empty_password
  unknown_user_case: vars1.password == NULL (user not in mysql_users)
                     gated by passthrough_auth_unknown_users

For the unknown-user case the session's routing/schema defaults are
synthesized from globals before the cache lookup, since there's no
mysql_users row to drive PPHR_5passwordTrue:

  default_hostgroup       <- mysql-passthrough_default_hg
  default_schema (if NULL) <- mysql-passthrough_default_schema
                              (falls back to mysql-default_schema)
  schema_locked           <- false
  transaction_persistent  <- true
  session_fast_forward    <- NONE
  user_max_connections    <- 0 (unlimited; honors global cap)

These are re-derived each connect (spec §3.5), so changing
mysql-passthrough_default_hg affects future logins immediately
without requiring a cache flush.

On cache hit, account_details.default_hostgroup is also populated
to keep the post-verification path consistent. On cache miss the
existing TLS gate runs, then PPHR_passthrough_init drives the
backend probe through AUTHENTICATING_BACKEND_FOR_CLIENT.
docs/passthrough-auth-spec
Rene Cannao 1 month ago
parent 2ff27f39cb
commit 3b09af50bf

@ -2442,50 +2442,88 @@ bool MySQL_Protocol::PPHR_verify_password(MyProt_tmp_auth_vars& vars1, account_d
bool ret = false;
char reply[SHA_DIGEST_LENGTH + 1] = { 0 };
// Pass-through cache lookup (spec §8.2). When pass-through is enabled
// and the mysql_users row has an empty password (admin-provisioned
// opt-in), look up the in-memory cache. On hit, replace vars1.password
// with the learned cleartext so the existing verification path below
// runs against it as if mysql_users had stored a cleartext password.
// On miss, dispatch to PPHR_passthrough_init which drives the
// caching_sha2_password full-auth exchange and ultimately schedules a
// backend probe.
// Pass-through cache lookup and dispatch (spec §3.1, §8.2). Two
// eligibility cases share the same lookup-and-dispatch logic:
//
// Unknown-user fallback (no row in mysql_users) is wired in a later
// commit that synthesizes routing/schema defaults from globals; until
// then this only covers the empty-password row case.
if (mysql_thread___passthrough_auth_enabled
&& mysql_thread___passthrough_auth_empty_password
&& auth_plugin_id == AUTH_MYSQL_CACHING_SHA2_PASSWORD
&& (*myds)->sess->session_type == PROXYSQL_SESSION_MYSQL
&& GloMyPTAuthCache != NULL
&& vars1.user != NULL
&& vars1.password != NULL
&& strlen(vars1.password) == 0) {
std::string cleartext;
const uint32_t ttl_s =
mysql_thread___passthrough_auth_cache_ttl_s > 0
? static_cast<uint32_t>(mysql_thread___passthrough_auth_cache_ttl_s)
: 0;
if (GloMyPTAuthCache->lookup(std::string((const char*)vars1.user), cleartext, ttl_s)) {
free(vars1.password);
vars1.password = strdup(cleartext.c_str());
} else if (mysql_thread___passthrough_auth_require_tls && !(*myds)->encrypted) {
// Spec §7.1/§7.4: refuse to ask the client for cleartext over a
// non-TLS connection. Falls through to the normal verification
// path, which will reject the connection (empty password +
// non-empty client password = auth failure). No backend probe
// is dispatched and no AuthMoreData{0x04} is sent.
proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5,
"pass-through auth refused: client connection is not TLS and "
"mysql-passthrough_auth_require_tls=true (user='%s')\n",
vars1.user ? (const char*)vars1.user : "(null)");
} else {
// Cache miss → drive the caching_sha2_password full-auth
// exchange so the client emits its cleartext, which we will
// then probe against the backend.
PPHR_passthrough_init(vars1);
return ret; // not done yet; protocol state machine continues
// - empty_pw_case: mysql_users row exists with password=''
// (admin opt-in via passthrough_auth_empty_password)
// - unknown_user_case: user not in mysql_users at all
// (gated by passthrough_auth_unknown_users; routing
// and schema synthesized from globals each connect)
//
// On cache hit, replace vars1.password with the learned cleartext so
// the existing verification path below runs as if mysql_users had
// stored a cleartext password. On miss (and TLS gate satisfied),
// dispatch to PPHR_passthrough_init which drives the
// caching_sha2_password full-auth exchange and ultimately schedules a
// backend probe via AUTHENTICATING_BACKEND_FOR_CLIENT.
{
const bool empty_pw_case =
mysql_thread___passthrough_auth_empty_password
&& vars1.password != NULL
&& strlen(vars1.password) == 0;
const bool unknown_user_case =
mysql_thread___passthrough_auth_unknown_users
&& vars1.password == NULL;
if (mysql_thread___passthrough_auth_enabled
&& auth_plugin_id == AUTH_MYSQL_CACHING_SHA2_PASSWORD
&& (*myds)->sess->session_type == PROXYSQL_SESSION_MYSQL
&& GloMyPTAuthCache != NULL
&& vars1.user != NULL
&& (empty_pw_case || unknown_user_case)) {
// Unknown-user case: synthesize routing/schema defaults from
// globals (spec §3.5) directly onto the session, since there's
// no mysql_users row to drive PPHR_5passwordTrue.
if (unknown_user_case) {
(*myds)->sess->default_hostgroup = mysql_thread___passthrough_default_hg;
if ((*myds)->sess->default_schema == NULL) {
const char *ds =
(mysql_thread___passthrough_default_schema
&& mysql_thread___passthrough_default_schema[0] != '\0')
? mysql_thread___passthrough_default_schema
: mysql_thread___default_schema;
if (ds != NULL && ds[0] != '\0') {
(*myds)->sess->default_schema = strdup(ds);
}
}
(*myds)->sess->schema_locked = false;
(*myds)->sess->transaction_persistent = true;
(*myds)->sess->session_fast_forward = SESSION_FORWARD_TYPE_NONE;
(*myds)->sess->user_max_connections = 0;
}
std::string cleartext;
const uint32_t ttl_s =
mysql_thread___passthrough_auth_cache_ttl_s > 0
? static_cast<uint32_t>(mysql_thread___passthrough_auth_cache_ttl_s)
: 0;
if (GloMyPTAuthCache->lookup(
std::string((const char*)vars1.user), cleartext, ttl_s)) {
if (vars1.password) { free(vars1.password); }
vars1.password = strdup(cleartext.c_str());
// For unknown-user case, also need to populate
// account_details so the post-verification path in
// process_pkt_handshake_response sees consistent values.
if (unknown_user_case) {
account_details.default_hostgroup =
mysql_thread___passthrough_default_hg;
}
} else if (mysql_thread___passthrough_auth_require_tls && !(*myds)->encrypted) {
// Spec §7.1/§7.4: refuse to ask the client for cleartext
// over a non-TLS connection. Fall through to the normal
// rejection path.
proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5,
"pass-through auth refused: client connection is not TLS and "
"mysql-passthrough_auth_require_tls=true (user='%s')\n",
vars1.user ? (const char*)vars1.user : "(null)");
} else {
// Cache miss → drive the caching_sha2_password full-auth
// exchange so the client emits its cleartext, which we will
// then probe against the backend.
PPHR_passthrough_init(vars1);
return ret; // not done yet; protocol state machine continues
}
}
}

Loading…
Cancel
Save