From 479cd7456fb28d696896fca089271b7d573530b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Canna=C3=B2?= Date: Sun, 14 May 2023 21:07:45 +0000 Subject: [PATCH 1/2] POC for pass-through authentication With caching_sha2_password it is possible to do pass-through authentication: * client connects to proxysql * proxysql retrieves the credentials and tries to connect to backend * if connection to backend succeeds, complete the client authentication This is still a POC --- include/MySQL_Protocol.h | 1 + include/MySQL_Session.h | 1 + include/proxysql_structs.h | 1 + lib/MySQL_Protocol.cpp | 80 ++++++++++++++++++++++++++++++++++++-- lib/MySQL_Session.cpp | 50 +++++++++++++++++++++--- 5 files changed, 124 insertions(+), 9 deletions(-) diff --git a/include/MySQL_Protocol.h b/include/MySQL_Protocol.h index 6a1d634703..d42ce7e472 100644 --- a/include/MySQL_Protocol.h +++ b/include/MySQL_Protocol.h @@ -195,6 +195,7 @@ class MySQL_Protocol { void PPHR_5passwordFalse_auth2(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1, char * reply, MyProt_tmp_auth_attrs& attr1 , void *& sha1_pass); void PPHR_6auth2(bool& ret, MyProt_tmp_auth_vars& vars1); void PPHR_sha2full(bool& ret, MyProt_tmp_auth_vars& vars1, enum proxysql_auth_plugins passformat); + void PPHR_sha2full_unknown_user(bool& ret, MyProt_tmp_auth_vars& vars1, enum proxysql_auth_plugins passformat); void PPHR_7auth1(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1, char * reply, MyProt_tmp_auth_attrs& attr1 , void *& sha1_pass); void PPHR_7auth2(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1, char * reply, MyProt_tmp_auth_attrs& attr1 , void *& sha1_pass); void PPHR_SetConnAttrs(MyProt_tmp_auth_vars& vars1, MyProt_tmp_auth_attrs& attr1); diff --git a/include/MySQL_Session.h b/include/MySQL_Session.h index 424d7d6943..bf84e5482d 100644 --- a/include/MySQL_Session.h +++ b/include/MySQL_Session.h @@ -310,6 +310,7 @@ class MySQL_Session // this variable is relevant only if status == SETTING_VARIABLE enum mysql_variable_name changing_variable_idx; + MySQL_Session(); ~MySQL_Session(); diff --git a/include/proxysql_structs.h b/include/proxysql_structs.h index 439ae11612..1fe0d9a2f9 100644 --- a/include/proxysql_structs.h +++ b/include/proxysql_structs.h @@ -226,6 +226,7 @@ enum mysql_variable_name { enum session_status { CONNECTING_CLIENT, + CONNECTING_CLIENT_RESUME, CONNECTING_SERVER, LDAP_AUTH_CLIENT, PINGING_SERVER, diff --git a/lib/MySQL_Protocol.cpp b/lib/MySQL_Protocol.cpp index 76463360d4..3a80b13171 100644 --- a/lib/MySQL_Protocol.cpp +++ b/lib/MySQL_Protocol.cpp @@ -446,6 +446,15 @@ bool MySQL_Protocol::generate_pkt_ERR(bool send, void **ptr, unsigned int *len, break; } default: + if (sess->status == CONNECTING_SERVER) { + if (sess->previous_status.empty() == false) { + if (sess->previous_status.top() == CONNECTING_CLIENT_RESUME) { + // this is an exception. We are trying to shortcut the + // client authentication against the server + break; + } + } + } // LCOV_EXCL_START assert(0); // LCOV_EXCL_STOP @@ -2226,6 +2235,56 @@ void MySQL_Protocol::PPHR_sha2full( } } +void MySQL_Protocol::PPHR_sha2full_unknown_user( + bool& ret, + MyProt_tmp_auth_vars& vars1, + enum proxysql_auth_plugins passformat + ) { + if ((*myds)->switching_auth_stage == 0) { + const unsigned char perform_full_authentication = '\4'; + generate_one_byte_pkt(perform_full_authentication); + (*myds)->switching_auth_type = auth_plugin_id; + (*myds)->switching_auth_stage = 4; + (*myds)->auth_in_progress = 1; + } else if ((*myds)->switching_auth_stage == 5) { + if (passformat == AUTH_MYSQL_CACHING_SHA2_PASSWORD) { + //assert(strlen(vars1.password) == 70); + (*myds)->switching_auth_stage = 6; + (*myds)->auth_in_progress = 1; + assert(vars1.password == NULL); + userinfo->password=strdup((const char *)vars1.pass); + if (vars1.db != NULL) { + userinfo->schemaname=strdup((const char *)vars1.db); + } else { + userinfo->schemaname=strdup((const char *)mysql_thread___default_schema); + } + sess->default_hostgroup = 0; + sess->current_hostgroup = sess->default_hostgroup; + sess->mybe = sess->find_or_create_backend(sess->current_hostgroup); + // TODO: add timeout + sess->mybe->server_myds->connect_retries_on_failure = 0; + sess->previous_status.push(CONNECTING_CLIENT_RESUME); + sess->set_status(CONNECTING_SERVER); + } else { + assert(0); + } + } else if ((*myds)->switching_auth_stage == 6) { + if (passformat == AUTH_MYSQL_CACHING_SHA2_PASSWORD) { + if (sess->mybe) { + if (sess->mybe->server_myds) { + if (sess->mybe->server_myds->myconn) { + GloMyAuth->set_clear_text_password((char *)vars1.user, USERNAME_FRONTEND, sess->mybe->server_myds->myconn->userinfo->password); + ret = true; + } + } + } + } + (*myds)->auth_in_progress = 0; + } else { + assert(0); + } +} + void MySQL_Protocol::PPHR_SetConnAttrs(MyProt_tmp_auth_vars& vars1, MyProt_tmp_auth_attrs& attr1) { MySQL_Connection *myconn = NULL; myconn=sess->client_myds->myconn; @@ -2442,6 +2501,12 @@ bool MySQL_Protocol::process_pkt_handshake_response(unsigned char *pkt, unsigned // try LDAP if (auth_plugin_id == AUTH_MYSQL_CLEAR_PASSWORD) { PPHR_5passwordFalse_auth2(pkt, len, ret, vars1, reply, attr1, sha1_pass); + } else if ( + auth_plugin_id == AUTH_MYSQL_CACHING_SHA2_PASSWORD + && + ((*myds)->sess->session_type == PROXYSQL_SESSION_MYSQL) + ) { // caching_sha2_password + PPHR_sha2full_unknown_user(ret, vars1, AUTH_MYSQL_CACHING_SHA2_PASSWORD); } } } else { @@ -2461,7 +2526,13 @@ bool MySQL_Protocol::process_pkt_handshake_response(unsigned char *pkt, unsigned if ( auth_plugin_id == AUTH_MYSQL_CACHING_SHA2_PASSWORD && - strlen(vars1.password) > 60 + vars1.password == NULL + ) { + assert(0); + } else if ( + auth_plugin_id == AUTH_MYSQL_CACHING_SHA2_PASSWORD + && + strlen(vars1.password) == 70 && strncasecmp(vars1.password,"$A$0",4)==0 ) { @@ -2541,13 +2612,16 @@ bool MySQL_Protocol::process_pkt_handshake_response(unsigned char *pkt, unsigned if (!userinfo->username) // if set already, ignore userinfo->username=strdup((const char *)vars1.user); - userinfo->password=strdup((const char *)vars1.password); + if (userinfo->password == NULL) + userinfo->password=strdup((const char *)vars1.password); if (vars1.db) userinfo->set_schemaname(vars1.db,strlen(vars1.db)); } else { // we always duplicate username and password, or crashes happen if (!userinfo->username) // if set already, ignore userinfo->username=strdup((const char *)vars1.user); - if (vars1.pass_len) userinfo->password=strdup((const char *)""); + if (vars1.pass_len) + if (userinfo->password == NULL) + userinfo->password=strdup((const char *)""); } userinfo->set(NULL,NULL,NULL,NULL); // just to call compute_hash() diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 2bc4865753..37a0b9cd2d 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -611,6 +611,7 @@ MySQL_Session::MySQL_Session() { last_HG_affected_rows = -1; // #1421 : advanced support for LAST_INSERT_ID() proxysql_node_address = NULL; use_ldap_auth = false; + } void MySQL_Session::init() { @@ -664,6 +665,7 @@ void MySQL_Session::reset() { client_myds->myconn->reset(); } } + } MySQL_Session::~MySQL_Session() { @@ -2983,9 +2985,18 @@ bool MySQL_Session::handler_again___status_CONNECTING_SERVER(int *_rc) { // see bug #979 RequestEnd(myds); } - while (previous_status.size()) { - st=previous_status.top(); - previous_status.pop(); + if (previous_status.empty() == false) { + if (previous_status.top() == CONNECTING_CLIENT_RESUME) { + myds->destroy_MySQL_Connection_From_Pool( myerr ? true : false ); + st=previous_status.top(); + previous_status.pop(); + NEXT_IMMEDIATE_NEW(st); + } else { + while (previous_status.size()) { + st=previous_status.top(); + previous_status.pop(); + } + } } if (mirror) { PROXY_TRACE(); @@ -4749,6 +4760,27 @@ int MySQL_Session::handler() { //fprintf(stderr,"CONNECTING_CLIENT\n"); // FIXME: to implement break; + case CONNECTING_CLIENT_RESUME: + if (mybe != NULL) { + if (mybe->server_myds != NULL) { + if (mybe->server_myds->myconn != NULL) { + proxy_warning("Resuming client connection\n"); + set_status(CONNECTING_CLIENT); + uint8_t _pid = client_myds->pkt_sid; _pid++; + GloMyLogger->log_audit_entry(PROXYSQL_MYSQL_AUTH_OK, this, NULL); + client_myds->DSS = STATE_CLIENT_HANDSHAKE; + client_myds->myprot.generate_pkt_OK(true, NULL, NULL, _pid, 0, 0, 2, 0, NULL); + status=WAITING_CLIENT_DATA; + client_myds->DSS=STATE_CLIENT_AUTH_OK; + mybe->server_myds->return_MySQL_Connection_To_Pool(); + break; + } + } + } + // something was wrong , we destroy the session + handler_ret = -1; + return handler_ret; + break; case PINGING_SERVER: { int rc=handler_again___status_PINGING_SERVER(); @@ -6971,7 +7003,13 @@ void MySQL_Session::handler___client_DSS_QUERY_SENT___server_DSS_NOT_INITIALIZED } } } - if (session_fast_forward == false && qpo->create_new_conn == false) { + bool resume_client_authentication = false; + if (previous_status.empty() == false) { + if (previous_status.top() == CONNECTING_CLIENT_RESUME) { + resume_client_authentication = true; + } + } + if (session_fast_forward == false && qpo->create_new_conn == false && resume_client_authentication == false) { if (qpo->min_gtid) { gtid_uuid = qpo->min_gtid; with_gtid = true; @@ -7031,9 +7069,9 @@ void MySQL_Session::handler___client_DSS_QUERY_SENT___server_DSS_NOT_INITIALIZED if (mc==NULL) { if (trxid) { - mc=MyHGM->get_MyConn_from_pool(mybe->hostgroup_id, this, (session_fast_forward || qpo->create_new_conn), uuid, trxid, -1); + mc=MyHGM->get_MyConn_from_pool(mybe->hostgroup_id, this, (session_fast_forward || qpo->create_new_conn || resume_client_authentication), uuid, trxid, -1); } else { - mc=MyHGM->get_MyConn_from_pool(mybe->hostgroup_id, this, (session_fast_forward || qpo->create_new_conn), NULL, 0, (int)qpo->max_lag_ms); + mc=MyHGM->get_MyConn_from_pool(mybe->hostgroup_id, this, (session_fast_forward || qpo->create_new_conn || resume_client_authentication), NULL, 0, (int)qpo->max_lag_ms); } #ifdef STRESSTEST_POOL if (mc && (loops < NUM_SLOW_LOOPS - 1)) { From 32fdaf9975caa5c9d80a90c18da140576511002f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Canna=C3=B2?= Date: Sun, 14 May 2023 21:11:17 +0000 Subject: [PATCH 2/2] Removing commented code used for POC --- lib/MySQL_Protocol.cpp | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/lib/MySQL_Protocol.cpp b/lib/MySQL_Protocol.cpp index 3a80b13171..69151ccf75 100644 --- a/lib/MySQL_Protocol.cpp +++ b/lib/MySQL_Protocol.cpp @@ -2397,41 +2397,6 @@ bool MySQL_Protocol::process_pkt_handshake_response(unsigned char *pkt, unsigned assert(0); break; } -/* - if (auth_plugin_id == AUTH_UNKNOWN_PLUGIN) { // unknown plugin -*/ -/* - ret = false; - proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . goto __exit_process_pkt_handshake_response . Unknown auth plugin\n", (*myds), (*myds)->sess, vars1.user); - goto __exit_process_pkt_handshake_response; - } else if (auth_plugin_id == AUTH_UNKNOWN_PLUGIN) { -*/ -/* - } else if (auth_plugin_id == AUTH_MYSQL_NATIVE_PASSWORD) { - } else if (auth_plugin_id == AUTH_MYSQL_CLEAR_PASSWORD) { - } else if (auth_plugin_id == AUTH_MYSQL_CACHING_SHA2_PASSWORD) { // caching_sha2_password -*/ -/* - unsigned char a[SHA256_DIGEST_LENGTH]; - unsigned char b[SHA256_DIGEST_LENGTH]; - unsigned char c[SHA256_DIGEST_LENGTH+20]; - unsigned char d[SHA256_DIGEST_LENGTH]; - unsigned char e[SHA256_DIGEST_LENGTH]; - SHA256((const unsigned char *)"admin", strlen("admin"), a); - SHA256(a, SHA256_DIGEST_LENGTH, b); - memcpy(c,b,SHA256_DIGEST_LENGTH); - memcpy(c+SHA256_DIGEST_LENGTH, (*myds)->myconn->scramble_buff, 20); - SHA256(c, SHA256_DIGEST_LENGTH+20, d); - for (int i=0; i