- 后端
- 数据库
- 负载均衡
【免费下载链接】proxysql
High-performance proxy for MySQL and PostgreSQL
引言
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 的实现让该流程在代理层自洽完成:
- 客户端使用
caching_sha2_password插件发起连接; - ProxySQL 发送握手包,进入认证切换(auth switch)流程;
- 若需要完整认证,ProxySQL 发出
0x04(perform full authentication)挑战; - 客户端选择以下任一方式:
- 发送
0x02请求 ProxySQL 的 RSA 公钥,再加密密码回传(--get-server-public-key模式); - 直接使用本地预置的公钥文件加密密码回传(
--server-public-key-path模式);
- 发送
- 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 在将配置发布到前端会话之前,会整体校验或生成完整的密钥对;如果重载失败,三个变量的运行时值以及先前已加载的密钥快照均保持不变。
| Variable | Default | Description |
|---|---|---|
mysql-caching_sha2_password_auto_generate_rsa_keys | true | 当两个配置文件都不存在时,自动生成一对 2048 位 RSA 密钥。 |
mysql-caching_sha2_password_private_key_path | proxysql-caching-sha2-private-key.pem | 私钥路径。相对路径解析到 ProxySQL 数据目录(datadir)之下。 |
mysql-caching_sha2_password_public_key_path | proxysql-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认证流程:
- ProxySQL 发送完整认证挑战(
0x04); - 客户端发送
0x02请求公钥; - ProxySQL 返回公钥(通过
generate_auth_more_data携带 PEM,见 lib/MySQL_Protocol.cpp); - 客户端使用该公钥加密密码,回传 RSA 密文;
- 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认证流程:
- ProxySQL 发送
0x04挑战; - 客户端不发送
0x02,直接使用本地公钥文件加密密码并回传 RSA 密文; - 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
相关推荐
WebSSH公钥认证完全指南:支持DSA、RSA、ECDSA、Ed25519密钥
WebSSH公钥认证完全指南:支持DSA、RSA、ECDSA、Ed25519密钥 WebSSH是基于Web的SSH客户端,提供了便捷的远程服务器管理体验。本指南
后端运维网络安全Go-MySQL认证机制:支持mysql_native_password、caching_sha2_password
Go MySQL认证机制:支持mysql_native_password、caching_sha2_password 在当今的数据驱动时代,MySQL数据库的安
数据库后端CANN/driver ECC时间戳查询
dcmi\_get\_multi\_ecc\_time\_info<a name="ZH CN_TOPIC_0000002485455470" </a 函数原型
驱动开发人工智能CANN
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考