拓冰建站拓冰建站
首页 / 资讯中心 / 正文

MySQL SSL加密配置实操:从证书生成到强制加密

上个月帮一个做电商的朋友排查数据库慢查询习惯性地在跳板机上起了一个 tcpdump 抓包结果眼睁睁看着一条 UPDATE 语句把用户的手机号和收货地址整段整段地暴露在网络上。那一刻我确实有点冒冷汗——这台 MySQL 部署在机房内网但内网不等于安全能登录跳板机的人、同一网段被攻破的服务、偶尔要从办公网直连的开发入口任何一个环节有漏洞数据就是在裸奔。这篇文章不聊虚的直接完整走一遍 MySQL 配置 SSL 加密连接的实操流程从 OpenSSL 生成 CA 和服务器证书、修改服务端配置、各种客户端命令行、JDBC、Python、Workbench的连法到强制加密、证书轮换、常见报错排查。内容偏 DBA 和全栈开发向运维和后端同学照着做就行如果你只想快速跑通一个最小方案我把核心命令单独标出来了全程大概 15 分钟。踩过的坑我都会提前指出来尤其是那些网上教程不会写的细节。1. 先搞清楚MySQL 为什么要配 SSL配完之后解决了什么1.1 不加密的 MySQL 到底有多“裸”MySQL 的经典协议就是 3306 端口跑的那个协议默认情况下是明文传输的。这里说的明文包括三样东西客户端发出去的 SQL 语句、服务器返回的结果集、还有认证阶段使用的密码哈希。什么意思呢就是只要有人在客户端和服务器之间的链路上抓包你的数据字典、用户资料、业务 SQL 基本等于摊开给人看。很多人觉得“我在内网风险不大”但内网从来不是信任边界。云环境里同一 VPC 下的其他主机、办公网里装了抓包工具的机器、被植入后门的跳板机都是潜在风险点。而且很多合规审计都会检查数据库传输是否加密如果连这个都没有报告基本过不了。我当时那个朋友的情况就是这样数据库连着公网还开着 3306明文跑了三年想想都后怕。1.2 SSL 到底保护了哪几层东西配置了 SSL 之后主要解决三件事。第一是传输加密SQL 语句和结果集在网络链路上变成密文即使被抓包看到的也是一堆不可读的 TLS 记录而不是能直接用的 SQL。第二是身份验证客户端可以校验服务器的证书确认自己连的确实是目标服务器而不是中间人冒充的假库。第三是完整性保护TLS 的 MAC 校验能发现数据在传输过程中是否被篡改。有一点要说清楚SSL 只保护“客户端到 MySQL 服务器”这一段网络传输不保护数据库文件本身也不保护应用服务器和数据库之间的业务逻辑。它解决的是传输链路问题磁盘加密、权限管控、审计日志这些是另一套工程别混为一谈。1.3 握手过程一句话讲明白MySQL 的 SSL/TLS 握手本质上和 HTTPS 一样客户端在连接时声明要使用 SSL服务器把自己的证书链发过来客户端验证证书是否可信对应 CA 校验然后双方协商出会话密钥后续所有通信都用这把会话密钥加密。MySQL 支持 TLSv1.2 和 TLSv1.3具体用哪个版本由双方协商结果决定。理解这个握手过程很重要因为后面所有报错几乎都出在这几个环节上证书不可信、证书过期、主机名不匹配、协议版本不兼容、客户端没带证书。把这些环节逐个排查比瞎试参数高效得多。2. 证书生成用 OpenSSL 搭一套自己的 CA2.1 准备阶段确认版本和工具要配置 SSL首先得有一套证书。生产环境最标准的做法是自己搭一个内部 CACertificate Authority证书颁发机构用这个 CA 去签发服务器证书和客户端证书。好处是所有证书都在自己手里需要签多少张都行证书的 CN、有效期、扩展属性都能自定义。需要用到的工具就是 OpenSSL几乎所有的 Linux 发行版都自带。先确认版本openssl version只要是 1.1.1 以上版本就完全够用1.1.1 和 3.x 的命令兼容性没问题。另外先确认 MySQL 的版本和安装方式不管你是用 yum、apt 还是 Docker 装的 MySQL证书生成和服务端配置的思路完全相同区别只在配置文件路径。Docker 部署的话要注意把证书目录挂载进容器后面我会专门提到。注意我下面的命令都是在专门的证书目录下执行建议统一放在 /etc/mysql/ssl 或者 /data/mysql-ssl 这类独立目录不要随手丢在 /tmp 里避免权限混乱和误删。2.2 生成 CA 根证书第一步生成 CA 的私钥。私钥是整个信任链的根一定要保护好权限 600 起步最好放到只有 root 能读的目录mkdir -p /etc/mysql/ssl cd /etc/mysql/ssl openssl genrsa 2048 -out ca-key.pem2048 位是目前的基本要求如果机器性能允许也可以用 4096 位更安全但握手性能会略受影响。生成完私钥之后用这个私钥自签一个 CA 根证书openssl req -new -x509 -days 3650 -key ca-key.pem -out ca.pem \ -subj /CNMySQL-Internal-CA \ -addext basicConstraintscritical,CA:TRUE \ -addext keyUsagecritical,keyCertSign,cRLSign这里几个参数说明一下。-days 3650 是 CA 根证书的有效期根证书一般给 10 年但实际运维中建议 5 年就轮换一次避免根证书泄露的影响面太大。-subj 里的 CN 只是个标识可以写成你们的组织名重点在于后面服务器证书的 CN 一定要写准。-addext 是给 CA 加上关键扩展标注这是一个 CA 证书并且只能用来签发证书。2.3 生成服务器证书并签发CA 有了接下来给 MySQL 服务器签发证书。这里有一个极其关键的坑服务器证书的 CN 必须和客户端连接时使用的域名或 IP 匹配否则后面用 VERIFY_IDENTITY 模式严格校验主机名时会直接失败。推荐用域名如果只能用 IP 连接CN 就写 IP。先生成服务器私钥和证书签名请求CSRopenssl genrsa 2048 -out server-key.pem openssl req -new -key server-key.pem -out server-csr.pem \ -subj /CNdb1.example.com然后用刚才的 CA 私钥去签发服务器证书。签发时我建议加上 SANSubject Alternative Name这样一台 MySQL 如果有多个访问域名可以在 SAN 里都列上避免以后换域名要重新签发openssl x509 -req -days 825 -in server-csr.pem \ -CA ca.pem -CAkey ca-key.pem -CAcreateserial \ -out server-cert.pem \ -extfile (printf subjectAltNameDNS:db1.example.com,DNS:db1-internal.example.com,IP:10.0.0.10\n)签出来的 server-cert.pem 就是服务器给客户端看的证书。825 天是 2 年多几天我习惯把服务器证书有效期控制在 2~3 年太长容易忘记轮换太短频繁续签也是负担。CAcreateserial 参数会生成一个 ca.srl 序列号文件用来防止签发出重复序列号的证书这个文件也要保存好。2.4 可选生成客户端证书做双向认证如果你的安全要求更高想让客户端也持有证书连数据库时不仅要输密码还要出示证书那就需要再签一张客户端证书openssl genrsa 2048 -out client-key.pem openssl req -new -key client-key.pem -out client-csr.pem \ -subj /CNclient-app01 openssl x509 -req -days 825 -in client-csr.pem \ -CA ca.pem -CAkey ca-key.pem -CAcreateserial \ -out client-cert.pem \ -extfile (printf extendedKeyUsageclientAuth\n)注意客户端证书的 extendedKeyUsage 要标成 clientAuth服务器证书要标成 serverAuth签发服务器证书时最好也加上。这种双向认证模式对应 MySQL 里的 REQUIRE X509后面第 5 部分会讲。2.5 文件权限与放置位置证书文件生成之后权限是最容易踩坑的地方。MySQL 服务进程通常以 mysql 用户运行私钥文件如果其他用户也能读MySQL 可能直接拒绝启动或者安全扫描报出风险。标准做法chmod 600 ca-key.pem server-key.pem client-key.pem chmod 644 ca.pem server-cert.pem client-cert.pem chown -R mysql:mysql /etc/mysql/ssl私钥 600证书 644目录属于 mysql 用户。这一步做完证书就绪可以去配置服务端了。3. 服务端配置让 MySQL 加载证书并开启加密3.1 修改 my.cnf / mysqld 配置段找到 MySQL 的配置文件一般路径是 /etc/my.cnf 或者 /etc/mysql/mysql.conf.d/mysqld.cnfDocker 容器里通常通过挂载配置文件的方式覆盖。在 [mysqld] 段下加三行[mysqld] ssl-ca/etc/mysql/ssl/ca.pem ssl-cert/etc/mysql/ssl/server-cert.pem ssl-key/etc/mysql/ssl/server-key.pem如果你的 MySQL 版本是 8.0也可以写成下划线风格ssl_ca、ssl_cert、ssl_key两种都识别。除此之外还有一个参数建议顺手加上tls_versionTLSv1.2,TLSv1.3这个参数控制允许使用的 TLS 协议版本。MySQL 5.7.10 之后支持 tls_version8.0.16 之后支持 TLSv1.3。明确只开 TLSv1.2 和 TLSv1.3把 TLSv1.0、TLSv1.1 关掉能直接堵住一大批老协议漏洞。很多安全扫描报告里提到的 CVE-2016-2183 这类问题本质上就是弱加密算法和老协议导致的禁用之后基本就能消掉。修改完配置文件在确认配置无语法错误的情况下重启 MySQL。先验证一下配置能不能被 MySQL 正常读取mysqld --validate-config这个命令会检查配置文件和参数有错误会直接输出。如果 MySQL 是 8.0执行这个命令时可能提示需要指定 datadirnormal 情况下不需要额外处理直接跑就行。没问题就重启systemctl restart mysqld # 或者 Docker 部署 docker restart mysql-container3.2 重启服务并验证 SSL 状态重启完成后登录 MySQL 检查 SSL 相关变量SHOW VARIABLES LIKE %ssl%;重点看两个值have_ssl 应该是 YESssl_ca、ssl_cert、ssl_key 应该指向你配置的三个文件。如果 have_ssl 是 DISABLED说明证书加载失败去错误日志里找原因大概率是文件权限或者证书文件损坏。再查一下动态状态SHOW STATUS LIKE Ssl%;里面的关键指标是 Ssl_version当前协议的协商版本、Ssl_cipher协商出来的加密套件、Ssl_accepts接受 SSL 连接的次数。如果这些值里有内容说明服务器已经在提供 SSL 加密服务了。3.3 关于 MySQL 8.0 自动生成证书的那点事MySQL 8.0 在初始化数据目录时如果发现没有配置任何 SSL 相关参数会自动生成一套自签名证书参数 auto_generate_certs 默认就是 ON。所以很多 8.0 用户即使什么都不配置have_ssl 也会是 YES。但这里有一个很大的误区8.0 自动生成的证书是自签名的它的 CA 就是它自己客户端如果不显式信任这个 CA用严格校验模式连接会直接报证书不可信。而且自动生成的证书有效期通常只有一年到期后如果没注意所有强制校验的客户端会瞬间连不上。所以我强烈建议哪怕 8.0 自动生成了证书也按上面步骤手签一套替换掉把主动权握在自己手里。4. 客户端连接命令行、JDBC、Python、Workbench 全覆盖4.1 mysql 命令行的 --ssl-mode 五档说明服务端配置完成接下来是客户端。先用 mysql 命令行工具验证。MySQL 5.7.11 之后客户端支持 --ssl-mode 参数一共五档从松到严分别是ssl-mode 取值行为适用场景DISABLED完全不使用 SSL只用于排查、本地回环调试PREFERRED优先 SSL服务器不支持则退化为明文默认值兼容性好但不安全REQUIRED必须 SSL但不校验 CA只要求加密不关心是不是假服务器VERIFY_CA必须 SSL 且校验 CA生产环境推荐VERIFY_IDENTITY必须 SSL、校验 CA 且校验主机名有域名/IP 固定映射的场景等我配置好之后生产环境最少要用 VERIFY_CA。验证时把 CA 传给客户端mysql --ssl-modeVERIFY_CA --ssl-ca/etc/mysql/ssl/ca.pem \ -h db1.example.com -u app_user -p如果一切正常登录后在 MySQL 里执行SHOW STATUS LIKE Ssl_cipher;能看到一个具体的加密套件名称比如 TLS_AES_256_GCM_SHA384就说明这条连接已经是加密状态了。如果结果是空说明还是明文连接。4.2 JDBC 连接串配置与常见坑Java 应用用的是 Connector/J 驱动。老的 5.x 驱动用 useSSL、requireSSL、verifyServerCertificate 这一组参数新一点的 8.0 驱动推荐直接用 sslMode语义更清楚。jdbc:mysql://db1.example.com:3306/app_db?useSSLtruerequireSSLtrueverifyServerCertificatetruetrustCertificateKeyStoreUrlfile:/etc/ssl/ca-truststore.jkstrustCertificateKeyStorePasswordchangeit如果是 8.0 驱动可以这样写jdbc:mysql://db1.example.com:3306/app_db?sslModeVERIFY_CAtrustCertificateKeyStoreUrlfile:/etc/ssl/ca-truststore.jkstrustCertificateKeyStorePasswordchangeit这里最大的坑是信任库。Java 不认 .pem 格式的 CA你得先把 CA 导入到 JKSJava KeyStore里。导入命令keytool -import -alias mysql-ca -file ca.pem \ -keystore ca-truststore.jks -storepass changeit很多 Java 应用 SSL 连接报错报错信息里出现 “unable to find valid certification path to requested target” 或者 “certificate_verify_failed”十有八九就是这个 JKS 信任库没配对或者根本没导 CA。另外提醒一句verifyServerCertificatetrue 在 5.x 驱动里默认是 false光写 useSSLtrue 不校验 CA 等于白搭起不到防中间人的作用。4.3 PythonPyMySQL / mysql-connector-pythonPython 生态里 PyMySQL 用得最多SSL 配置直接通过 ssl 参数字典传进去import pymysql conn pymysql.connect( hostdb1.example.com, userapp_user, passwordxxxx, databaseapp_db, ssl{ ca: /etc/mysql/ssl/ca.pem, verify_cert: True, }, )PyMySQL 里 verify_cert 对应的是 VERIFY_CA 语义如果想做主机名校验还需要传 ssl_verify_identityTrue。注意PyMySQL 的 ssl_verify_identity 在 1.0 版本之后才支持完整校验逻辑老版本要么升级要么就只做 CA 校验。如果用官方 mysql-connector-python参数更直观import mysql.connector conn mysql.connector.connect( hostdb1.example.com, userapp_user, passwordxxxx, databaseapp_db, ssl_ca/etc/mysql/ssl/ca.pem, ssl_verify_certTrue, )4.4 MySQL Workbench 图形化配置图形化工具 MySQL Workbench 在配置连接时点击 Advanced 或者 SSL 标签页选择 Use SSL然后填 CA 文件路径。如果是双向认证还要填客户端证书和客户端私钥的路径。填完之后点 Test Connection能通过就说明证书链路没问题。Workbench 还带一个很实用的功能在 Server Status 面板里可以直接看到 SSL 状态包括当前连接的加密套件。我平时排查问题时经常用 Workbench 快速验证一批开发机的 SSL 是否真的生效比在命令行一个个敲 SHOW STATUS 快得多。5. 强制加密不加密就不让连5.1 账号级别 REQUIRE SSL / REQUIRE X509配置好 SSL 之后服务端并不会强制所有连接都走加密。默认情况下客户端可以选择不加密连接服务器照样放行。要让加密真正落地必须显式地把账号设成强制 SSL。用管理员账号登录创建新账号时直接带 REQUIRE SSLCREATE USER app_user% IDENTIFIED BY xxxx REQUIRE SSL;如果是已有账号用 ALTER 改一下ALTER USER app_user% REQUIRE SSL;改完之后这个账号再用明文方式连接会直接报错。想更严格一点用双向认证的模式ALTER USER app_user% REQUIRE X509;REQUIRE X509 要求客户端不仅要用 SSL还必须携带一张由受信任 CA 签发的客户端证书。这种方式适合对安全极度敏感的场景比如支付类、核心用户数据类应用。缺点也很明显每台应用服务器都要单独发证书、维护证书有效期运维成本高不少。5.2 全局级别 require_secure_transport账号级别一个个改账号多了容易漏。MySQL 8.0.8 开始支持全局参数 require_secure_transport设为 ON 之后就变成“所有账号、所有连接都必须走加密或本地 socket”任何明文连接都会被拒绝[mysqld] require_secure_transportON老版本5.7 及更早没有这个全局开关只能在账号维度逐个 REQUIRE SSL或者通过触发器/定时任务定期扫描 mysql.user 表把没有 REQUIRE SSL 的账号批量修复。5.7 用户如果嫌麻烦也可以在应用侧入口统一做限制比如所有客户端都配置成 VERIFY_CA从源头杜绝明文连接。5.3 怎么验证强制加密真的生效了配置完强制加密一定要实际验证一次。最直接的办法用一个不传 SSL 参数的命令行客户端去连接看是否被拒绝mysql --ssl-modeDISABLED -h db1.example.com -u app_user -p如果服务端强制生效这里会提示连接被拒或者在认证阶段直接报 SSL 相关错误。反过来用正常的 VERIFY_CA 方式连一次确认能正常登录。这两步都通过说明开关是真正落地了而不是只改了个配置没生效。6. 常见报错与排查实录6.1 高频错误速查表配置过程中我把最常见的报错整理成一张表建议收藏。逐条对着查比在黑板上瞎试快得多错误信息可能原因排查方向ERROR 2026 (HY000): SSL connection error握手失败多为证书路径错误、权限问题或协议不兼容检查错误日志和 ssl 变量[08001] SSL connection required, but not provided by server客户端要求 SSL 但服务器没启用 SSL或证书加载失败看服务端 have_ssl 是否为 YESSSL certificate problem: unable to get local issuer certificate客户端不信任服务器 CA或 CA 文件没传对检查 --ssl-ca / truststore 配置certificate_verify_failedCA 校验失败通常是 CA 不匹配或证书链不完整确认客户端用的是同一套 CA 签发的证书no required SSL certificate was sent服务器要求 X509 客户端证书但客户端没带证书客户端配置 client-cert / client-keyThe server does not support SSL / 服务器不支持SSL服务端 SSL 功能没开检查服务器证书加载状态ssl recv 错误 / protocol version mismatchTLS 版本协商失败客户端或服务器协议版本太老统一 tls_version 配置Java: unable to find valid certification pathJKS 信任库没导入 CA或 CA 路径不对用 keytool 重新导入6.2 三个真实踩坑案例先说第一个主机名校验失败的坑。我给一台测试机配好了 SSL客户端用 VERIFY_CA 连接没问题但一升级到 VERIFY_IDENTITY 就报证书校验失败。查了半天才发现服务器证书的 CN 写的是 db1.example.com但测试时客户端连的是 IP 10.0.0.10主机名完全对不上。解决办法是把 IP 加到证书的 SAN 扩展里重新签发或者让客户端统一用域名连接。这个坑在跨网段、内网 IP 直连的场景下非常容易出现提前把 SAN 列全能省很多事。第二个坑是 Java 应用的信任库。当时一个 Java 服务连库报 unable to find valid certification path我以为是网络问题后来发现是连接串里少了 trustCertificateKeyStoreUrl 参数导致 JVM 拿着默认的 JDK 信任库去校验我自己的 CA当然过不了。把自定义 CA 导入到一个独立 JKS并在连接串里显式指定问题立刻解决。第三个坑是 Docker 部署的证书权限。同事把证书放到宿主机目录容器里把证书目录挂载进去后MySQL 一直启动失败日志里显示 key 文件无法读取。原因很直接挂载进来的文件属主是宿主机用户容器内的 mysql 用户没有读权限。把证书文件的属主改成 UID 999容器内 mysql 用户的常见 UID或者直接 chmod 到容器能读的权限容器才能正常启动。这个问题排查起来很隐蔽因为文件看起来一直在那个路径下实际上权限根本不够。6.3 用抓包和 openssl 命令验证是否真的加密了前面这些是“配置层面”的验证最后再说一个“真实流量层面”的验证手段。用 tcpdump 抓一下 3306 端口的包tcpdump -ni any tcp port 3306 -A如果你能看到 SQL 的关键字比如 SELECT、UPDATE、WHERE 后面跟着表名和字段值说明这条链路还是明文SSL 配置一定有问题。如果看到的内容全是乱码一样的二进制数据而且有 Application Data 这种 TLS 记录特征说明加密已经生效。另外较新版本的 OpenSSL 支持直接对 MySQL 做 STARTTLS 探测openssl s_client -starttls mysql -connect db1.example.com:3306 -brief如果服务器 SSL 正常这条命令会输出协商出来的 TLS 版本和加密套件。这个方法适合在没有 mysql 客户端的机器上快速探测服务器是否开启了加密端口非常简单粗暴。7. 运维向证书轮换、安全扫描与长期维护7.1 证书过期检测与平滑轮换证书有有效期过期前不处理客户端会陆续开始报错。很多生产事故都是这么来的凌晨证书到期第二天业务方集体反馈连不上库。所以证书的到期监控必须提前做好。Linux 下查看证书的过期时间很简单openssl x509 -in /etc/mysql/ssl/server-cert.pem -noout -dates可以把这个命令挂到定时任务里每周检查一次到期前 30 天发告警。轮换时新证书签发之后MySQL 8.0.21 以上版本支持在线重载 TLS 配置不用重启实例ALTER INSTANCE RELOAD TLS;这个命令会重新读取 ssl_ca、ssl_cert、ssl_key 指向的证书文件非常适合做无缝轮换。老版本没有这个功能只能在低峰期重启 MySQL如果集群有主从或者代理可以逐个节点重启避免业务中断。7.2 TLS 版本与弱加密套件加固配置完 SSL 只是第一步安全扫描常常还会盯上 TLS 配置本身。比如刚才提到的 CVE-2016-2183本质是 TLS 里对 3DES、RC4 这类弱加密算法的支持导致的。MySQL 里可以通过 tls_version 和 ssl_cipher 两个参数来限制[mysqld] tls_versionTLSv1.2,TLSv1.3 ssl_cipherTLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256tls_version 控制协议版本ssl_cipher 控制加密套件白名单。只保留 GCM 类的 AEAD 套件把 CBC、RC4、3DES 全部排除在外安全扫描基本就能干净很多。注意 ssl_cipher 的参数格式是冒号分隔不同版本支持的套件名称略有差异配置完后一定要用 SHOW VARIABLES LIKE ssl_cipher 确认实际生效情况。7.3 监控 SSL 连接的使用比例最后说监控。运维层面除了看证书过期还要关注“到底有多少连接在走 SSL”。用下面这条 SQL 查所有非本地 socket 连接里哪些没走 SSLSELECT PROCESSLIST_ID, USER, HOST, DB, COMMAND FROM performance_schema.threads WHERE NAME LIKE %client% AND PROCESSLIST_ID IS NOT NULL;更直观的方式是统计服务器端 Ssl 状态变量SHOW STATUS LIKE Ssl_%;重点看 Ssl_accepts 和 Ssl_client_connects 的数量变化。如果 Ssl_accepts 增长但业务量没增长或者某些账号的 Ssl_cipher 一直是空说明有客户端还在走明文。把这些信息接到监控系统里做一个“明文连接占比”的告警一旦有非 SSL 连接出现立即感知比事后排查要省心得多。最后一个实操建议上线 SSL 前一定要先在测试环境把整套证书生成、服务端配置、客户端连接、强制加密完整走一遍并且把证书备份放到另外一个安全位置。我见过太多人生产环境直接开干结果证书路径写错导致服务起不来或者忘记在客户端信任库里导入 CA 导致大面积连不上。这套流程本身不复杂难就难在细节多、链路长按这个顺序一步步来基本不会翻车。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门