最近发现阿里云 99 计划的数据库,竟然包含香港地区,于是又冲动消费了。整了一个给 K3s 玩,试试它的 HA 模式。不过现有的节点很多都不在阿里云内网,需要开放数据库的公网访问才可以。开放数据库公网访问必然要开启 SSL 了,不然数据直接在公网上面明文传输十分危险。
问题
一开始我直接在阿里云 RDS 控制台上启用 SSL,使用默认的云端证书,然后先用 Grafana 去尝试连接,发现无法成功校验证书,报错:
Error: ✗ failed to connect to database: tls: failed to verify certificate: x509: certificate relies on legacy Common Name field, use SANs instead
猜测默认的云端证书只有在 Common Name 字段带上了数据库连接地址,而 Go 1.15 之后需要从 Subject Alternative Names 中进行校验,综合考虑后决定使用自签证书。
自签证书
平时 OpenSSL 用得不多,于是直接使唤 AI 干活:
openssl genpkey -algorithm RSA -out ca-key.pem
openssl req -new -x509 -key ca-key.pem -out ca-cert.pem -days 3650 -subj "/CN=MySQL CA"
openssl genpkey -algorithm RSA -out server-key.pem
openssl req -new -key server-key.pem -out server-req.pem -subj "/CN=rm-foo.mysql.rds.aliyuncs.com"
openssl x509 -req -in server-req.pem -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 3650 -extfile <(printf "subjectAltName=DNS:rm-foo.mysql.rds.aliyuncs.com")
然后在 RDS 控制台上传生成的服务端证书就可以了。
在 Grafana 上配置
我们需要先将上面生成的 CA 证书先挂载到 Grafana 容器中,然后参考官方文档进行配置,使其可以经由 TLS 连接数据库。
我的 Grafana 是使用官方的 Helm Chart 部署的,其提供了一个可以注入 ConfigMap 的配置项,因此我们先创建一个包含了 CA 的 ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp: null
name: grafana-database-tls-ca
namespace: monitoring
data:
certificates.crt: |
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
然后在 values.yaml 中配置挂载它:
extraConfigmapMounts:
- name: database-tls-ca
mountPath: /etc/grafana/ssl/
subPath: certificates.crt
configMap: grafana-database-tls-ca
readOnly: true
这样的话,CA 就已经被挂载到 Grafana 容器中了。接下来我们需要配置 Grafana 以开启 TLS,同样,这里也是在 values.yaml 中配置就行。
grafana.ini:
database:
type: mysql
host: rm-foo.mysql.rds.aliyuncs.com
ssl_mode: true
ssl_sni: rm-foo.mysql.rds.aliyuncs.com
server_cert_name: rm-foo.mysql.rds.aliyuncs.com
ca_cert_path: /etc/grafana/ssl
user: $__file{/etc/secrets/grafana-database/username}
password: $__file{/etc/secrets/grafana-database/password}
如果配置没有问题的话,此时执行更新之后应该就可以了。
在 K3s 上配置
同样,官方文档给出了配置项。我们需要先把刚才的 CA 证书复制到 K3s server 服务器上,然后修改启动脚本。
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--flannel-backend=wireguard-native --disable=servicelb --disable=traefik --disable=metrics-server" \
sh -s - server \
--datastore-endpoint="mysql://username:password@tcp(host:3306)/database" \
--datastore-cafile="/usr/local/share/ca-certificates/mysql.crt" \
--flannel-external-ip \
--node-ip 1.2.3.4 \
--kubelet-arg=node-ip=0.0.0.0
因为配置变化,安装脚本会更新配置并重启 K3s。如果配置没有问题,K3s 会正常启动。
后记
强制指定用户使用加密连接
如果全部的工作负载已经迁移至 TLS 连接,那么我们就可以配置数据库特定用户必须使用加密连接。
ALTER USER k3s@'%' REQUIRE SSL;
FLUSH PRIVILEGES;
配置生效之后,我们可以重启工作负载进行验证。
为什么不通过一些 workaround 来使用默认的云端证书
为了行文的连贯性,我把这段放到了这里。你可能会想到几个问题:
Q:为什么不直接 skip-verify?省得折腾半天。
A:不安全,这不是我的风格。而且根据 K3s 文档,其至今仍然暂时无法跳过证书检查。虽然 K3s 的数据库读写垫片 kine 项目已经有人实现了 skip-verify 选项并且已经合入主干+发布,但是当下等待 K3s 支持还早,感兴趣的可以看下面的链接。
k3s can’t utilize tls option with mysql external database · Issue #1093 · k3s-io/k3s · GitHub
Add support for TLS skip verification by tuxillo · Pull Request #306 · k3s-io/kine · GitHub
Q:为什么不使用 GODEBUG=x509ignoreCN=0
环境变量使其可以校验 CN 而不是 SANs?
A:Go 从 1.15 开始校验 SANs 而不是 CN,从 1.17 开始将会移除这个选项,出于长远考虑我不会这么做。
引用
Configure Grafana | Grafana documentation
How do I use SANs with openSSL instead of common name? – Stack Overflow
发表回复