概述

通过 CA 双向认证方式因为需要基于根 CA 证书、根 CA 私钥进行证书签发,一般需要在集群的 master 节点上进行配置,用户不一定有权限进入到 master 节点中。但是一般情况都能拿到对应的一个 kubeconfig 文件用来访问 kubernetes集群,基于 kubeconfig 文件我们也可以进行证书的批准和授权,这就是 kubernetes CSR 机制,通过使用 CSR 方式签发客户端证书。

CertificateSigningRequest(CSR)机制

CertificateSigningRequest(CSR)资源用来向指定的签名者申请证书签名, 在最终签名之前,申请可能被批准,也可能被拒绝。

请求签名流程

CertificateSigningRequest 资源类型允许客户端基于签名请求申请发放 X.509 证书。 CertificateSigningRequest 对象在 spec.request 字段中包含一个 PEM 编码的 PKCS#10 签名请求。 CertificateSigningRequest 使用 spec.signerName 字段标示签名者(请求的接收方)。 注意,spec.signerNamecertificates.k8s.io/v1 之后的 API 版本是必填项。 在 Kubernetes v1.22 和以后的版本,客户可以设置 spec.expirationSeconds 字段(可选)来为颁发的证书设定一个特定的有效期。该字段的最小有效值是 600,也就是 10 分钟。

创建完成的 CertificateSigningRequest,要先通过批准,然后才能签名。 根据所选的签名者,CertificateSigningRequest 可能会被控制器自动批准。 否则,就必须人工批准, 人工批准可以使用 REST API(或 client-go),也可以执行 kubectl certificate approve 命令。 同样,CertificateSigningRequest 也可能被驳回, 这就相当于通知了指定的签名者,这个证书不能签名。

对于已批准的证书,下一步是签名。 对应的签名控制器首先验证签名条件是否满足,然后才创建证书。 签名控制器然后更新 CertificateSigningRequest, 将新证书保存到现有 CertificateSigningRequest 对象的 status.certificate 字段中。 此时,字段 status.certificate 要么为空,要么包含一个用 PEM 编码的 X.509 证书。 直到签名完成前,CertificateSigningRequest 的字段 status.certificate 都为空。

一旦 status.certificate 字段完成填充,请求既算完成, 客户端现在可以从 CertificateSigningRequest 资源中获取已签名的证书的 PEM 数据。 当然如果不满足签名条件,签名者可以拒签。

为了减少集群中遗留的过时的 CertificateSigningRequest 资源的数量, 一个垃圾收集控制器将会周期性地运行。 此垃圾收集器会清除在一段时间内没有改变过状态的 CertificateSigningRequest:

  • 已批准的请求:1 小时后自动删除
  • 已拒绝的请求:1 小时后自动删除
  • 已失败的请求:1 小时后自动删除
  • 挂起的请求:24 小时后自动删除
  • 所有请求:在颁发的证书过期后自动删除

签名者

Kubernetes 提供了内置的签名者,每个签名者都有一个众所周知的 signerName

  1. kubernetes.io/kube-apiserver-client:签名的证书将被 API 服务器视为客户端证书, kube-controller-manager 不会自动批准它。
  2. kubernetes.io/kube-apiserver-client-kubelet:签名的证书将被 kube-apiserver 视为客户端证书。 kube-controller-manager 可以自动批准它。
  3. kubernetes.io/kubelet-serving:签名服务端证书,该服务证书被 API 服务器视为有效的 kubelet 服务端证书, 但没有其他保证。kube-controller-manager 不会自动批准它。
  4. kubernetes.io/legacy-unknown:不保证信任。Kubernetes 的一些第三方发行版可能会使用它签署的客户端证书。 稳定版的 CertificateSigningRequest API(certificates.k8s.io/v1 以及之后的版本)不允许将 signerName 设置为 kubernetes.io/legacy-unknownkube-controller-manager 不会自动批准这类请求。

kube-controller-manager 为每个内置签名者实现了控制平面签名。 注意:所有这些故障仅在 kube-controller-manager 日志中报告。

创建用户并授权

接下来以在系统上创建一个 dev 用户,和在 kubernetes 上创建一个 dev 用户为例子,示例说明在如何通过创建用户和通过 CSR 签发客户端证书,并使用证书来访问 kubernetes。

系统上创建 dev 用户

在系统上创建 dev 用户,并创建 .kube 目录:

useradd -d /home/dev dev
mkdir /home/dev/.kube

kubernetes 创建 ServiceAccount 用户

通过 ServiceAccount 创建 dev 用户:

kubectl create serviceaccount dev -n kube-system

kubernetes 对 ServiceAccount 进行 RBAC 授权

使用 RBAC 创建规则,使 dev 用户可以访问资源。比如这里所以 admin 权限 default 这个 namespace:

kubectl create rolebinding dev-admin-binding --clusterrole=admin --user=dev --namespace=default

创建 CertificateSigningRequest

生成客户端私钥

生成 dev 用户的私钥

openssl genrsa -out dev.key 2048

生成证书请求文件

基于用户私钥生成用户证书签名请求文件 dev.csr,其中 CN 代表 kubernetes 用户。

openssl req -new -key dev.key -subj "/CN=dev" -out dev.csr

获取 CSR 文件内容

获取 CSR 文件内容的 base64 编码值。 要得到该值。

cat dev.csr | base64 | tr -d "\n"

创建一个 CertificateSigningRequest, 并通过 kubectl 将其提交到 Kubernetes 集群。 下面是生成 CertificateSigningRequest 的脚本。

cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: dev
spec:
  request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ1V6Q0NBVHNDQVFBd0RqRU1NQW9HQTFVRUF3d0RaR1YyTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQwpBUThBTUlJQkNnS0NBUUVBekozcDdtS1o2cXV2ZEtBZ05RT0xxdFJnVlBjVExhckdMZUVhbTduUmZ0eWN1OG9DClJxbWpxZmJCc3VWUHdPOURzdHNGOEdSbWg3Y2RoOGFLTlVlSngwK1k0aWlFNW8xUEFESnphRmU4REczOUY4U2kKdTZlWklTRzBWdkZLYUlGdisvbzJWd2ZDTGdTbG9LNk9ENWJCazgwOS9oRC9GWER3cXBGQk4yanVwMk5wNVdTUwpKTm5sOWpsWDVjSTRzTEpiTHZTR09LRGVWWDZZM0xxK2NhdHpBUGtuWlNYT3lrUzhlUUJoSE5oVjV2cmlFQ1VvCjVsbWd0REErUzdxbVJPTjNEOHY1V1hLSnRYeFg4ME5KZGpZOHltTllGMitsYStGZy9oYURDbXpkbW9zNGVTYlYKWEVBb0RUM1hmNXR6NkZ2Z2wwdXN0YThUeDJtV0xqU2hRS09STndJREFRQUJvQUF3RFFZSktvWklodmNOQVFFTApCUUFEZ2dFQkFBVDB6NGQ3TFZySElNSFRHMVQya1B3NFAxVGo5UDRuYWR2NUZzRE1FMExnSi8vSzNJLzNSMWFQCmpJSmYzbGVIcVVWUEFadnR6Kzdjd3JFUlNvaC9QL2JkR2xpYm8vV1llYjZVYlhxbm4wNmIzcWxRVVJvR1F6cTYKSzk3YTZoQUF0MGI5UXBYY0FUOWp1S041U0VqakhuTklHNEtQalU4OEZ3NUhjN3E4VENUUXgzY2N2Q0VPeXhPYgowSEl1RllEaGM4dlZJak9wU2dqWGxoNjJ0djJxZ2ErS2o2U3ZSbHBqb0FpU2d4cGdCYXBpaXBrWTFadWZTSnpLCktiSVJhTnJnZHgwLzZ5UkVwSXpDd1B5NXpXTS9vWnRRUDBKUlNxbzdkV3pURUtFT1VrakdHaWFyclEzZXczQUUKQW45bHF2NUZhWTR2Q240UFlhZlBWdE53NzJWTnNTWT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tCg==
  signerName: kubernetes.io/kube-apiserver-client
  expirationSeconds: 86400  # one day
  usages:
  - client auth
EOF

需要注意的几点:

  • usage 字段必须是 'client auth'
  • expirationSeconds 可以设置为更长(例如 864000 是十天)或者更短(例如 3600 是一个小时)
  • request 字段是 CSR 文件内容的 base64 编码值。

批准 CertificateSigningRequest

获取 CSR 列表:

kubectl get csr

此时看到状态为 Pending。批准 CSR:

kubectl certificate approve dev

此时状态变更为 Approved,Issued。

同样,如果不想批准,可以驳回这个 CSR:

kubectl certificate deny dev

从 CertificateSigningRequest 导出颁发的 crt 证书

从 CSR 取得证书:

kubectl get csr dev -o yaml

证书的内容使用 base64 编码,存放在字段 status.certificate

从 CertificateSigningRequest 导出颁发的证书:

kubectl get csr dev -o jsonpath='{.status.certificate}'| base64 -d > dev.crt

查看证书有效期:

# openssl x509 -in dev.crt -noout -dates
notBefore=Sep  7 06:42:27 2024 GMT
notAfter=Sep  8 06:42:27 2024 GMT

查看证书拥有者名字:

# openssl x509 -in dev.crt -noout -subject
subject=CN = dev

验证证书

验证新签发用户证书也是由 kuberntest s集群 CA 证书签发的:

# openssl verify -CAfile=/etc/kubernetes/pki/ca.crt dev.crt 
dev.crt: OK

使用证书测试访问 API Server:

# curl --cert dev.crt --key dev.key --cacert /etc/kubernetes/pki/ca.crt https://192.168.0.2:6443/api
{
  "kind": "APIVersions",
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "192.168.0.2:6443"
    }
  ]
}

测试访问能够获取 API 信息。

设置 kubeconfig 文件

设置集群

设置一个名为 kubernetes 的集群名字:

kubectl config set-cluster kubernetes --server=https://192.168.0.2:6443 --embed-certs=true --certificate-authority=/etc/kubernetes/pki/ca.crt --kubeconfig=/home/dev/.kube/config

将用户详细信息添加到配置文件中

将客户端证书文件 dev.crt 和客户端密钥文件 dev.key 设置为名为 dev 的用户凭证,并将这些证书和密钥嵌入到 kubeconfig 文件中:

kubectl config set-credentials dev --embed-certs=true --client-key=dev.key --client-certificate=dev.crt --kubeconfig=/home/dev/.kube/config

将上下文详细信息添加到配置文件中

为集群和 dev 用户配置 kubeconfig 文件中:

kubectl config set-context dev@kubernetes --cluster=kubernetes --user=dev --kubeconfig=/home/dev/.kube/config

设置用户上下文

设置 dev 用户的上下文环境:

kubectl config use-context dev@kubernetes --kubeconfig=/home/dev/.kube/config

现在当 dev 用户输入 kubectl 命令时,相应动作会应用于 dev@kubernetes 上下文中所列的集群和名字空间。 同时,命令会使用 dev 上下文中所列用户的凭证。

使用 kubeconfig 文件

把/home/dev/.kube 目录更改权限为 dev 用户使用:

chown -R dev:dev /home/dev/.kube/

后面系统上的 dev 用户便可以通过 kubernetes 的 dev 用户使用 kubectl 去访问资源。