在集群中管理 TLS 证书
Kubernetes 提供了一个 certificates.k8s.io
API,允许你提供由你控制的证书颁发机构 (CA) 签名的 TLS 证书。你的工作负载可以使用这些 CA 和证书来建立信任。
certificates.k8s.io
API 使用的协议类似于 ACME 草案。
注意
使用certificates.k8s.io
API 创建的证书由专用的 CA 签名。可以将你的集群配置为使用集群根 CA 来实现此目的,但你绝不应该依赖此方式。不要假设这些证书会针对集群根 CA 进行验证。开始之前
你需要拥有一个 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 证书列表中。例如,在使用 golang TLS 配置时,你可以解析证书链,并将解析后的证书添加到 tls.Config
结构体中的 RootCAs
字段。
注意
尽管自定义 CA 证书可能包含在文件系统中(在 ConfigMap kube-root-ca.crt
中),但除了用于验证内部 Kubernetes 端点之外,你不应将该证书颁发机构用于任何其他目的。内部 Kubernetes 端点的一个例子是默认命名空间中名为 kubernetes
的 Service。
如果你想为工作负载使用自定义证书颁发机构,则应单独生成该 CA,并使用 Pod 有权读取的 ConfigMap 分发其 CA 证书。
请求证书
以下部分演示了如何为通过 DNS 访问的 Kubernetes Service 创建 TLS 证书。
注意
本教程使用 CFSSL:Cloudflare 的 PKI 和 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 的 Cluster 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
此命令会生成两个文件:server.csr
文件包含 PEM 编码的 PKCS#10 认证请求,server-key.pem
文件包含尚待创建的证书的 PEM 编码密钥。
创建 CertificateSigningRequest 对象以发送到 Kubernetes API
生成 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
。有关更多信息,请查看支持的签名者名称的文档。
CSR 现在应该可以在 API 中看到,处于 Pending 状态。你可以通过运行以下命令查看它:
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: yourname@example.com
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 审批
证书签名请求 (certificate signing request) 的审批可以通过自动化审批过程或由集群管理员一次性手动完成。如果你有权审批证书请求,可以使用 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 yourname@example.com <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 对象的 status 中填充已签名的证书:
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 -
注意
这使用了命令行工具jq
来填充 .status.certificate
字段中的 base64 编码内容。如果你没有 jq
,也可以将 JSON 输出保存到文件,手动填充此字段,然后上传生成的文件。CSR 获批并上传已签名的证书后,运行:
kubectl get csr
输出类似于:
NAME AGE SIGNERNAME REQUESTOR REQUESTEDDURATION CONDITION
my-svc.my-namespace 20m example.com/serving yourname@example.com <none> Approved,Issued
下载并使用证书
现在,作为请求用户,你可以通过运行以下命令下载已颁发的证书并将其保存到 server.crt
文件中:
kubectl get csr my-svc.my-namespace -o jsonpath='{.status.certificate}' \
| base64 --decode > server.crt
现在,你可以将 server.crt
和 server-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 approve
和 kubectl certificate deny
命令手动批准(或拒绝)CertificateSigningRequests。但是,如果你打算大量使用此 API,可以考虑编写一个自动化的证书控制器。
警告
审批 CSR 的能力决定了在你的环境中谁信任谁。不应广泛或轻易授予审批 CSR 的能力。
在授予 approve
权限之前,你应该确保清楚地理解审批者应承担的验证要求以及颁发特定证书的后果。
无论是机器还是使用 kubectl 的人,**审批者**的角色是验证 CSR 是否满足两个要求:
- CSR 的主体控制用于签署 CSR 的私钥。这解决了第三方伪装成授权主体的威胁。在上述示例中,这一步就是验证 Pod 控制用于生成 CSR 的私钥。
- CSR 的主体被授权在请求的上下文中操作。这解决了不期望的主体加入集群的威胁。在上述示例中,这一步就是验证 Pod 是否允许参与请求的 Service。
当且仅当满足这两个要求时,审批者才应批准 CSR,否则应拒绝 CSR。
有关证书审批和访问控制的更多信息,请阅读证书签名请求参考页面。
配置集群以提供签名功能
本页假设已设置签名者来提供证书 API 服务。Kubernetes 控制器管理器提供了一个签名器的默认实现。要启用它,请向控制器管理器传递 --cluster-signing-cert-file
和 --cluster-signing-key-file
参数,指定证书颁发机构密钥对的路径。