ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL SSL加密配置实战:自建CA、服务端开启与连接排查

MySQL SSL加密配置实战:自建CA、服务端开启与连接排查 前阵子给一家公司做数据库迁移后的安全加固安全团队抓包巡检的结果直接把我给看愣了3306端口上跑的全是明文包括应用账号的密码。这里说的不是公网而是内网。但只要有一个人误装抓包工具、存在被挂马的跳板机、或者网络设备做了端口镜像全量口令就等于摆在那里的裸奔状态。于是领导顺手把任务丢了过来把MySQL的SSL加密访问配上。这篇文章就是这套操作的完整记录从证书生成、服务端配置、客户端连接验证到最常见的MySQL SSL连接错误排查一次说清楚。适合所有在用MySQL的DBA、后端开发和运维同学哪怕之前没碰过证书也不慌照着步骤走就能落地。1. 明文连接的隐患与SSL加密的定位1.1 内网抓包就能看到root密码明文连接的真实风险MySQL默认情况下客户端与服务器之间跑的是纯明文协议。所谓“默认”指的就是你从命令行敲mysql -uroot -p连上去之后认证阶段的口令、后续所有SQL语句和查询结果都是可直接嗅探的。我用tcpdump在内网机房抓过包重启服务后顺手一抓一会儿就看到Authentication Plaintext Password那一行root的密码就安静地躺在数据包里。这个场景其实很普遍不是只有被攻破才会出事安全审计、等保检查、合作伙伴需要提供加密传输截图这些需求一年总要来几次。明文连接的真正风险有三层口令泄露认证阶段的数据明文可见抓包等于拿密码。数据被篡改中间人可以把UPDATE改成DELETE客户端和服务端根本感知不到。身份被冒充客户端以为是你的服务器其实连到了一个伪装的MySQL实例。所以SSL加密访问在MySQL这里的定位从来不是“防内鬼”那么悲观而是“假设网络上有人能看、能改你的连接依然可信”。这个边界先想清楚后面配置起来思路就顺了。1.2 SSL在MySQL里的作用传输加密与身份校验大家经常说“MySQL SSL”其实底层就是TLS协议只是约定成俗叫SSL。它在数据库连接里干两件事第一件事是传输加密。握手之后的会话数据全部用对称密钥加密抓包只能看到一堆密文再也要不回密码和语句明文。第二件事是身份校验。服务端会出示证书客户端可以验证这个证书是不是由你信任的CA签发的防止连到假服务器。如果需要更强的安全性还可以让客户端也出示证书实现双向认证这样MySQL可以精确到“只允许持有指定客户端证书的账号连接”。从证书文件的构成来看一套最小可用体系需要三件东西CA私钥与CA证书、服务端私钥与服务端证书。如果做双向认证还要额外给客户端签发一份证书。搞清楚这个结构之后接下来所有命令都是在围绕这三件套打转。2. 证书准备自建CA、OpenSSL生成与权限2.1 为什么我不建议拿网站SSL证书给MySQL用很多人的第一反应是“我有阿里云免费的SSL证书能不能直接拿去给MySQL用”我用过之后的结论是不推荐。网站SSL证书签发时绑定的是域名比如api.example.com而MySQL客户端连接时往往走的是IP地址或内网主机名证书里的域名和实际连接地址对不上只要开启严格校验就会失败。另外免费证书的有效期通常只有90天到一年你得不停续期、替换文件MySQL又不像Nginx可以无感reload每换一次证书就得重启一次数据库这个运维成本不值得。MySQL内部传输加密更适合的方案是自建CA。自建CA签出来的证书不需要外面任何人信任只需要参与通信的客户端和服务端信任这份CA证书就行。就像公司内部自己发工作牌不需要派出所备案但只要门卫认这个工作牌就没问题。整个过程完全可控签发年限也能设成10年减少频繁换证书的烦恼。如果你的MySQL是官方安装包或源仓库安装的bin目录下其实带了一个mysql_ssl_rsa_setup脚本可以一键生成一套自签证书。我的建议是测试环境拿它快速跑通没问题生产环境还是用OpenSSL手动生成更稳妥因为脚本生成的证书CN信息基本没配置后面一旦要开VERIFY_IDENTITY严格校验主机名验证基本过不了。2.2 OpenSSL生成CA和服务端证书的完整过程先在工作机上建一个目录后面所有证书操作都在这里进行mkdir -p /opt/mysql-certs cd /opt/mysql-certs第一步生成CA私钥和CA证书。这里用的-nodes参数表示私钥不加密。CA私钥理论上应该加密保护但如果CA私钥加了密码后续每次签发证书都要手动输密码自动化脚本就没法用了。我的做法是CA私钥留密码并单独收好下面命令为了方便展示先不带额外口令openssl genrsa 2048 ca-key.pem openssl req -new -x509 -nodes -days 3650 -key ca-key.pem -out ca-cert.pem -subj /CNMySQL-Internal-CA第二步生成服务端私钥和证书签名请求CSR。服务端私钥一定不要加密码因为MySQL每次启动都要读取它加了口令之后要么无法自动启动要么得额外处理密钥密码非常麻烦openssl genrsa 2048 server-key.pem openssl req -new -key server-key.pem -out server-csr.pem -subj /CNmysql-server-01第三步准备扩展文件把服务器的域名和IP填进SAN字段。这一步很多人会漏但客户端在VERIFY_IDENTITY模式下会严格检查证书里的SAN和实际连接主机是否一致不写SAN后续排查会花掉不少时间。我一般会这样写cat server-ext.cnf EOF subjectAltName DNS:db.example.com, IP:192.168.1.10 EOF第四步用CA签发服务端证书指定有效期10年openssl x509 -req -in server-csr.pem -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 3650 -extfile server-ext.cnf最后生成完后检查一下证书内容确认SAN和有效期正确openssl x509 -in server-cert.pem -text -noout | grep -A1 Subject Alternative Name如果以后要做双向认证再给客户端签发一份证书流程类似生成客户端私钥、CSR再用同一套CA签发即可。2.3 证书文件权限与路径设计的坑证书生成完毕放在哪个目录、权限怎么设是很容易翻车的环节。我踩过最典型的一个坑是私钥权限太开放MySQL启动时直接报权限错误拒绝读取。可靠的做法是把证书统一放到一个独立路径比如/etc/mysql/certs/mkdir -p /etc/mysql/certs cp ca-cert.pem server-cert.pem server-key.pem /etc/mysql/certs/ chown -R mysql:mysql /etc/mysql/certs chmod 600 /etc/mysql/certs/server-key.pem chmod 644 /etc/mysql/certs/ca-cert.pem /etc/mysql/certs/server-cert.pem这里的关键判断是私钥必须只有mysql进程能读权限给600证书文件本身是公钥性质的给别人看无所谓644即可。如果权限给成了其他非mysql用户可读MySQL在部分发行版上的OpenSSL实现会拒绝使用报错往往写的是bad permission之类问题不大但很迷惑人。3. 服务端开启SSL配置项、强制加密与验证3.1 my.cnf配置与require_secure_transport的取舍证书就位后修改MySQL配置文件。在[mysqld]段下追加三行[mysqld] ssl_ca/etc/mysql/certs/ca-cert.pem ssl_cert/etc/mysql/certs/server-cert.pem ssl_key/etc/mysql/certs/server-key.pem require_secure_transportON前三项是告诉MySQL“用哪套证书”require_secure_transportON是强硬表态所有连接必须走加密通道不支持SSL的客户端直接拒绝连接。这个选项MySQL 5.7.5以上和8.0全系都支持。很多初次配置的同学会在这时候犯嘀咕“我开了强制加密会不会把现有客户端全部搞挂”这个担心是对的所以生产环境建议分两步走先只配置SSL证书不开启require_secure_transport等客户端都验证能连上、确认没问题后再打开强制加密开关。不要第一天就把门焊死容易把业务连接全部冲到地上。另外[mysqld]段还有一个skip_ssl开关默认是关闭的也就是说只要没有显式写skip_sslMySQL本身是具备SSL能力的。之所以要显式配置证书是因为MySQL自带的自动证书生成只覆盖初始化阶段手动指定之后才可控、可更新、可严格校验。3.2 重启后怎么验证SSL真正生效配置完成重启MySQLsystemctl restart mysqld重启后用root或其他管理账号登录执行这条SQL查看SSL相关变量SHOW VARIABLES LIKE %ssl%;几个关键结果的正常状态是变量名正常值说明have_sslYES服务端SSL功能已启用have_opensslYES使用OpenSSL实现ssl_ca/etc/mysql/certs/ca-cert.pemCA证书加载成功ssl_cert/etc/mysql/certs/server-cert.pem服务端证书加载成功ssl_key/etc/mysql/certs/server-key.pem服务端私钥加载成功如果have_ssl是DISABLED说明配置没有被正确加载。这时候第一时间去MySQL错误日志里翻证书相关报错比如路径不存在、私钥无法解析、权限不足等逐条核对。日志文件一般在/var/log/mysql/error.log或/var/log/mysqld.log不同发行版路径略有差异。如果担心配置前后行为不一致还可以执行SHOW STATUS LIKE Ssl_cipher;看一下当前会话的加密套件。新会话连上后这个值是TLS_AES_256_GCM_SHA384之类的字符串说明当前连接已经完成TLS握手。特别提醒通过本地socket连接的会话走的是Unix socket不是网络协议栈即使SSL配置成功socket连接本身并不受SSL保护这属于正常行为。调试阶段还有一个非常实用的压测方法在另一台机器上不指定任何SSL参数直接连接如果看到ERROR 3159 (HY000): Connections using insecure transport are prohibited while --require_secure_transportON恭喜你强制加密已经生效。4. 客户端连接SSL模式、双向认证与GUI配置4.1 命令行连接--ssl-modeREQUIRED的正确姿势MySQL 8.0客户端默认的SSL模式是PREFERRED意思是服务器支持就加密不支持就退回明文。这个默认值照顾了兼容性但也埋了一个隐患——如果服务器SSL配置有问题客户端会悄无声息地降级成明文你根本察觉不到。所以客户端侧要主动用REQUIRED模式强制加密。命令行连接方式如下mysql -h db.example.com -u app_user -p --ssl-modeREQUIRED如果你希望客户端同时校验服务器证书是否可信再加上CA证书参数mysql -h db.example.com -u app_user -p --ssl-modeVERIFY_CA --ssl-ca/etc/mysql/certs/ca-cert.pemVERIFY_CA只验证证书是由受信CA签的VERIFY_IDENTITY还会额外校验主机名。两者差别要分清前者防伪造CA后者还能防别人拿着同一CA签发的其他域名证书来冒认服务器。老版本MySQL 5.7的客户端用的是--ssl和--ssl-verify-server-cert参数名不同语义类似。我的建议是能升级客户端就升级到8.0毕竟--ssl-mode这一套在新老版本里被统一得最干净。4.2 如何确认真实连接真的在加密我自己见过不少“配了SSL但实际没生效”的情况所以验证这一步绝对不能省。连接上MySQL之后直接在客户端执行SHOW SESSION STATUS LIKE Ssl_cipher;返回结果里只要有一串加密套件名称比如TLS_AES_256_GCM_SHA384就说明当前连接是加密的。如果返回空就代表这个会话没有走SSL哪怕服务器已经开了SSL也可能是客户端这边主动选择了不加密。也可以用performance_schema查所有会话的加密情况这个在生产环境排障时更好用SELECT PROCESSLIST_ID, VARIABLE_VALUE FROM performance_schema.session_status WHERE VARIABLE_NAMESsl_cipher AND PROCESSLIST_ID IS NOT NULL;这样能一次看清楚当前MySQL实例上的所有连接里哪些是加密的哪些还是明文。我上线新的强制加密策略之前一定会执行这条语句对照一遍确认没有漏网之鱼。4.3 进阶REQUIRE X509双向认证与编程语言连接参数如果你面对的审计要求比较高比如“必须双向认证”那就要走到用户级别的策略了。MySQL可以在账号粒度上做限制比如ALTER USER app_user% REQUIRE X509;这一条执行之后app_user连接时必须出示客户端证书缺证书直接拒绝。配合服务端设置的ssl_caMySQL会校验客户端证书是否由同一CA签发。这种做法适合对安全极其敏感的内部系统代价是每个应用服务器都得存放并分发客户端证书和私钥证书的吊销与轮换需要额外管理流程。编程语言这边的连接参数也顺带提一下方便大家直接“抄作业”。Java的JDBC连接串可以这样加jdbc:mysql://db.example.com:3306/appdb?sslModeREQUIREDMySQL Connector/J 8.0.30以上推荐用sslMode更早的版本用useSSLtrue和requireSSLtrue。Python的连接示例import mysql.connector conn mysql.connector.connect( hostdb.example.com, userapp_user, passwordsecret, ssl_ca/etc/mysql/certs/ca-cert.pem, ssl_verify_certTrue, )Go语言的go-sql-driver/mysql则是在DSN里拼tlstrue或用tlsskip-verify、tlspreferred等。这些参数名在不同驱动里叫法不一但核心思路一致显式指定加密策略不要依赖默认值。5. SSL connection error排查链路从报错到根因5.1 ERROR 2026服务端没开启或TLS版本不对在论坛和社区里MySQL SSL相关热词的搜索权重一直很高尤其是“SSL connection error”和“mysql ssl连接错误”几乎每周都能看到有人问。最常见的报错长这样ERROR 2026 (HY000): SSL connection error: protocol version mismatchprotocol version mismatch协议版本不匹配这个说法很有迷惑性。我遇到的大部分情况并不是“TLS协议版本不兼容”而是服务端SSL压根没配好或者客户端太老、无法和服务器协商出共同支持的加密套件。排查第一步永远是回服务端确认状态。登录MySQL执行SHOW VARIABLES LIKE have_ssl;如果结果是DISABLED问题大概率出在服务端。回去看错误日志很大概率是证书路径错误、私钥权限不对或者证书和私钥不配对。这三个原因占掉一半以上。5.2 证书验证失败SAN不匹配与自签名证书另一类高发问题是证书验证失败。客户端如果指定了--ssl-ca或--ssl-modeVERIFY_IDENTITY服务端证书不是由该CA签发、证书过期、SAN里没有客户端连接时使用的主机名或IP都会导致握手失败。这时候最实用的验证命令是openssl x509 -in /etc/mysql/certs/server-cert.pem -text -noout | grep -E Not Before|Not After|Subject Alternative Name我推荐把重点放在SAN上。很多人签名证书时只填了CN没填SAN客户端连接时又恰好用了VERIFY_IDENTITY这种组合在MySQL 8.0客户端里基本必报错。另外要记得核对服务器系统时间证书有效期校验非常依赖时间服务器时间偏了没过期的证书也会被当成过期。还有一种常见场景用了自签名证书客户端没有指定--ssl-ca只用了REQUIRED。这种写法连接是加密的但客户端并没有校验服务器身份等于“防偷听但不防冒充”。如果审计明确要求身份验证就一定要把CA证书发到客户端并显式指定。5.3 一条可复现的排查步骤清单在实际运维中我会按照下面的顺序来排查基本不会漏服务端执行SHOW VARIABLES LIKE %ssl%;确认have_sslYES。检查错误日志中是否有证书加载报错路径、权限、格式逐个核对。用openssl检查服务端证书有效期和SAN字段是否匹配客户端连接地址。客户端先不带--ssl-ca只用--ssl-modeREQUIRED测试连通性。再逐步加上--ssl-ca和--ssl-modeVERIFY_IDENTITY缩小验证范围。如果客户端版本太老换最新8.0客户端重试。最后检查MySQL账号是否被REQUIRE SSL或REQUIRE X509限制。把报错现象、可能原因和定位动作放在一张表里会更直观报错现象可能原因优先排查动作ERROR 2026 protocol version mismatch服务端SSL未启用或套件不兼容检查have_ssl、错误日志ERROR 3159 insecure transport prohibited服务端强制加密客户端在明文连接客户端加--ssl-modeREQUIREDCertificate Verification Failed / Unknown CA客户端未指定CA或服务器证书非该CA签发客户端指定--ssl-ca并核对CA链Hostname/IP does not match certificateSAN字段缺失或不匹配检查证书SAN并使用匹配连接地址bad permission / unable to load key私钥权限过宽或属主不对chmod 600并chown给mysql用户这张表基本覆盖了日常能看到的大部分SSL连接问题。顺着链路走一遍十分钟内能定位。6. 容易被忽略的进阶场景Docker、主从复制与性能损耗6.1 Docker挂载证书的几个注意点现在不少人用Docker跑MySQL比如docker run或者docker-compose。证书挂载进去的姿势比想象中容易踩坑。最简单可靠的方式是启动时用只读挂载docker run -d \ --name mysql-ssl \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDsecret \ -v /etc/mysql/certs:/etc/mysql/certs:ro \ -v /etc/mysql/conf.d:/etc/mysql/conf.d:ro \ mysql:8.0这里有两个经验值得分享。第一容器内的MySQL配置文件需要把证书路径指向挂载目录并且要保证容器中mysql用户对私钥文件有读取权限。官方镜像里的mysql用户UID通常是999宿主机的文件如果属主是root且权限是600容器里会直接读取失败。我的做法是明确执行chown 999:999 /etc/mysql/certs/server-key.pem chmod 600 /etc/mysql/certs/server-key.pem第二挂载目录用:ro只读模式更安全证书属于不可变配置没必要让容器往里写。当初我被docker cp填进去的证书坑过一次容器重建就丢后来改成宿主机持久化挂载一劳永逸。6.2 主从复制的SSL参数MySQL主从复制同样支持SSL原理和普通客户端连接一样因为从库本质就是一个MySQL客户端。老版本通过CHANGE MASTER TO配置CHANGE MASTER TO MASTER_HOSTdb-master.example.com, MASTER_USERrepl, MASTER_PASSWORDsecret, MASTER_SSL1, MASTER_SSL_CA/etc/mysql/certs/ca-cert.pem;MySQL 8.0.22之后的语法改成了CHANGE REPLICATION SOURCE TO参数名也变成了SOURCE_SSL、SOURCE_SSL_CA语义完全相同。如果你的全局配置已经开了require_secure_transportON那么从库连接也必须支持SSL否则复制线程会一直报连接失败。这个坑比较隐蔽因为它不会体现在业务查询里只会在复制状态里静默报错。6.3 加密后的性能影响到底有多大不少人对开启SSL有顾虑觉得加解密会拖慢数据库。实测下来的结论是影响可控但要看场景。TLS握手阶段的开销相对明显每次新建连接都要做一次非对称加密交换不过在长连接架构下这个一次性成本会被摊薄。真正持续占用的是对称加密部分现代CPU基本都支持AES-NI硬件加速指令加解密损耗通常很低。我在一个压力测试场景里对比过开SSL后简单查询的QPS大概下降5%左右高并发写入场景的延迟也有增加但远没到“不能忍”的程度。比较稳妥的策略是先开启SSL支持观察一段时间业务响应时间再用require_secure_transport强制加密。如果业务对性能极度敏感可以只对高安全要求的账号单独设置REQUIRE SSL普通连接继续走明文既守住底线又控制成本。根据我个人的经验绝大多数业务场景其实并不会感知到SSL带来的额外开销真正需要注意的是证书轮换流程而不是性能。配置好SSL之后还有一个细节值得养成习惯把证书有效期记录在案提前一个月设置提醒。毕竟数据库实例的证书一旦过期所有应用连接会成片报错那场面比配错任何一个参数都更让人头疼。
返回列表