证书 API 支持
[X.509]
的自动化配置,
它为 Kubernetes API 的客户端提供一个编程接口,
用于从证书颁发机构(CA)请求并获取 X.509
。
CertificateSigningRequest(CSR)资源用来向指定的签名者申请证书签名,
在最终签名之前,申请可能被批准,也可能被拒绝。
CertificateSigningRequest 资源类型允许客户使用它申请发放 X.509 证书。
CertificateSigningRequest 对象 在 spec.request
中包含一个 PEM 编码的 PKCS#10 签名请求。
CertificateSigningRequest 使用 spec.signerName
字段标示签名者(请求的接收方)。
注意,spec.signerName
在 certificates.k8s.io/v1
之后的 API 版本是必填项。
在 Kubernetes v1.22 和以后的版本,客户可以可选地设置 spec.expirationSeconds
字段来为颁发的证书设定一个特定的有效期。该字段的最小有效值是 600
,也就是 10 分钟。
创建完成的 CertificateSigningRequest,要先通过批准,然后才能签名。
根据所选的签名者,CertificateSigningRequest 可能会被
自动批准。
否则,就必须人工批准,
人工批准可以使用 REST API(或 go 客户端),也可以执行 kubectl certificate approve
命令。
同样,CertificateSigningRequest 也可能被驳回,
这就相当于通知了指定的签名者,这个证书不能签名。
对于已批准的证书,下一步是签名。
对应的签名控制器首先验证签名条件是否满足,然后才创建证书。
签名控制器然后更新 CertificateSigningRequest,
将新证书保存到现有 CertificateSigningRequest 对象的 status.certificate
字段中。
此时,字段 status.certificate
要么为空,要么包含一个用 PEM 编码的 X.509 证书。
直到签名完成前,CertificateSigningRequest 的字段 status.certificate
都为空。
一旦 status.certificate
字段完成填充,请求既算完成,
客户端现在可以从 CertificateSigningRequest 资源中获取已签名的证书的 PEM 数据。
当然如果不满足签名条件,签名者可以拒签。
为了减少集群中遗留的过时的 CertificateSigningRequest 资源的数量,
一个垃圾收集控制器将会周期性地运行。
此垃圾收集器会清除在一段时间内没有改变过状态的 CertificateSigningRequests:
也可以指定自定义 signerName。
所有签名者都应该提供自己工作方式的信息,
以便客户端可以预期到他们的 CSR 将发生什么。
此类信息包括:
spec.expirationSeconds
字段指定等,spec.expirationSeconds
字段不同时的应对手段。一般来说,当 CSR 被批准通过,且证书被签名后,status.certificate
字段
将包含一个 PEM 编码的 X.509 证书。
有些签名者在 status.certificate
字段中存储多个证书。
在这种情况下,签名者的说明文档应当指明附加证书的含义。
例如,这是要在 TLS 握手时提供的证书和中继证书。
PKCS#10 签名请求格式并没有一种标准的方法去设置证书的过期时间或者生命期。
因此,证书的过期时间或者生命期必须通过 CSR 对象的 spec.expirationSeconds
字段来设置。
当 spec.expirationSeconds
没有被指定时,内置的签名者默认使用 ClusterSigningDuration
配置选项
(kube-controller-manager 的命令行选项 --cluster-signing-duration
),该选项的默认值设为 1 年。
当 spec.expirationSeconds
被指定时,spec.expirationSeconds
和 ClusterSigningDuration
中的最小值会被使用。
spec.expirationSeconds
字段是在 Kubernetes v1.22 中加入的。早期的 Kubernetes 版本并不认识该字段。
v1.22 版本之前的 Kubernetes API 服务器会在创建对象的时候忽略该字段。
Kubernetes 提供了内置的签名者,每个签名者都有一个众所周知的 signerName
:
kubernetes.io/kube-apiserver-client
:签名的证书将被 API 服务器视为客户证书。system:masters
的 CertificateSubjectRestriction 准入插件默认处于启用状态,["client auth"]
,但不能包含["digital signature", "key encipherment", "client auth"]
之外的键。--cluster-signing-duration
选项和 CSR 对象的 spec.expirationSeconds
字段(如有设置该字段)中的最小值。kubernetes.io/kube-apiserver-client-kubelet
: 签名的证书将被 kube-apiserver 视为客户证书。
可以自动批准它。
["system:nodes"]
,用户名以 “system:node:
“ 开头["key encipherment", "digital signature", "client auth"]
--cluster-signing-duration
选项和 CSR 对象的 spec.expirationSeconds
字段(如有设置该字段)中的最小值。kubernetes.io/kubelet-serving
: 签名服务证书,该服务证书被 API 服务器视为有效的 kubelet 服务证书,["system:nodes"]
,用户名以 “system:node:
“ 开头["key encipherment", "digital signature", "server auth"]
--cluster-signing-duration
选项和 CSR 对象的 spec.expirationSeconds
字段(如有设置该字段)中的最小值。kubernetes.io/legacy-unknown
: 不保证信任。Kubernetes 的一些第三方发行版可能会使用它签署的客户端证书。certificates.k8s.io/v1
以及之后的版本)不允许将signerName
设置为 kubernetes.io/legacy-unknown
。--cluster-signing-duration
选项和 CSR 对象的 spec.expirationSeconds
字段(如有设置该字段)中的最小值。注意:所有这些故障仅在 kube-controller-manager 日志中报告。
spec.expirationSeconds
字段是在 Kubernetes v1.22 中加入的。早期的 Kubernetes 版本并不认识该字段。
v1.22 版本之前的 Kubernetes API 服务器会在创建对象的时候忽略该字段。
对于这些签名者,信任的分发发生在带外(out of band)。上述信任之外的任何信任都是完全巧合的。
例如,一些发行版可能会将 kubernetes.io/legacy-unknown
作为 kube-apiserver 的客户端证书,
但这个做法并不标准。
这些用途都没有以任何方式涉及到 ServiceAccount 中的 Secrets .data[ca.crt]
。
此 CA 证书包只保证使用默认的服务(kubernetes.default.svc
)来验证到 API 服务器的连接。
授权创建 CertificateSigningRequest 和检索 CertificateSigningRequest:
create
、get
、list
、watch
,certificates.k8s.io
,certificatesigningrequests
例如:
授权批准 CertificateSigningRequest:
get
、list
、watch
,certificates.k8s.io
,certificatesigningrequests
update
,certificates.k8s.io
,certificatesigningrequests/approval
approve
,certificates.k8s.io
,signers
,<signerNameDomain>/<signerNamePath>
或 <signerNameDomain>/*
例如:
授权签名 CertificateSigningRequest:
get
、list
、watch
,certificates.k8s.io
,certificatesigningrequests
update
,certificates.k8s.io
,certificatesigningrequests/status
sign
,certificates.k8s.io
,signers
,<signerNameDomain>/<signerNamePath>
或 <signerNameDomain>/*
为了让普通用户能够通过认证并调用 API,需要执行几个步骤。
首先,该用户必须拥有 Kubernetes 集群签发的证书,
然后将该证书提供给 Kubernetes API。
下面的脚本展示了如何生成 PKI 私钥和 CSR。
设置 CSR 的 CN 和 O 属性很重要。CN 是用户名,O 是该用户归属的组。
你可以参考 [RBAC] 了解标准组的信息。
openssl genrsa -out myuser.key 2048
openssl req -new -key myuser.key -out myuser.csr
创建一个 CertificateSigningRequest,并通过 kubectl 将其提交到 Kubernetes 集群。
下面是生成 CertificateSigningRequest 的脚本。
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: myuser
spec:
request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ1ZqQ0NBVDRDQVFBd0VURVBNQTBHQTFVRUF3d0dZVzVuWld4aE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRgpBQU9DQVE4QU1JSUJDZ0tDQVFFQTByczhJTHRHdTYxakx2dHhWTTJSVlRWMDNHWlJTWWw0dWluVWo4RElaWjBOCnR2MUZtRVFSd3VoaUZsOFEzcWl0Qm0wMUFSMkNJVXBGd2ZzSjZ4MXF3ckJzVkhZbGlBNVhwRVpZM3ExcGswSDQKM3Z3aGJlK1o2MVNrVHF5SVBYUUwrTWM5T1Nsbm0xb0R2N0NtSkZNMUlMRVI3QTVGZnZKOEdFRjJ6dHBoaUlFMwpub1dtdHNZb3JuT2wzc2lHQ2ZGZzR4Zmd4eW8ybmlneFNVekl1bXNnVm9PM2ttT0x1RVF6cXpkakJ3TFJXbWlECklmMXBMWnoyalVnald4UkhCM1gyWnVVV1d1T09PZnpXM01LaE8ybHEvZi9DdS8wYk83c0x0MCt3U2ZMSU91TFcKcW90blZtRmxMMytqTy82WDNDKzBERHk5aUtwbXJjVDBnWGZLemE1dHJRSURBUUFCb0FBd0RRWUpLb1pJaHZjTgpBUUVMQlFBRGdnRUJBR05WdmVIOGR4ZzNvK21VeVRkbmFjVmQ1N24zSkExdnZEU1JWREkyQTZ1eXN3ZFp1L1BVCkkwZXpZWFV0RVNnSk1IRmQycVVNMjNuNVJsSXJ3R0xuUXFISUh5VStWWHhsdnZsRnpNOVpEWllSTmU3QlJvYXgKQVlEdUI5STZXT3FYbkFvczFqRmxNUG5NbFpqdU5kSGxpT1BjTU1oNndLaTZzZFhpVStHYTJ2RUVLY01jSVUyRgpvU2djUWdMYTk0aEpacGk3ZnNMdm1OQUxoT045UHdNMGM1dVJVejV4T0dGMUtCbWRSeEgvbUNOS2JKYjFRQm1HCkkwYitEUEdaTktXTU0xMzhIQXdoV0tkNjVoVHdYOWl4V3ZHMkh4TG1WQzg0L1BHT0tWQW9FNkpsYWFHdTlQVmkKdjlOSjVaZlZrcXdCd0hKbzZXdk9xVlA3SVFjZmg3d0drWm89Ci0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo=
signerName: kubernetes.io/kube-apiserver-client
expirationSeconds: 86400 # one day
usages:
- client auth
EOF
需要注意的几点:
usage
字段必须是 ‘client auth
‘expirationSeconds
可以设置为更长(例如 864000
是十天)或者更短(例如 3600
是一个小时)request
字段是 CSR 文件内容的 base64 编码值。
要得到该值,可以执行命令
cat myuser.csr | base64 | tr -d "\n"
使用 kubectl 创建 CSR 并批准。
获取 CSR 列表:
kubectl get csr
批准 CSR:
kubectl certificate approve myuser
从 CSR 取得证书:
kubectl get csr/myuser -o yaml
证书的内容使用 base64 编码,存放在字段 status.certificate
。
从 CertificateSigningRequest 导出颁发的证书。
kubectl get csr myuser -o jsonpath='{.status.certificate}'| base64 -d > myuser.crt
创建了证书之后,为了让这个用户能访问 Kubernetes 集群资源,现在就要创建
Role 和 RoleBinding 了。
下面是为这个新用户创建 Role 的示例命令:
kubectl create role developer --verb=create --verb=get --verb=list --verb=update --verb=delete --resource=pods
下面是为这个新用户创建 RoleBinding 的示例命令:
kubectl create rolebinding developer-binding-myuser --role=developer --user=myuser
最后一步是将这个用户添加到 kubeconfig 文件。
首先,你需要添加新的凭据:
kubectl config set-credentials myuser --client-key=myuser.key --client-certificate=myuser.crt --embed-certs=true
然后,你需要添加上下文:
kubectl config set-context myuser --cluster=kubernetes --user=myuser
来测试一下,把上下文切换为 myuser
:
kubectl config use-context myuser
kube-controller-manager 内建了一个证书批准者,其 signerName 为kubernetes.io/kube-apiserver-client-kubelet
,
该批准者将 CSR 上用于节点凭据的各种权限委托给权威认证机构。
kube-controller-manager 将 SubjectAccessReview 资源发送(POST)到 API 服务器,
以便检验批准证书的授权。
kubectl
批准或驳回Kubernetes 管理员(拥有足够的权限)可以手工批准(或驳回)CertificateSigningRequests,
此操作使用 kubectl certificate approve
和 kubectl certificate deny
命令实现。
使用 kubectl 批准一个 CSR:
kubectl certificate approve <certificate-signing-request-name>
同样地,驳回一个 CSR:
kubectl certificate deny <certificate-signing-request-name>
REST API 的用户可以通过向待批准的 CSR 的 approval
子资源提交更新请求来批准 CSR。
例如,你可以编写一个
来监视特定类型的 CSR,然后发送一个更新来批准它。
当你发出批准或驳回的指令时,根据你期望的状态来选择设置 Approved
或 Denied
。
批准(Approved
) 的 CSR:
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
...
status:
conditions:
- lastUpdateTime: "2020-02-08T11:37:35Z"
lastTransitionTime: "2020-02-08T11:37:35Z"
message: Approved by my custom approver controller
reason: ApprovedByMyPolicy # You can set this to any string
type: Approved
驳回(Denied
)的 CSR:
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
...
status:
conditions:
- lastUpdateTime: "2020-02-08T11:37:35Z"
lastTransitionTime: "2020-02-08T11:37:35Z"
message: Denied by my custom approver controller
reason: DeniedByMyPolicy # You can set this to any string
type: Denied
status.conditions.reason
字段通常设置为一个首字母大写的对机器友好的原因码;
这是一个命名约定,但你也可以随你的个人喜好设置。
如果你想添加一个供人类使用的注释,那就用 status.conditions.message
字段。
Kubernetes 控制平面实现了每一个
[Kubernetes 签名者],
每个签名者的实现都是 kube-controller-manager 的一部分。
在 Kubernetes v1.18 之前,
kube-controller-manager 签名所有标记为 approved 的 CSR。
spec.expirationSeconds
字段是在 Kubernetes v1.22 中加入的。早期的 Kubernetes 版本并不认识该字段。
v1.22 版本之前的 Kubernetes API 服务器会在创建对象的时候忽略该字段。
REST API 的用户可以通过向待签名的 CSR 的 status
子资源提交更新请求来对 CSR 进行签名。
作为这个请求的一部分,status.certificate
字段应设置为已签名的证书。
此字段可包含一个或多个 PEM 编码的证书。
所有的 PEM 块必须具备 “CERTIFICATE” 标签,且不包含文件头,且编码的数据必须是
[RFC5280 第 4 节]
中描述的 BER 编码的 ASN.1 证书结构。
-----BEGIN CERTIFICATE-----
MIIDgjCCAmqgAwIBAgIUC1N1EJ4Qnsd322BhDPRwmg3b/oAwDQYJKoZIhvcNAQEL
BQAwXDELMAkGA1UEBhMCeHgxCjAIBgNVBAgMAXgxCjAIBgNVBAcMAXgxCjAIBgNV
BAoMAXgxCjAIBgNVBAsMAXgxCzAJBgNVBAMMAmNhMRAwDgYJKoZIhvcNAQkBFgF4
MB4XDTIwMDcwNjIyMDcwMFoXDTI1MDcwNTIyMDcwMFowNzEVMBMGA1UEChMMc3lz
dGVtOm5vZGVzMR4wHAYDVQQDExVzeXN0ZW06bm9kZToxMjcuMC4wLjEwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDne5X2eQ1JcLZkKvhzCR4Hxl9+ZmU3
+e1zfOywLdoQxrPi+o4hVsUH3q0y52BMa7u1yehHDRSaq9u62cmi5ekgXhXHzGmm
kmW5n0itRECv3SFsSm2DSghRKf0mm6iTYHWDHzUXKdm9lPPWoSOxoR5oqOsm3JEh
Q7Et13wrvTJqBMJo1GTwQuF+HYOku0NF/DLqbZIcpI08yQKyrBgYz2uO51/oNp8a
sTCsV4OUfyHhx2BBLUo4g4SptHFySTBwlpRWBnSjZPOhmN74JcpTLB4J5f4iEeA7
2QytZfADckG4wVkhH3C2EJUmRtFIBVirwDn39GXkSGlnvnMgF3uLZ6zNAgMBAAGj
YTBfMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDAjAMBgNVHRMB
Af8EAjAAMB0GA1UdDgQWBBTREl2hW54lkQBDeVCcd2f2VSlB1DALBgNVHREEBDAC
ggAwDQYJKoZIhvcNAQELBQADggEBABpZjuIKTq8pCaX8dMEGPWtAykgLsTcD2jYr
L0/TCrqmuaaliUa42jQTt2OVsVP/L8ofFunj/KjpQU0bvKJPLMRKtmxbhXuQCQi1
qCRkp8o93mHvEz3mTUN+D1cfQ2fpsBENLnpS0F4G/JyY2Vrh19/X8+mImMEK5eOy
o0BMby7byUj98WmcUvNCiXbC6F45QTmkwEhMqWns0JZQY+/XeDhEcg+lJvz9Eyo2
aGgPsye1o3DpyXnyfJWAWMhOz7cikS5X2adesbgI86PhEHBXPIJ1v13ZdfCExmdd
M1fLPhLyR54fGaY+7/X8P9AZzPefAkwizeXwe9ii6/a08vWoiE4=
-----END CERTIFICATE-----
非 PEM 内容可能会出现在证书 PEM 块前后的位置,且未经验证,
以允许使用 [RFC7468 第 5.2 节]中描述的解释性文本。
当使用 JSON 或 YAML 格式时,此字段是 base-64 编码。
包含上述示例证书的 CertificateSigningRequest 如下所示:
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
...
status:
certificate: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JS..."