为阿里云 RDS MySQL 开启 SSL,并使用自签证书

最近发现阿里云 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 开始将会移除这个选项,出于长远考虑我不会这么做。

x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0 · Issue #775 · rancher/rke2 · GitHub

引用

Configure Grafana | Grafana documentation

Cluster Datastore | K3s

certificates – Provide subjectAltName to openssl directly on the command line – Information Security Stack Exchange

How do I use SANs with openSSL instead of common name? – Stack Overflow

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

目录