为 MySQL 客户端开启 TLS

本文主要描述了在 Kubernetes 上如何为 TiDB 集群的 MySQL 客户端开启 TLS。TiDB Operator 从 v1.1 开始已经支持为 Kubernetes 上 TiDB 集群开启 MySQL 客户端 TLS。开启步骤为:

  1. 为 TiDB Server 颁发一套 Server 端证书,为 MySQL Client 颁发一套 Client 端证书。并创建两个 Secret 对象,Secret 名字分别为:${cluster_name}-tidb-server-secret${cluster_name}-tidb-client-secret,分别包含前面创建的两套证书;

    注意:

    创建的 Secret 对象必须符合上述命名规范,否则将导致 TiDB 集群部署失败。

  2. 部署集群,设置 .spec.tidb.tlsClient.enabled 属性为 true

  3. 配置 MySQL 客户端使用加密连接。

其中,颁发证书的方式有多种,本文档提供两种方式,用户也可以根据需要为 TiDB 集群颁发证书,这两种方式分别为:

  • 使用 cfssl 系统颁发证书;
  • 使用 cert-manager 系统颁发证书;

当需要更新已有 TLS 证书时,可参考更新和替换 TLS 证书

第一步:为 TiDB 集群颁发两套证书

使用 cfssl 系统颁发证书

  1. 首先下载 cfssl 软件并初始化证书颁发机构:

    1. mkdir -p ~/bin
    2. curl -s -L -o ~/bin/cfssl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
    3. curl -s -L -o ~/bin/cfssljson https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
    4. chmod +x ~/bin/{cfssl,cfssljson}
    5. export PATH=$PATH:~/bin
    6. mkdir -p cfssl
    7. cd cfssl
    8. cfssl print-defaults config > ca-config.json
    9. cfssl print-defaults csr > ca-csr.json
  2. ca-config.json 配置文件中配置 CA 选项:

    1. {
    2. "signing": {
    3. "default": {
    4. "expiry": "8760h"
    5. },
    6. "profiles": {
    7. "server": {
    8. "expiry": "8760h",
    9. "usages": [
    10. "signing",
    11. "key encipherment",
    12. "server auth"
    13. ]
    14. },
    15. "client": {
    16. "expiry": "8760h",
    17. "usages": [
    18. "signing",
    19. "key encipherment",
    20. "client auth"
    21. ]
    22. }
    23. }
    24. }
    25. }
  3. 您还可以修改 ca-csr.json 证书签名请求 (CSR):

    1. {
    2. "CN": "TiDB Server",
    3. "CA": {
    4. "expiry": "87600h"
    5. },
    6. "key": {
    7. "algo": "rsa",
    8. "size": 2048
    9. },
    10. "names": [
    11. {
    12. "C": "US",
    13. "L": "CA",
    14. "O": "PingCAP",
    15. "ST": "Beijing",
    16. "OU": "TiDB"
    17. }
    18. ]
    19. }
  4. 使用定义的选项生成 CA:

    1. cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
  5. 生成 Server 端证书。

    首先生成默认的 server.json 文件:

    1. cfssl print-defaults csr > server.json

    然后编辑这个文件,修改 CNhosts 属性:

    1. ...
    2. "CN": "TiDB Server",
    3. "hosts": [
    4. "127.0.0.1",
    5. "::1",
    6. "${cluster_name}-tidb",
    7. "${cluster_name}-tidb.${namespace}",
    8. "${cluster_name}-tidb.${namespace}.svc",
    9. "*.${cluster_name}-tidb",
    10. "*.${cluster_name}-tidb.${namespace}",
    11. "*.${cluster_name}-tidb.${namespace}.svc",
    12. "*.${cluster_name}-tidb-peer",
    13. "*.${cluster_name}-tidb-peer.${namespace}",
    14. "*.${cluster_name}-tidb-peer.${namespace}.svc"
    15. ],
    16. ...

    其中 ${cluster_name} 为集群的名字,${namespace} 为 TiDB 集群部署的命名空间,用户也可以添加自定义 hosts

    最后生成 Server 端证书:

    1. cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server.json | cfssljson -bare server
  6. 生成 Client 端证书。

    首先生成默认的 client.json 文件:

    1. cfssl print-defaults csr > client.json

    然后编辑这个文件,修改 CNhosts 属性,hosts 可以留空:

    1. ...
    2. "CN": "TiDB Client",
    3. "hosts": [],
    4. ...

    最后生成 Client 端证书:

    1. cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client
  7. 创建 Kubernetes Secret 对象。

    到这里假设你已经按照上述文档把两套证书都创建好了。通过下面的命令为 TiDB 集群创建 Secret 对象:

    1. kubectl create secret generic ${cluster_name}-tidb-server-secret --namespace=${namespace} --from-file=tls.crt=server.pem --from-file=tls.key=server-key.pem --from-file=ca.crt=ca.pem
    2. kubectl create secret generic ${cluster_name}-tidb-client-secret --namespace=${namespace} --from-file=tls.crt=client.pem --from-file=tls.key=client-key.pem --from-file=ca.crt=ca.pem

    这样就给 Server/Client 端证书分别创建了:

    • 一个 Secret 供 TiDB Server 启动时加载使用;
    • 另一个 Secret 供 MySQL 客户端连接 TiDB 集群时候使用。

用户可以生成多套 Client 端证书,并且至少要生成一套 Client 证书供 TiDB Operator 内部组件访问 TiDB Server(目前有 TidbInitializer 会访问 TiDB Server 来设置密码或者一些初始化操作)。

使用 cert-manager 颁发证书

  1. 安装 cert-manager。

    请参考官网安装:cert-manager installation in Kubernetes

  2. 创建一个 Issuer 用于给 TiDB 集群颁发证书。

    为了配置 cert-manager 颁发证书,必须先创建 Issuer 资源。

    首先创建一个目录保存 cert-manager 创建证书所需文件:

    1. mkdir -p cert-manager
    2. cd cert-manager

    然后创建一个 tidb-server-issuer.yaml 文件,输入以下内容:

    1. apiVersion: cert-manager.io/v1alpha2
    2. kind: Issuer
    3. metadata:
    4. name: ${cluster_name}-selfsigned-ca-issuer
    5. namespace: ${namespace}
    6. spec:
    7. selfSigned: {}
    8. ---
    9. apiVersion: cert-manager.io/v1alpha2
    10. kind: Certificate
    11. metadata:
    12. name: ${cluster_name}-ca
    13. namespace: ${namespace}
    14. spec:
    15. secretName: ${cluster_name}-ca-secret
    16. commonName: "TiDB CA"
    17. isCA: true
    18. duration: 87600h # 10yrs
    19. renewBefore: 720h # 30d
    20. issuerRef:
    21. name: ${cluster_name}-selfsigned-ca-issuer
    22. kind: Issuer
    23. ---
    24. apiVersion: cert-manager.io/v1alpha2
    25. kind: Issuer
    26. metadata:
    27. name: ${cluster_name}-tidb-issuer
    28. namespace: ${namespace}
    29. spec:
    30. ca:
    31. secretName: ${cluster_name}-ca-secret

    上面的文件创建三个对象:

    • 一个 SelfSigned 类型的 Issuer 对象(用于生成 CA 类型 Issuer 所需要的 CA 证书);
    • 一个 Certificate 对象,isCa 属性设置为 true
    • 一个可以用于颁发 TiDB Server TLS 证书的 Issuer。

      最后执行下面的命令进行创建:

      1. kubectl apply -f tidb-server-issuer.yaml
  3. 创建 Server 端证书。

    cert-manager 中,Certificate 资源表示证书接口,该证书将由上面创建的 Issuer 颁发并保持更新。

    首先来创建 Server 端证书,创建一个 tidb-server-cert.yaml 文件,并输入以下内容:

    1. apiVersion: cert-manager.io/v1alpha2
    2. kind: Certificate
    3. metadata:
    4. name: ${cluster_name}-tidb-server-secret
    5. namespace: ${namespace}
    6. spec:
    7. secretName: ${cluster_name}-tidb-server-secret
    8. duration: 8760h # 365d
    9. renewBefore: 360h # 15d
    10. organization:
    11. - PingCAP
    12. commonName: "TiDB Server"
    13. usages:
    14. - server auth
    15. dnsNames:
    16. - "${cluster_name}-tidb"
    17. - "${cluster_name}-tidb.${namespace}"
    18. - "${cluster_name}-tidb.${namespace}.svc"
    19. - "*.${cluster_name}-tidb"
    20. - "*.${cluster_name}-tidb.${namespace}"
    21. - "*.${cluster_name}-tidb.${namespace}.svc"
    22. - "*.${cluster_name}-tidb-peer"
    23. - "*.${cluster_name}-tidb-peer.${namespace}"
    24. - "*.${cluster_name}-tidb-peer.${namespace}.svc"
    25. ipAddresses:
    26. - 127.0.0.1
    27. - ::1
    28. issuerRef:
    29. name: ${cluster_name}-tidb-issuer
    30. kind: Issuer
    31. group: cert-manager.io

    其中 ${cluster_name} 为集群的名字:

    • spec.secretName 请设置为 ${cluster_name}-tidb-server-secret
    • usages 请添加上 server auth
    • dnsNames 需要填写这 6 个 DNS,根据需要可以填写其他 DNS:
      • ${cluster_name}-tidb
      • ${cluster_name}-tidb.${namespace}
      • ${cluster_name}-tidb.${namespace}.svc
      • *.${cluster_name}-tidb
      • *.${cluster_name}-tidb.${namespace}
      • *.${cluster_name}-tidb.${namespace}.svc
      • *.${cluster_name}-tidb-peer
      • *.${cluster_name}-tidb-peer.${namespace}
      • *.${cluster_name}-tidb-peer.${namespace}.svc
    • ipAddresses 需要填写这两个 IP ,根据需要可以填写其他 IP:
      • 127.0.0.1
      • ::1
    • issuerRef 请填写上面创建的 Issuer;
    • 其他属性请参考 cert-manager API

      通过执行下面的命令来创建证书:

      1. kubectl apply -f tidb-server-cert.yaml

      创建这个对象以后,cert-manager 会生成一个名字为 ${cluster_name}-tidb-server-secret 的 Secret 对象供 TiDB Server 使用。

  4. 创建 Client 端证书。

    创建一个 tidb-client-cert.yaml 文件,并输入以下内容:

    1. apiVersion: cert-manager.io/v1alpha2
    2. kind: Certificate
    3. metadata:
    4. name: ${cluster_name}-tidb-client-secret
    5. namespace: ${namespace}
    6. spec:
    7. secretName: ${cluster_name}-tidb-client-secret
    8. duration: 8760h # 365d
    9. renewBefore: 360h # 15d
    10. organization:
    11. - PingCAP
    12. commonName: "TiDB Client"
    13. usages:
    14. - client auth
    15. issuerRef:
    16. name: ${cluster_name}-tidb-issuer
    17. kind: Issuer
    18. group: cert-manager.io

    其中 ${cluster_name} 为集群的名字:

    • spec.secretName 请设置为 ${cluster_name}-tidb-client-secret
    • usages 请添加上 client auth
    • dnsNamesipAddresses 不需要填写;
    • issuerRef 请填写上面创建的 Issuer;
    • 其他属性请参考 cert-manager API

      通过执行下面的命令来创建证书:

      1. kubectl apply -f tidb-client-cert.yaml

      创建这个对象以后,cert-manager 会生成一个名字为 ${cluster_name}-tidb-client-secret 的 Secret 对象供 TiDB Client 使用。

  5. 创建多套 Client 端证书(可选)。

    TiDB Operator 集群内部有 4 个组件需要请求 TiDB Server,当开启 TLS 验证后,这些组件可以使用证书来请求 TiDB Server,每个组件都可以使用单独的证书。这些组件有:

    1. 创建一个 tidb-components-client-cert.yaml 文件,并输入以下内容:

      1. apiVersion: cert-manager.io/v1alpha2
      2. kind: Certificate
      3. metadata:
      4. name: ${cluster_name}-tidb-initializer-client-secret
      5. namespace: ${namespace}
      6. spec:
      7. secretName: ${cluster_name}-tidb-initializer-client-secret
      8. duration: 8760h # 365d
      9. renewBefore: 360h # 15d
      10. organization:
      11. - PingCAP
      12. commonName: "TiDB Initializer client"
      13. usages:
      14. - client auth
      15. issuerRef:
      16. name: ${cluster_name}-tidb-issuer
      17. kind: Issuer
      18. group: cert-manager.io
      19. ---
      20. apiVersion: cert-manager.io/v1alpha2
      21. kind: Certificate
      22. metadata:
      23. name: ${cluster_name}-pd-dashboard-client-secret
      24. namespace: ${namespace}
      25. spec:
      26. secretName: ${cluster_name}-pd-dashboard-client-secret
      27. duration: 8760h # 365d
      28. renewBefore: 360h # 15d
      29. organization:
      30. - PingCAP
      31. commonName: "PD Dashboard client"
      32. usages:
      33. - client auth
      34. issuerRef:
      35. name: ${cluster_name}-tidb-issuer
      36. kind: Issuer
      37. group: cert-manager.io
      38. ---
      39. apiVersion: cert-manager.io/v1alpha2
      40. kind: Certificate
      41. metadata:
      42. name: ${cluster_name}-backup-client-secret
      43. namespace: ${namespace}
      44. spec:
      45. secretName: ${cluster_name}-backup-client-secret
      46. duration: 8760h # 365d
      47. renewBefore: 360h # 15d
      48. organization:
      49. - PingCAP
      50. commonName: "Backup client"
      51. usages:
      52. - client auth
      53. issuerRef:
      54. name: ${cluster_name}-tidb-issuer
      55. kind: Issuer
      56. group: cert-manager.io
      57. ---
      58. apiVersion: cert-manager.io/v1alpha2
      59. kind: Certificate
      60. metadata:
      61. name: ${cluster_name}-restore-client-secret
      62. namespace: ${namespace}
      63. spec:
      64. secretName: ${cluster_name}-restore-client-secret
      65. duration: 8760h # 365d
      66. renewBefore: 360h # 15d
      67. organization:
      68. - PingCAP
      69. commonName: "Restore client"
      70. usages:
      71. - client auth
      72. issuerRef:
      73. name: ${cluster_name}-tidb-issuer
      74. kind: Issuer
      75. group: cert-manager.io

      其中 ${cluster_name} 为集群的名字:

      • spec.secretName 请设置为 ${cluster_name}-${component}-client-secret
      • usages 请添加上 client auth
      • dnsNamesipAddresses 不需要填写;
      • issuerRef 请填写上面创建的 Issuer;
      • 其他属性请参考 cert-manager API

        如需要为 TiDB Lignting 组件生成 Client 端证书,则可以使用以下内容并通过在 TiDB Lightning 的 Helm Chart values.yaml 中设置 tlsCluster.tlsClientSecretName${cluster_name}-lightning-client-secret

        1. apiVersion: cert-manager.io/v1alpha2
        2. kind: Certificate
        3. metadata:
        4. name: ${cluster_name}-lightning-client-secret
        5. namespace: ${namespace}
        6. spec:
        7. secretName: ${cluster_name}-lightning-client-secret
        8. duration: 8760h # 365d
        9. renewBefore: 360h # 15d
        10. organization:
        11. - PingCAP
        12. commonName: "Lightning client"
        13. usages:
        14. - client auth
        15. issuerRef:
        16. name: ${cluster_name}-tidb-issuer
        17. kind: Issuer
        18. group: cert-manager.io
    2. 通过执行下面的命令来创建证书:

      1. kubectl apply -f tidb-components-client-cert.yaml
    3. 创建这些对象以后,cert-manager 会生成 4 个 Secret 对象供上面四个组件使用。

      注意:

      TiDB Server 的 TLS 兼容 MySQL 协议。当证书内容发生改变后,需要管理员手动执行 SQL 语句 alter instance reload tls 进行刷新。

第二步:部署 TiDB 集群

接下来将会创建一个 TiDB 集群,并且执行以下步骤:

  • 开启 MySQL 客户端 TLS;
  • 对集群进行初始化(这里创建了一个数据库 app);
  • 创建一个 Backup 对象对集群进行备份;
  • 创建一个 Restore 对象对进群进行恢复;
  • TidbInitializer,PD Dashboard,Backup 以及 Restore 分别使用单独的 Client 证书(用 tlsClientSecretName 指定)。
  1. 创建三个 .yaml 文件:

    • tidb-cluster.yaml:

      1. apiVersion: pingcap.com/v1alpha1
      2. kind: TidbCluster
      3. metadata:
      4. name: ${cluster_name}
      5. namespace: ${namespace}
      6. spec:
      7. version: v5.2.1
      8. timezone: UTC
      9. pvReclaimPolicy: Retain
      10. pd:
      11. baseImage: pingcap/pd
      12. replicas: 1
      13. requests:
      14. storage: "1Gi"
      15. config: {}
      16. tlsClientSecretName: ${cluster_name}-pd-dashboard-client-secret
      17. tikv:
      18. baseImage: pingcap/tikv
      19. replicas: 1
      20. requests:
      21. storage: "1Gi"
      22. config: {}
      23. tidb:
      24. baseImage: pingcap/tidb
      25. replicas: 1
      26. service:
      27. type: ClusterIP
      28. config: {}
      29. tlsClient:
      30. enabled: true
      31. ---
      32. apiVersion: pingcap.com/v1alpha1
      33. kind: TidbInitializer
      34. metadata:
      35. name: ${cluster_name}-init
      36. namespace: ${namespace}
      37. spec:
      38. image: tnir/mysqlclient
      39. cluster:
      40. namespace: ${namespace}
      41. name: ${cluster_name}
      42. initSql: |-
      43. create database app;
      44. tlsClientSecretName: ${cluster_name}-tidb-initializer-client-secret
    • backup.yaml:

      1. apiVersion: pingcap.com/v1alpha1
      2. kind: Backup
      3. metadata:
      4. name: ${cluster_name}-backup
      5. namespace: ${namespace}
      6. spec:
      7. backupType: full
      8. br:
      9. cluster: ${cluster_name}
      10. clusterNamespace: ${namespace}
      11. sendCredToTikv: true
      12. from:
      13. host: ${host}
      14. secretName: ${tidb_secret}
      15. port: 4000
      16. user: root
      17. tlsClientSecretName: ${cluster_name}-backup-client-secret
      18. s3:
      19. provider: aws
      20. region: ${my_region}
      21. secretName: ${s3_secret}
      22. bucket: ${my_bucket}
      23. prefix: ${my_folder}
    • restore.yaml:

      1. apiVersion: pingcap.com/v1alpha1
      2. kind: Restore
      3. metadata:
      4. name: ${cluster_name}-restore
      5. namespace: ${namespace}
      6. spec:
      7. backupType: full
      8. br:
      9. cluster: ${cluster_name}
      10. clusterNamespace: ${namespace}
      11. sendCredToTikv: true
      12. to:
      13. host: ${host}
      14. secretName: ${tidb_secret}
      15. port: 4000
      16. user: root
      17. tlsClientSecretName: ${cluster_name}-restore-client-secret
      18. s3:
      19. provider: aws
      20. region: ${my_region}
      21. secretName: ${s3_secret}
      22. bucket: ${my_bucket}
      23. prefix: ${my_folder}

      其中 ${cluster_name} 为集群的名字,${namespace} 为 TiDB 集群部署的命名空间。通过设置 spec.tidb.tlsClient.enabled 属性为 true 来开启 MySQL 客户端 TLS。

  2. 部署 TiDB 集群:

    1. kubectl apply -f tidb-cluster.yaml
  3. 集群备份:

    1. kubectl apply -f backup.yaml
  4. 集群恢复:

    1. kubectl apply -f restore.yaml

第三步:配置 MySQL 客户端使用加密连接

可以根据官网文档提示,使用上面创建的 Client 证书,通过下面的方法连接 TiDB 集群:

获取 Client 证书的方式并连接 TiDB Server 的方法是:

  1. kubectl get secret -n ${namespace} ${cluster_name}-tidb-client-secret -ojsonpath='{.data.tls\.crt}' | base64 --decode > client-tls.crt
  2. kubectl get secret -n ${namespace} ${cluster_name}-tidb-client-secret -ojsonpath='{.data.tls\.key}' | base64 --decode > client-tls.key
  3. kubectl get secret -n ${namespace} ${cluster_name}-tidb-client-secret -ojsonpath='{.data.ca\.crt}' | base64 --decode > client-ca.crt
  1. mysql -uroot -p -P 4000 -h ${tidb_host} --ssl-cert=client-tls.crt --ssl-key=client-tls.key --ssl-ca=client-ca.crt

注意:

MySQL 8.0 默认认证插件mysql_native_password 更新为 caching_sha2_password,因此如果使用 MySQL 8.0 客户端访问 TiDB 服务(TiDB 版本 < v4.0.7),并且用户账户有配置密码,需要显示指定 --default-auth=mysql_native_password 参数。

最后请参考 官网文档 来验证是否正确开启了 TLS。