给 Redis 加 TLS,配起来不难,难的是知道每个开关到底挡住了什么、代价多大。这篇用 openssl 自己生成一套测试 CA 和证书,在本机跑通明文口 + TLS 口并存,把证书校验、双向认证、握手与往返代价、以及把证书 CN 映射到 ACL 用户都量一遍。
ca.crt subject=CN=Test Root CA | issuer=CN=Test Root CA server.crt subject=CN=redis-server | issuer=CN=Test Root CA client.crt subject=CN=svc-app | issuer=CN=Test Root CA rogue.crt subject=CN=svc-app | issuer=CN=Rogue CA server.crt SAN: DNS:localhost, IP Address:127.0.0.1
全部是本机 openssl 现生成的测试证书(EC P-256,30 天):一个 CA,一张 SAN 为 localhost 和 127.0.0.1 的服务端证书,一张 CN=svc-app 的客户端证书,以及另一个 CA(rogue)签的同名 CN 证书,用来验证「CN 相同但 CA 不受信任」的情形。
明文 redis-cli 连 TLS 口 → Error: Connection reset by peer --tls 但没给 --cacert → Could not connect to Redis at 127.0.0.1:9152: SSL_connect failed: certificate verify failed --tls --cacert ca.crt → PONG --tls --cacert(不相关的 CA) → Could not connect to Redis at 127.0.0.1:9152: SSL_connect failed: certificate verify failed --tls --insecure(不校验服务端证书) → PONG 明文 redis-cli 连明文口 → PONG 用原始套接字向 TLS 口发明文 PING: ConnectionResetError (没有任何回复,或连接被关)
TLS 口拒绝明文协议(连接被重置)。客户端不给 CA、给了不相关的 CA,都是 certificate verify failed。--insecure 能连上,但客户端什么都不校验——它防不了中间人,只在测试里用。
tls-auth-clients = yes yes:不带客户端证书 → Error: Server closed the connection yes:带受信任 CA 签的客户端证书 → PONG yes:带不受信任 CA 签的证书(CN 也叫 svc-app) → Error: Server closed the connection optional:不带证书 → PONG optional:带不受信任的证书 → Error: Server closed the connection openssl verify -CAfile ca.crt rogue.crt → CN=svc-app / error 20 at 0 depth lookup: unable to get local issuer certificate / error rogue.crt: verification failed
yes 要求每个客户端出示受信任 CA 签发的证书,没有或不受信任都在握手阶段被拒(服务端直接关闭连接,客户端看到 Server closed the connection)。optional 允许不出示,但出示了就必须可信。注意 rogue 证书的 CN 与合法证书相同也没用,信任来自签发者。
协议 TLSv1.3 套件 TLS_AES_256_GCM_SHA384 server_hostname=wrong.example → [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch, certificate is not valid for 'w 客户端只允许 TLSv1_2 → TLSv1.2 客户端只允许 TLSv1_3 → TLSv1.3 服务端 tls-protocols 配置: ['tls-protocols']
握手协商到 TLS 1.3。本文没有测试强制服务端只允许旧版本(tls-protocols 保持默认为空)。主机名校验是客户端的事:证书里没有的名字握手就失败。
新建连接 + 1 次 PING(300 次):明文 中位 0.14 ms / p99 0.35 ms;TLS 中位 0.86 ms / p99 1.09 ms 已建立连接上的 PING 往返(20000 次):明文 中位 24 µs / p99 42 µs;TLS 中位 26 µs / p99 41 µs redis-benchmark(-c 50 -n 200000,无流水线)明文: SET: 113507 req/s p50=0.287 ms | GET: 117096 req/s p50=0.279 ms redis-benchmark 同参数 TLS: SET: 94429 req/s p50=0.327 ms | GET: 96899 req/s p50=0.319 ms
建连的代价明显(握手),已建立连接上单次往返的差别很小,吞吐下降的幅度见上面的 redis-benchmark 数字。都是本机回环、单次运行的数字,不是通用结论;真实网络里延迟主要来自往返本身。
CONFIG GET tls-auth-clients-user → tls-auth-clients-user off
映射关闭(off)时,带 CN=svc-app 的证书,ACL WHOAMI → default
CONFIG SET tls-auth-clients-user CN → OK
映射 CN 时,带 CN=svc-app 的证书,ACL WHOAMI → NOPERM User svc-app has no permissions to run the 'acl|whoami' command
同一证书的连接 SET app:1(用户有 +set ~app:*) → OK
同一证书的连接 SET other(没有权限的 key) → NOPERM No permissions to access a key
映射 CN 时,带 CN=nobody 的证书(没有这个用户) → default
带证书但又显式 AUTH default(此处 default 是 nopass) → default / Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
INFO stats: ['acl_access_denied_auth:0', 'acl_access_denied_tls_cert:1']8.10.2 里有 tls-auth-clients-user(默认 off):设成 CN 后,客户端证书的 CN 直接作为登录的 ACL 用户名,不用再 AUTH。关键的坑:CN 对应的用户不存在时,连接没有被拒绝,而是当作 default 用户继续用——只有把 default 用户关掉或设了密码,这里才安全(INFO 里的 acl_access_denied_tls_cert 会计数)。