管理集群中的 TLS 证书

Kubernetes 提供了一个 certificates.k8s.io API,它允许你配置由你控制的证书颁发机构 (CA) 签署的 TLS 证书。你的工作负载可以使用这些 CA 和证书来建立信任。

certificates.k8s.io API 使用的协议类似于 ACME 草案

准备开始

你需要有一个 Kubernetes 集群,并且必须将 kubectl 命令行工具配置为与你的集群通信。建议在至少有两个节点不充当控制平面主机的集群上运行本教程。如果你还没有集群,可以使用 minikube 创建一个集群,或者可以使用以下 Kubernetes 游乐场之一

你需要 cfssl 工具。你可以从 https://github.com/cloudflare/cfssl/releases 下载 cfssl

此页面中的某些步骤使用 jq 工具。如果你没有 jq,可以通过操作系统的软件源安装它,或者从 https://jqlang.github.io/jq/ 获取它。

信任集群中的 TLS

要信任来自作为 Pod 运行的应用程序的自定义 CA,通常需要一些额外的应用程序配置。你需要将 CA 证书包添加到 TLS 客户端或服务器信任的 CA 证书列表中。例如,你可以通过解析证书链并将解析的证书添加到 tls.Config 结构中的 RootCAs 字段来使用 golang TLS 配置执行此操作。

请求证书

以下部分演示如何为通过 DNS 访问的 Kubernetes Service 创建 TLS 证书。

创建证书签名请求

运行以下命令生成私钥和证书签名请求(或 CSR)

cat <<EOF | cfssl genkey - | cfssljson -bare server
{
  "hosts": [
    "my-svc.my-namespace.svc.cluster.local",
    "my-pod.my-namespace.pod.cluster.local",
    "192.0.2.24",
    "10.0.34.2"
  ],
  "CN": "my-pod.my-namespace.pod.cluster.local",
  "key": {
    "algo": "ecdsa",
    "size": 256
  }
}
EOF

其中 192.0.2.24 是 Service 的集群 IP,my-svc.my-namespace.svc.cluster.local 是 Service 的 DNS 名称,10.0.34.2 是 Pod 的 IP,my-pod.my-namespace.pod.cluster.local 是 Pod 的 DNS 名称。你应该会看到类似于以下内容的输出

2022/02/01 11:45:32 [INFO] generate received request
2022/02/01 11:45:32 [INFO] received CSR
2022/02/01 11:45:32 [INFO] generating key: ecdsa-256
2022/02/01 11:45:32 [INFO] encoded CSR

此命令生成两个文件;它生成包含 PEM 编码的 PKCS#10 证书请求的 server.csr,以及包含 PEM 编码的密钥的 server-key.pem,该密钥用于尚未创建的证书。

创建要发送到 Kubernetes API 的 CertificateSigningRequest 对象

生成 CSR 清单(YAML 格式),并将其发送到 API 服务器。你可以通过运行以下命令来完成此操作

cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: my-svc.my-namespace
spec:
  request: $(cat server.csr | base64 | tr -d '\n')
  signerName: example.com/serving
  usages:
  - digital signature
  - key encipherment
  - server auth
EOF

请注意,在步骤 1 中创建的 server.csr 文件经过 base64 编码并存储在 .spec.request 字段中。你还请求了一个具有“数字签名”、“密钥加密”和“服务器身份验证”密钥用途的证书,该证书由示例 example.com/serving 签名者签名。必须请求特定的 signerName。有关更多信息,请查看支持的签名者名称 的文档。

现在,应该可以在 API 中以“待处理”状态查看 CSR。你可以通过运行以下命令来查看它

kubectl describe csr my-svc.my-namespace
Name:                   my-svc.my-namespace
Labels:                 <none>
Annotations:            <none>
CreationTimestamp:      Tue, 01 Feb 2022 11:49:15 -0500
Requesting User:        [email protected]
Signer:                 example.com/serving
Status:                 Pending
Subject:
        Common Name:    my-pod.my-namespace.pod.cluster.local
        Serial Number:
Subject Alternative Names:
        DNS Names:      my-pod.my-namespace.pod.cluster.local
                        my-svc.my-namespace.svc.cluster.local
        IP Addresses:   192.0.2.24
                        10.0.34.2
Events: <none>

批准 CertificateSigningRequest

批准 证书签名请求 可以通过自动批准流程完成,也可以由集群管理员一次性完成。如果你有权批准证书请求,则可以使用 kubectl 手动执行此操作;例如

kubectl certificate approve my-svc.my-namespace
certificatesigningrequest.certificates.k8s.io/my-svc.my-namespace approved

你现在应该会看到以下内容

kubectl get csr
NAME                  AGE   SIGNERNAME            REQUESTOR              REQUESTEDDURATION   CONDITION
my-svc.my-namespace   10m   example.com/serving   [email protected]   <none>              Approved

这意味着证书请求已获批准,并且正在等待请求的签名者对其进行签名。

签署 CertificateSigningRequest

接下来,你将扮演证书签名者的角色,颁发证书,并将其上传到 API。

签名者通常会监视 CertificateSigningRequest API 中是否有带有其 signerName 的对象,检查它们是否已获批准,为这些请求签署证书,并使用颁发的证书更新 API 对象状态。

创建证书颁发机构

你需要一个颁发机构来为新证书提供数字签名。

首先,通过运行以下命令创建签名证书

cat <<EOF | cfssl gencert -initca - | cfssljson -bare ca
{
  "CN": "My Example Signer",
  "key": {
    "algo": "rsa",
    "size": 2048
  }
}
EOF

你应该会看到类似于以下内容的输出

2022/02/01 11:50:39 [INFO] generating a new CA key and certificate from CSR
2022/02/01 11:50:39 [INFO] generate received request
2022/02/01 11:50:39 [INFO] received CSR
2022/02/01 11:50:39 [INFO] generating key: rsa-2048
2022/02/01 11:50:39 [INFO] encoded CSR
2022/02/01 11:50:39 [INFO] signed certificate with serial number 263983151013686720899716354349605500797834580472

这将生成证书颁发机构密钥文件 (ca-key.pem) 和证书 (ca.pem)。

颁发证书

{
    "signing": {
        "default": {
            "usages": [
                "digital signature",
                "key encipherment",
                "server auth"
            ],
            "expiry": "876000h",
            "ca_constraint": {
                "is_ca": false
            }
        }
    }
}

使用 server-signing-config.json 签名配置以及证书颁发机构密钥文件和证书对证书请求进行签名

kubectl get csr my-svc.my-namespace -o jsonpath='{.spec.request}' | \
  base64 --decode | \
  cfssl sign -ca ca.pem -ca-key ca-key.pem -config server-signing-config.json - | \
  cfssljson -bare ca-signed-server

你应该会看到类似于以下内容的输出

2022/02/01 11:52:26 [INFO] signed certificate with serial number 576048928624926584381415936700914530534472870337

这将生成一个已签名的服务证书文件 ca-signed-server.pem

上传已签名的证书

最后,在 API 对象的状态中填充已签名的证书

kubectl get csr my-svc.my-namespace -o json | \
  jq '.status.certificate = "'$(base64 ca-signed-server.pem | tr -d '\n')'"' | \
  kubectl replace --raw /apis/certificates.k8s.io/v1/certificatesigningrequests/my-svc.my-namespace/status -f -

批准 CSR 并上传已签名的证书后,运行

kubectl get csr

输出类似于以下内容

NAME                  AGE   SIGNERNAME            REQUESTOR              REQUESTEDDURATION   CONDITION
my-svc.my-namespace   20m   example.com/serving   [email protected]   <none>              Approved,Issued

下载证书并使用它

现在,作为请求用户,你可以下载颁发的证书并将其保存到 server.crt 文件中,方法是运行以下命令

kubectl get csr my-svc.my-namespace -o jsonpath='{.status.certificate}' \
    | base64 --decode > server.crt

现在,你可以将 server.crtserver-key.pem 填充到 Secret 中,稍后可以将其挂载到 Pod 中(例如,与提供 HTTPS 的 Web 服务器一起使用)。

kubectl create secret tls server --cert server.crt --key server-key.pem
secret/server created

最后,你可以将 ca.pem 填充到 ConfigMap 中,并将其用作信任根来验证服务证书

kubectl create configmap example-serving-ca --from-file ca.crt=ca.pem
configmap/example-serving-ca created

批准 CertificateSigningRequests

Kubernetes 管理员(具有适当权限)可以使用 kubectl certificate approvekubectl certificate deny 命令手动批准(或拒绝)CertificateSigningRequests。但是,如果你打算大量使用此 API,则可以考虑编写自动证书控制器。

无论是使用 kubectl 的机器还是人类(如上所述),*审批人* 的作用都是验证 CSR 是否满足以下两个要求

  1. CSR 的使用者控制用于签署 CSR 的私钥。这解决了第三方冒充授权使用者的威胁。在上面的示例中,此步骤将验证 Pod 是否控制用于生成 CSR 的私钥。
  2. CSR 的标的被授权在请求的上下文中执行操作。这解决了不希望的标的加入集群的威胁。在上面的例子中,这一步将验证 Pod 是否被允许参与请求的服务。

当且仅当满足这两个要求时,批准者才应批准 CSR,否则应拒绝 CSR。

有关证书批准和访问控制的更多信息,请阅读证书签名请求参考页面。

配置您的集群以提供签名

本页假设已设置签名者以服务于证书 API。Kubernetes 控制器管理器提供了一个签名者的默认实现。要启用它,请将 --cluster-signing-cert-file--cluster-signing-key-file 参数传递给控制器管理器,并使用证书颁发机构密钥对的路径。

上次修改时间:2023 年 10 月 2 日晚上 11:58 PST:更新 jq 链接 (e822502654)