☰
ProxySQL caching_sha2_password RSA 公钥认证:非 TLS 前端的完整认证方案
2026/10/7 2:09:59 网站建设 项目流程
  • 后端
  • 数据库
  • 负载均衡

【免费下载链接】proxysql

High-performance proxy for MySQL and PostgreSQL

项目地址:https://gitcode.com/gh_mirrors/pr/proxysql
点击查看免费下载

引言

ProxySQL 3.1 和 4.0 新增了对 MySQLcaching_sha2_password插件的完整支持:在非 TLS 前端连接下,客户端可以请求 ProxySQL 的 RSA 公钥,或使用本地预置的可信公钥副本,对密码进行 RSA-OAEP 加密后回传,从而完成全量认证。本文围绕 doc/caching_sha2_password_rsa.md 展开,深入讲解该特性的三个核心变量、密钥文件的格式与权限约束、重载与集群行为、客户端两种 RSA 模式,并结合 include/MySQL_Caching_Sha2_RSA.h、lib/MySQL_Caching_Sha2_RSA.cpp、lib/MySQL_Thread.cpp 等源码,说明其底层实现原理。读完本文,你将掌握如何配置、验证、轮换 ProxySQL 的 RSA 密钥对,并能安全地让caching_sha2_password客户端在非 TLS 场景下完成认证。

重要安全提示:TLS 仍是官方推荐的配置。通过非认证连接请求公钥,只能防止被动窃听者获知密码,无法验证 ProxySQL 服务器身份,且易受主动网络攻击者的公钥替换攻击。若需要服务器身份与传输完整性保障,请务必使用 TLS。该特性在 ProxySQL 3.0 中不可用,需 3.1+ 或 4.0。


1. 特性概述与适用场景

MySQL 8.0 默认的认证插件caching_sha2_password在完整认证(full authentication)阶段要求传输明文密码。官方客户端在非 TLS 连接下不允许直接发送明文密码,而是通过 RSA 公钥加密密码后传输。

ProxySQL 3.1/4.0 的实现让该流程在代理层自洽完成:

  1. 客户端使用caching_sha2_password插件发起连接;
  2. ProxySQL 发送握手包,进入认证切换(auth switch)流程;
  3. 若需要完整认证,ProxySQL 发出0x04(perform full authentication)挑战;
  4. 客户端选择以下任一方式:
    • 发送0x02请求 ProxySQL 的 RSA 公钥,再加密密码回传(--get-server-public-key模式);
    • 直接使用本地预置的公钥文件加密密码回传(--server-public-key-path模式);
  5. ProxySQL 用对应密钥快照解密 RSA 密文,完成认证。

整个过程在 lib/MySQL_Protocol.cpp 中实现:capture_caching_sha2_rsa_snapshot()(lib/MySQL_Protocol.cpp)在认证开始时获取密钥快照;PPHR_decrypt_caching_sha2_rsa_response()(lib/MySQL_Protocol.cpp)负责校验密文长度、调用 RSA 解密、恢复密码明文;客户端发送0x02请求公钥时,则由PPHR_1()(lib/MySQL_Protocol.cpp)响应公钥 PEM 内容。


2. 配置变量

三个 MySQL 变量构成一个配置单元(见下表)。ProxySQL 在将配置发布到前端会话之前,会整体校验或生成完整的密钥对;如果重载失败,三个变量的运行时值以及先前已加载的密钥快照均保持不变。

VariableDefaultDescription
mysql-caching_sha2_password_auto_generate_rsa_keystrue当两个配置文件都不存在时,自动生成一对 2048 位 RSA 密钥。
mysql-caching_sha2_password_private_key_pathproxysql-caching-sha2-private-key.pem私钥路径。相对路径解析到 ProxySQL 数据目录(datadir)之下。
mysql-caching_sha2_password_public_key_pathproxysql-caching-sha2-public-key.pem公钥路径。相对路径解析到 ProxySQL 数据目录(datadir)之下。

默认值定义于 lib/MySQL_Thread.cpp;三个变量在 MySQL 变量表中注册为同一个分组(lib/MySQL_Thread.cpp)。

修改后需执行:

LOAD MYSQL VARIABLES TO RUNTIME;

2.1 相对路径约束与防符号链接处理

从源码 lib/MySQL_Caching_Sha2_RSA.cpp 可以看到路径解析的安全策略:

  • 相对路径必须位于 ProxySQL 数据目录之下:解析时拒绝空组件、.与..组件(valid_relative_component,见 lib/MySQL_Caching_Sha2_RSA.cpp);
  • 每个父目录都以不跟随符号链接的方式打开:通过openat()与O_NOFOLLOW逐层打开目录(见resolve_key_path),避免符号链接逃逸;
  • 绝对路径也被允许:当密钥托管在运维人员控制的其他目录时可以使用,此时直接打开父目录(不拒绝符号链接)。

这保证了即使 ProxySQL 以较低权限运行,配置的密钥文件也不会被符号链接劫持到任意位置。

2.2 三变量分组校验与回滚

commit()(lib/MySQL_Thread.cpp)将三个变量打包为MySQL_Caching_Sha2_RSA_Config,调用reload()(lib/MySQL_Caching_Sha2_RSA.cpp):

  • 若校验失败,记录Rejected caching_sha2_password RSA key configuration错误,并将三个变量整体标记为rejected_variables(见 lib/MySQL_Thread.cpp)——即要么全部接受,要么全部回滚;
  • 若私钥与公钥路径仅配置了其中一个,reload()直接拒绝并提示 "both RSA private and public key paths must be configured together"(lib/MySQL_Caching_Sha2_RSA.cpp)。

3. 密钥格式与权限要求

3.1 格式校验

ProxySQL 对密钥文件的格式有严格限制:

  • 私钥:必须是未加密的 PKCS#8 PEM RSA 私钥(PEM 头为BEGIN PRIVATE KEY)。传统的 PKCS#1(BEGIN RSA PRIVATE KEY)与加密私钥一律被拒绝;
  • 公钥:必须是 PEM 格式的 SubjectPublicKeyInfo(SPKI)公钥(BEGIN PUBLIC KEY);
  • 两个文件必须包含结构上有效且匹配的 RSA 密钥对,长度至少 2048 位。

从源码实现可见这些约束并非文档声明而已:

  • load_private_key()使用has_single_pem_envelope()检查 PEM 信封唯一性,用PEM_read_bio_PKCS8_PRIV_KEY_INFO()解码 PKCS#8,并通过reject_password_callback(lib/MySQL_Caching_Sha2_RSA.cpp)拒绝任何需要口令的解密回调,从机制上保证"不支持加密私钥"(lib/MySQL_Caching_Sha2_RSA.cpp);
  • validate_rsa_key()检查EVP_PKEY_base_id必须为 RSA、位数不低于MINIMUM_RSA_KEY_BITS = 2048,并对私钥执行EVP_PKEY_private_check与EVP_PKEY_pairwise_check、对公钥执行EVP_PKEY_public_check结构性校验(lib/MySQL_Caching_Sha2_RSA.cpp);
  • load_key_pair()会分别导出私钥与公钥的 PEM 并逐一比对,确保二者是匹配的一对(lib/MySQL_Caching_Sha2_RSA.cpp)。

此外,私钥文件读取有1 MiB 大小上限(MAXIMUM_PEM_FILE_SIZE,见 lib/MySQL_Caching_Sha2_RSA.cpp),避免异常大文件导致内存耗尽。

3.2 文件权限

  • 私钥文件必须是普通文件(regular file),且不得授予任何组(group)或其他人(other)权限(见validate_open_file,lib/MySQL_Caching_Sha2_RSA.cpp);
  • 自动生成的文件权限模式为:
    • 私钥:0600
    • 公钥:0644

由于 ProxySQL 运行时没有口令输入通道,不支持加密私钥。如果密钥文件权限过宽或格式不合法,加载会被拒绝。

3.3 密钥自动生成与发布

自动生成仅在两个路径都不存在时发生(lib/MySQL_Caching_Sha2_RSA.cpp):

  • 若恰好只有一个文件存在,ProxySQL 报告配置错误,不会覆盖或替换任一文件("only one RSA key file exists; refusing to generate or overwrite a partial pair");
  • 生成采用临时文件 + 不覆盖发布(no-overwrite publication)机制(create_temporary_key_file使用O_CREAT | O_EXCL,发布使用linkat,见 lib/MySQL_Caching_Sha2_RSA.cpp),配合基于flock的生成锁,确保并发运行的多个 ProxySQL 进程不会发布混合的密钥对(generate_pair,lib/MySQL_Caching_Sha2_RSA.cpp)。

3.4 初始加载失败的降级行为

如果在初始运行时加载时编译默认的密钥对不可用、且无法安全重新生成,ProxySQL 会记录一个显式的TLS-only 状态(自动生成关闭、两个路径均为空,见 lib/MySQL_Thread.cpp 的disabled_config):

  • TLS 认证仍然可用;
  • RSA 公钥认证保持禁用,直到加载到有效密钥对为止。

4. 重载与集群行为

4.1 密钥快照的不可变性

每次认证交换都从完整认证挑战(0x04)到 RSA 解密全程持有同一个不可变的密钥快照,无论客户端是请求公钥还是使用已固定的副本。这意味着:

  • 并发执行LOAD MYSQL VARIABLES TO RUNTIME进行密钥轮换,不会破坏正在进行中的认证交换;
  • 快照由acquire()以shared_ptr<const MySQL_Caching_Sha2_RSA_Key_Snapshot>形式获取(lib/MySQL_Caching_Sha2_RSA.h),解密时整个交换期间引用计数持有该快照,即使后续 reload 发布了新快照也互不影响。

4.2 密钥轮换的客户端协调

使用固定公钥(pinned public key)的客户端,在 ProxySQL 密钥对轮换时必须同步更新:

需要协调发布新公钥、客户端配置变更与 ProxySQL 运行时重载三者的时机。使用与 ProxySQL 所选密钥快照不匹配的密钥的客户端无法完成认证。ProxySQL 不提供"宽限期"——新连接不能同时使用新旧两把私钥。

因此建议的轮换流程是:先发布新公钥并更新客户端,最后再执行 ProxySQL 的运行时重载。

4.3 集群同步语义

  • 集群同步(LOAD MYSQL VARIABLES TO RUNTIME跨节点传播)传输的是变量值本身,而不是私钥内容;
  • 每个 ProxySQL 节点都必须能读取其配置的本地密钥对,或在启用自动生成时生成自己的密钥对;
  • 切勿将私钥内容存入 ProxySQL 配置数据库。

从架构上看,这是为了避免私钥在集群配置通道中扩散,同时保证每个节点都能独立服务本地认证。


5. 客户端两种 RSA 模式

客户端必须使用caching_sha2_password插件,并且只有在明确需要时才关闭 TLS。Oracle MySQL CLI 支持两种 RSA 模式。

5.1 模式一:请求 ProxySQL 的公钥(--get-server-public-key)

mysql --default-auth=caching_sha2_password \ --ssl-mode=DISABLED --get-server-public-key \ --host=127.0.0.1 --port=6033 --user=app --password

认证流程:

  1. ProxySQL 发送完整认证挑战(0x04);
  2. 客户端发送0x02请求公钥;
  3. ProxySQL 返回公钥(通过generate_auth_more_data携带 PEM,见 lib/MySQL_Protocol.cpp);
  4. 客户端使用该公钥加密密码,回传 RSA 密文;
  5. ProxySQL 用发出挑战时保留的同一密钥快照解密(PPHR_decrypt_caching_sha2_rsa_response,见 lib/MySQL_Protocol.cpp)。

安全边界:此模式防止被动观察者获知密码,但不认证 ProxySQL 本身。主动攻击者可以替换另一个公钥。若服务器身份重要,请优先使用 TLS 或固定密钥模式。

5.2 模式二:使用预置的公钥(--server-public-key-path)

mysql --default-auth=caching_sha2_password \ --ssl-mode=DISABLED \ --server-public-key-path=/etc/proxysql/proxysql-caching-sha2-public-key.pem \ --host=127.0.0.1 --port=6033 --user=app --password

认证流程:

  1. ProxySQL 发送0x04挑战;
  2. 客户端不发送0x02,直接使用本地公钥文件加密密码并回传 RSA 密文;
  3. ProxySQL 用发出挑战时保留的密钥快照解密。

安全边界:请通过可信渠道分发公钥文件并保护其完整性。此模式能验证端点确实持有对应私钥,并避免未经认证的带内密钥替换。RSA 只保护密码交换本身;后续的查询、结果及其他会话流量仍然是未加密、未认证的。当整个连接都需要机密性与完整性时,请使用 TLS。

5.3 两种模式的源码验证

测试用例 test/tap/tests/reg_test_5988-caching_sha2_rsa-t.cpp 同时覆盖了两种模式:

  • 通过--get-server-public-key认证成功(见该文件 L104、L330-L335);
  • 通过--server-public-key-path认证成功,并验证错误密码返回1045(L345);
  • 还验证了禁用 RSA 后返回caching_sha2_password TLS-or-key 1045 hint的提示消息(L267);
  • 以及分组 RSA 配置拒绝时三个变量整体回滚的行为(L282-L320)。

此外,test/repro/reg_test_5985_admin_caching_sha2_full_auth.bash 验证了 ADMIN 接口(:6032)上针对caching_sha2哈希凭据的完整认证完成路径,其证明方法依赖于stats_mysql_global.Client_Connections_sha2cached计数——成功登录而该计数仍为 0,说明认证来自完整的 full auth 而非缓存。


6. 失败行为与错误处理

ProxySQL 实现了 MySQL 协议定义的 RSA OAEP 交换,包括:

  • 协议规定的SHA-1 OAEP与MGF1 摘要(见 lib/MySQL_Caching_Sha2_RSA.cpp:EVP_PKEY_CTX_set_rsa_oaep_md(... EVP_sha1())与EVP_PKEY_CTX_set_rsa_mgf1_md(... EVP_sha1()),源码注释明确指出这是 MySQLcaching_sha2_password所要求的 OAEP SHA-1);
  • 密码与 scramble 的XOR 步骤(lib/MySQL_Caching_Sha2_RSA.cpp:plaintext[index] ^= scramble[index % scramble_length]);
  • 解密后验证明文是单一 NUL 结尾字符串(lib/MySQL_Caching_Sha2_RSA.cpp)。

以下所有情况均返回标准的1045/28000访问被拒绝响应:

  • 格式错误的密文(EVP_PKEY_decrypt失败);
  • 格式错误的明文(非单一 NUL 结尾字符串);
  • 密码不正确(认证校验失败)。

此外,lib/MySQL_Caching_Sha2_RSA.cpp 的decrypt_password()使用ScopedStringCleanser/ScopedBufferCleanser(OPENSSL_cleanse)在解密后立即擦除内存中的明文密码与中间缓冲,降低密码在内存中长期驻留的风险。

当没有可用的有效 RSA 密钥对时,返回相同的1045/28000错误码与 SQLSTATE,但错误消息会明确指出 RSA 密钥交换不可用,并建议使用 TLS 或配置密钥(对应MySQLFrontendAuthError::CACHING_SHA2_RSA_UNAVAILABLE,见 lib/MySQL_Protocol.cpp)。


7. 推荐的安全实践小结

场景推荐方案
需要服务器身份认证与传输完整性使用 TLS(始终是官方推荐配置)
仅需保护密码免于被动窃听,且不要求服务器身份--get-server-public-key模式
需要验证端点持有对应私钥、规避带内公钥替换--server-public-key-path固定公钥模式 + 可信分发通道
密钥轮换先发布新公钥 → 更新客户端 → 再执行 ProxySQL 运行时重载(无宽限期)
集群多节点各节点独立持有/生成本地密钥对;绝不把私钥写入配置数据库
私钥文件未加密 PKCS#8 PEM、≥2048 位、权限0600,存放于 datadir 下或受控绝对路径

该特性通过 include/MySQL_Caching_Sha2_RSA.h 的MySQL_Caching_Sha2_RSA类提供原子快照发布与解密,通过 lib/MySQL_Thread.cpp 的commit()完成三变量整体校验与回滚,具体流程可参阅 doc/caching_sha2_password_rsa.md 原文及对应回归测试 test/tap/tests/reg_test_5988-caching_sha2_rsa-t.cpp。

  • 后端
  • 数据库
  • 负载均衡

【免费下载链接】proxysql

High-performance proxy for MySQL and PostgreSQL

项目地址:https://gitcode.com/gh_mirrors/pr/proxysql
点击查看免费下载
上一篇:PaddleSpeech 声纹识别(说话人验证)实战:用 ECAPA-TDNN 提取说话人嵌入并计算相似度
下一篇:lilos异步RTOS未来路线图:物联网边缘计算的终极指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询