Summary
With mysql-default_authentication_plugin=caching_sha2_password, ProxySQL sends the
perform full authentication marker but implements neither of the two ways the MySQL
protocol allows a client to answer it:
- No RSA public-key exchange. When a client on a plaintext connection sends the
request_public_key packet (0x02), ProxySQL replies with something that is not
AuthMoreData, so the client aborts. MySQL server answers this request
(caching_sha2_password_auto_generate_rsa_keys=ON by default), which is why the same clients
work against MySQL without TLS.
- Cleartext-over-TLS is rejected on the Admin interface. ProxySQL does force
CLIENT_SSL
on for this plugin, so TLS is available and negotiates fine — but when the client then sends
the cleartext password that full auth requires, the Admin module answers Access denied for
credentials whose stored form is a caching_sha2_password hash, and for the
mysql-monitor_* credential.
Net effect: for the affected credentials there is no working path at all — plaintext fails
with perform full authentication, TLS fails with Access denied.
Client-side error text (go-sql-driver/mysql v1.10.0, but any client hits the same wire
behaviour):
unexpected resp from server for caching_sha2_password, perform full authentication
Environment
- ProxySQL 2.7.0 (
proxysql/proxysql:2.7.0) and 3.0.10 (proxysql/proxysql:3.0.10,
admin-version = 3.0.10-426-gf5f1e14) — every result below reproduces identically on both,
including the CLIENT_SSL/have_ssl behaviour and all six rows of the Finding 3 matrix
mysql-have_ssl=false, matching how ProxySQL ships in the Percona XtraDB Cluster Operator
- Client:
github.com/go-sql-driver/mysql v1.10.0; cross-checked with the MariaDB mysql CLI in
the image
Finding 1 — Admin interface: a mysql_users row silently breaks a valid Admin credential
This one is fully deterministic. monitor is a valid admin-admin_credentials entry stored in
cleartext; adding a mysql_users row of the same name holding a caching_sha2 hash flips the
Admin login onto the full-auth path, which can then never complete:
SET mysql-default_authentication_plugin='caching_sha2_password';
SET mysql-have_ssl='false';
LOAD MYSQL VARIABLES TO RUNTIME;
SET admin-admin_credentials='admin:admin;monitor:monitorpass';
LOAD ADMIN VARIABLES TO RUNTIME;
INSERT INTO mysql_users(username,password,default_hostgroup,frontend,backend)
VALUES('monitor', CACHING_SHA2_PASSWORD('monitorpass'),0,1,1);
LOAD MYSQL USERS TO RUNTIME;
Connecting to :6032 as monitor/monitorpass:
| connection |
result |
| plaintext |
unexpected resp from server for caching_sha2_password, perform full authentication |
tls=skip-verify |
ERROR 1045 (28000): ProxySQL Error: Access denied for user 'monitor'@'127.0.0.1' |
Remove the mysql_users row and both succeed. Switch the plugin to mysql_native_password and
both succeed with the row in place. Note a mysql_users credential is never valid for the Admin
interface on its own (it returns plain Access denied) — yet its mere presence changes the Admin
handshake for a same-named Admin credential.
Finding 2 — the mysql-monitor_* credential stops working on the Admin interface
mysql-monitor_username/mysql-monitor_password authenticates on :6032 under
mysql_native_password and is rejected under caching_sha2_password, with the password stored
in cleartext exactly as documented:
| default plugin |
monitor → :6032, plaintext |
mysql_native_password |
OK |
caching_sha2_password |
Access denied |
This is what #5363 reports (k8s liveness/readiness probes failing with
Access denied for user 'monitor'@'127.0.0.1', open since February with no reply) and what the
follow-up comment on #4845 asks about. #4845 has the maintainer statement that
mysql-monitor_password must be cleartext — it is cleartext here, and it still fails.
Finding 3 — full matrix, Admin interface :6032
All rows with caching_sha2_password and mysql-have_ssl=false:
| credential |
plaintext |
TLS |
in admin-admin_credentials, no same-named mysql_users row |
OK |
OK |
in admin-admin_credentials, shadowed by a hashed mysql_users row |
full-auth error |
Access denied |
mysql_users only, caching_sha2 hash |
full-auth error |
Access denied |
mysql_users only, cleartext password |
Access denied |
Access denied |
dedicated name in admin-stats_credentials |
OK |
OK |
(mysql_native_password) shadowed Admin credential |
OK |
OK |
The frontend (:6033) also produces the full-auth error for these credentials, but in our
testing the outcome there varied between otherwise-identical instances, so we're only asserting
the Admin results.
Reproducer: a self-contained script that spins up a fresh container per assertion (ProxySQL
auth state turned out to be order-dependent, so shared instances give inconsistent results) and
asserts every row above — 13 checks, all passing on both 2.7.0 and 3.0.10:
(attach or link the script)
Root cause in source
CLIENT_SSL is forced on whenever the default plugin is caching_sha2_password, independent of
have_ssl — lib/MySQL_Protocol.cpp:1036-1043 at tag v2.7.0, unchanged at
lib/MySQL_Protocol.cpp:1083-1090 at tag v3.0.10 and on the v3.0 branch:
if (mysql_thread___have_ssl==true || mysql_thread___default_authentication_plugin_int==2) {
// we enable SSL for client connections for either of these 2 conditions:
// - have_ssl is enabled
// - default_authentication_plugin=caching_sha2_password
mysql_thread___server_capabilities |= CLIENT_SSL;
} else {
mysql_thread___server_capabilities &= ~CLIENT_SSL;
}
==2 is caching_sha2_password (lib/MySQL_Thread.cpp:1854-1863 at v2.7.0,
lib/MySQL_Thread.cpp:2366 at v3.0.10). The Admin interface emits its greeting through this
same function (lib/ProxySQL_Admin.cpp:5590 at v2.7.0, lib/ProxySQL_Admin.cpp:2235 at
v3.0.10), which is why :6032 behaves like :6033 here. There is no code path that serves a server RSA public key or decrypts an
RSA-encrypted password.
To be explicit: forcing CLIENT_SSL is the right call and we are not asking for it to be
reverted — it is what makes a completion path possible at all. The gap is that neither
completion path actually works.
How MySQL differs (8.0.46, verified with the same client)
| MySQL 8.0.46 |
greeting |
caching_sha2 user, plaintext connection |
| TLS on (default) |
CLIENT_SSL=true |
OK — RSA full auth, cache cleared beforehand |
TLS off (--tls-version=, have_ssl=DISABLED) |
CLIENT_SSL=false |
OK — no TLS involved at any point |
MySQL also implements the cache the plugin is named for, so full auth only happens on first
connect, after FLUSH PRIVILEGES, or after a password change.
Worth pre-empting one thing: the ERROR 2061 ... Authentication requires secure connection
people quote from the mysql CLI is not server-side TLS enforcement — it is the client
declining to fetch a public key it wasn't told to trust:
mysql --ssl-mode=DISABLED -> ERROR 2061 ... requires secure connection
mysql --ssl-mode=DISABLED --get-server-public-key -> connected
Asks, in order of preference
- Serve the RSA public key and accept an RSA-encrypted password, matching MySQL's
--get-server-public-key / --server-public-key-path flow. This fixes non-TLS clients
generally.
- Fix the Admin module's full-auth completion so a cleartext password received over TLS is
verified against the stored credential — including admin-*_credentials and
mysql-monitor_*. This alone would make TLS a genuine workaround, which it currently isn't.
- Stop a
mysql_users row from shadowing a same-named Admin credential during the Admin
handshake (Finding 1).
- Failing all of the above: document the limitation — which credential classes can
authenticate to the Admin interface under caching_sha2_password, that hashed mysql_users
entries cannot, and that TLS does not help — and return an error naming the real cause instead
of an unexpected packet.
Impact
caching_sha2_password is the MySQL 8.0+ default and mysql_native_password was removed in
MySQL 9.0, so users are increasingly provisioned with caching_sha2 credentials. Today the only
reliable workarounds are to keep ProxySQL's frontend on the deprecated mysql_native_password,
or to give each admin-side client a dedicated admin-*_credentials name that appears nowhere in
mysql_users. Monitoring is hit first — health probes (#5363) and metrics exporters connecting
to :6032 — but any client of the Admin interface is affected.
Related
Summary
With
mysql-default_authentication_plugin=caching_sha2_password, ProxySQL sends theperform full authenticationmarker but implements neither of the two ways the MySQLprotocol allows a client to answer it:
request_public_keypacket (0x02), ProxySQL replies with something that is notAuthMoreData, so the client aborts. MySQL server answers this request(
caching_sha2_password_auto_generate_rsa_keys=ONby default), which is why the same clientswork against MySQL without TLS.
CLIENT_SSLon for this plugin, so TLS is available and negotiates fine — but when the client then sends
the cleartext password that full auth requires, the Admin module answers
Access deniedforcredentials whose stored form is a
caching_sha2_passwordhash, and for themysql-monitor_*credential.Net effect: for the affected credentials there is no working path at all — plaintext fails
with
perform full authentication, TLS fails withAccess denied.Client-side error text (go-sql-driver/mysql v1.10.0, but any client hits the same wire
behaviour):
Environment
proxysql/proxysql:2.7.0) and 3.0.10 (proxysql/proxysql:3.0.10,admin-version=3.0.10-426-gf5f1e14) — every result below reproduces identically on both,including the
CLIENT_SSL/have_sslbehaviour and all six rows of the Finding 3 matrixmysql-have_ssl=false, matching how ProxySQL ships in the Percona XtraDB Cluster Operatorgithub.com/go-sql-driver/mysqlv1.10.0; cross-checked with the MariaDBmysqlCLI inthe image
Finding 1 — Admin interface: a
mysql_usersrow silently breaks a valid Admin credentialThis one is fully deterministic.
monitoris a validadmin-admin_credentialsentry stored incleartext; adding a
mysql_usersrow of the same name holding a caching_sha2 hash flips theAdmin login onto the full-auth path, which can then never complete:
Connecting to
:6032asmonitor/monitorpass:unexpected resp from server for caching_sha2_password, perform full authenticationtls=skip-verifyERROR 1045 (28000): ProxySQL Error: Access denied for user 'monitor'@'127.0.0.1'Remove the
mysql_usersrow and both succeed. Switch the plugin tomysql_native_passwordandboth succeed with the row in place. Note a
mysql_userscredential is never valid for the Admininterface on its own (it returns plain
Access denied) — yet its mere presence changes the Adminhandshake for a same-named Admin credential.
Finding 2 — the
mysql-monitor_*credential stops working on the Admin interfacemysql-monitor_username/mysql-monitor_passwordauthenticates on:6032undermysql_native_passwordand is rejected undercaching_sha2_password, with the password storedin cleartext exactly as documented:
monitor→:6032, plaintextmysql_native_passwordcaching_sha2_passwordAccess deniedThis is what #5363 reports (k8s liveness/readiness probes failing with
Access denied for user 'monitor'@'127.0.0.1', open since February with no reply) and what thefollow-up comment on #4845 asks about. #4845 has the maintainer statement that
mysql-monitor_passwordmust be cleartext — it is cleartext here, and it still fails.Finding 3 — full matrix, Admin interface
:6032All rows with
caching_sha2_passwordandmysql-have_ssl=false:admin-admin_credentials, no same-namedmysql_usersrowadmin-admin_credentials, shadowed by a hashedmysql_usersrowmysql_usersonly, caching_sha2 hashmysql_usersonly, cleartext passwordadmin-stats_credentialsmysql_native_password) shadowed Admin credentialThe frontend (
:6033) also produces the full-auth error for these credentials, but in ourtesting the outcome there varied between otherwise-identical instances, so we're only asserting
the Admin results.
Reproducer: a self-contained script that spins up a fresh container per assertion (ProxySQL
auth state turned out to be order-dependent, so shared instances give inconsistent results) and
asserts every row above — 13 checks, all passing on both 2.7.0 and 3.0.10:
(attach or link the script)
Root cause in source
CLIENT_SSLis forced on whenever the default plugin iscaching_sha2_password, independent ofhave_ssl—lib/MySQL_Protocol.cpp:1036-1043at tagv2.7.0, unchanged atlib/MySQL_Protocol.cpp:1083-1090at tagv3.0.10and on thev3.0branch:==2iscaching_sha2_password(lib/MySQL_Thread.cpp:1854-1863atv2.7.0,lib/MySQL_Thread.cpp:2366atv3.0.10). The Admin interface emits its greeting through thissame function (
lib/ProxySQL_Admin.cpp:5590atv2.7.0,lib/ProxySQL_Admin.cpp:2235atv3.0.10), which is why:6032behaves like:6033here. There is no code path that serves a server RSA public key or decrypts anRSA-encrypted password.
To be explicit: forcing
CLIENT_SSLis the right call and we are not asking for it to bereverted — it is what makes a completion path possible at all. The gap is that neither
completion path actually works.
How MySQL differs (8.0.46, verified with the same client)
CLIENT_SSL=true--tls-version=,have_ssl=DISABLED)CLIENT_SSL=falseMySQL also implements the cache the plugin is named for, so full auth only happens on first
connect, after
FLUSH PRIVILEGES, or after a password change.Worth pre-empting one thing: the
ERROR 2061 ... Authentication requires secure connectionpeople quote from the
mysqlCLI is not server-side TLS enforcement — it is the clientdeclining to fetch a public key it wasn't told to trust:
Asks, in order of preference
--get-server-public-key/--server-public-key-pathflow. This fixes non-TLS clientsgenerally.
verified against the stored credential — including
admin-*_credentialsandmysql-monitor_*. This alone would make TLS a genuine workaround, which it currently isn't.mysql_usersrow from shadowing a same-named Admin credential during the Adminhandshake (Finding 1).
authenticate to the Admin interface under
caching_sha2_password, that hashedmysql_usersentries cannot, and that TLS does not help — and return an error naming the real cause instead
of an unexpected packet.
Impact
caching_sha2_passwordis the MySQL 8.0+ default andmysql_native_passwordwas removed inMySQL 9.0, so users are increasingly provisioned with caching_sha2 credentials. Today the only
reliable workarounds are to keep ProxySQL's frontend on the deprecated
mysql_native_password,or to give each admin-side client a dedicated
admin-*_credentialsname that appears nowhere inmysql_users. Monitoring is hit first — health probes (#5363) and metrics exporters connectingto
:6032— but any client of the Admin interface is affected.Related
mysql-monitor_passwordmust be cleartext; the follow-up questionabout Admin/SHA-2 support is unanswered
monitor_userpassword not supported (closed as "not possible at the moment")v3.0) intercepts the cleartext password duringcaching_sha2 full auth; it appears TLS-gated and frontend-only, so it looks like it leaves both
gaps above open