Kubernetes TLS bootstrapping 那点事

一、TLS bootstrapping 简介

当集群开启了 TLS 认证后,每个节点的 kubelet 组件都要使用由 apiserver 使用的 CA 签发的有效证书才能与 apiserver 通讯;此时如果节点多起来,为每个节点单独签署证书将是一件非常繁琐的事情;TLS bootstrapping 功能就是让 kubelet 先使用一个预定的低权限用户连接到 apiserver,然后向 apiserver 申请证书,kubelet 的证书由 apiserver 动态签署;在配合 RBAC 授权模型下的工作流程大致如下所示

bootstarp-1

二、TLS bootstrapping 相关术语

2.1、kubelet

kubelet 组件在工作时,采用主动的查询机制,即定期请求 apiserver 获取自己所应当处理的任务,如哪些 pod 分配到了自己身上,从而去处理这些任务;同时 kubelet 自己还会暴露出两个本身 api 的端口,用于将自己本身的私有 api 暴露出去,这两个端口分别是 10250 与 10255;对于 10250 端口,kubelet 会在其上采用 TLS 加密以提供适当的鉴权功能;对于 10255 端口,kubelet 会以只读形式暴露组件本身的私有 api,并且不做鉴权处理
总结一下,就是说 kubelet 上实际上有两个地方用到证书,一个是用于与 API server 通讯所用到的证书,另一个是 kubelet 的 10250 私有 api 端口需要用到的证书

2.2、CSR 请求类型

kubelet 发起的 CSR 请求都是由 kube-controller-manager 来做实际签署的,对于 kube-controller-manager 来说,TLS bootstrapping 下 kubelet 发起的 CSR 请求大致分为以下两种

  • nodeclient: kubelet 首次创建新证书以 O=system:bootstrappersCN=system:bootstrappers 形式发起的 CSR 请求
  • selfnodeclient: kubelet client 更新自己的证书以 O=system:nodesCN=system:nodes 形式发起的 CSR 请求

大白话加自己测试得出的结果: nodeclient 类型的 CSR 仅在第一次启动时会产生,selfnodeclient 类型的 CSR 请求实际上就是 kubelet 更新自己作为 client 跟 apiserver 通讯时使用的证书产生的。

三、TLS bootstrapping 具体引导过程

3.1、Kubernetes TLS 与 RBAC 认证

在说具体的引导过程之前先谈一下 TLS 和 RBAC,因为这两个事不整明白下面的都不用谈;

  • TLS 作用

    众所周知 TLS 的作用就是对通讯加密,防止中间人窃听;同时如果证书不信任的话根本就无法与 apiserver 建立连接,更不用提有没有权限向 apiserver 请求指定内容

  • RBAC 作用

    当 TLS 解决了通讯问题后,那么权限问题就应由 RBAC 解决(可以使用其他权限模型,如 ABAC);RBAC 中规定了一个用户或者用户组(subject)具有请求哪些 api 的权限;在配合 TLS 加密的时候,实际上 apiserver 读取客户端证书的 CN 字段作为用户名,读取 O 字段作为用户组

从以上两点上可以总结出两点: 第一,想要与 apiserver 通讯就必须采用由 apiserver CA 签发的证书,这样才能形成信任关系,建立 TLS 连接;第二,可以通过证书的 CN、O 字段来提供 RBAC 所需的用户与用户组

3.2、kubelet 首次启动流程

看完上面的介绍,不知道有没有人想过,既然 TLS bootstrapping 功能是让 kubelet 组件去 apiserver 申请证书,然后用于连接 apiserver;那么第一次启动时没有证书如何连接 apiserver ?

  • kubelet启动
  • kubelet看到它没有kubeconfig文件
  • kubelet搜索并查找bootstrap-kubeconfig文件
  • kubelet读取它的bootstrap文件,检索API server的URL和一个低权限的“token”
  • kubelet连接到API服务器,使用token进行身份验证
  • kubelet现在具有创建和检索证书签名请求(CSR)的有限凭据
  • kubelet为自己创建了一个CSR
  • CSR通过以下两种方式之一获得批准:
    • 如果已配置,kube-controller-manager将自动批准CSR
    • 如果已配置,则外部流程(可能是人员)使用Kubernetes API或通过批准CSR kubectl
  • 为kubelet创建证书
  • 证书颁发给kubelet
  • kubelet检索证书
  • kubelet 使用密钥和签名证书创建一个正确的kubeconfig文件
  • kubelet开始正常运作
  • 可选:如果已配置,则当证书接近到期时,kubelet会自动请求更新证书

在有些用户首次启动时,可能与遇到 kubelet 报 401 无权访问 apiserver 的错误;kubelet 通过bootstrap.kubeconfig 中的预设用户 Token 声明了自己的身份,然后创建 CSR 请求;但是不要忘记这个“用户和组”在我们不处理的情况下他没任何权限的,包括创建 CSR 请求;所以需要如下命令创建一个 ClusterRoleBinding,将预设“用户”或“组”与内置的 ClusterRole system:node-bootstrapper 绑定到一起,使其能够发起 CSR 请求(以下是用“组”做绑定)。

1
2
3
# 组
kubectl create clusterrolebinding kubeadm:kubelet-bootstrap \
--clusterrole system:node-bootstrapper --group system:bootstrappers
1
2
3
4
# 用户
kubectl create clusterrolebinding kubelet-bootstrap \
--clusterrole=system:node-bootstrapper \
--user=system:bootstrap:<token id>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 使用kubeadm管理token(在高版本中采用Kubeadm管理token可以不用创建自动批准csr步骤,因为默认已经为这个“组”创建好了自动批准csr请求的权限,接下来只需要生成bootstarp文件即可。)
kubeadm token create --groups system:bootstrappers:kubeadm:default-node-token


[root@k8s-m1 kubernetes]# kubeadm token list
TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS
0c15sx.z41i3lfabbizbqb5 23h 2020-09-16T08:56:30+08:00 authentication,signing <none> system:bootstrappers:kubeadm:default-node-token


[root@k8s-m1 kubernetes]# kubectl get secret -n kube-system
NAME TYPE DATA AGE
.....
bootstrap-token-0c15sx bootstrap.kubernetes.io/token 6 19m
.....

3.3、手动签发证书

在 kubelet 首次启动后,如果用户 Token 没问题,并且 RBAC 也做了相应的设置,那么此时在集群内应该能看到 kubelet 发起的 CSR 请求

bootstarp-2

出现 CSR 请求后,可以使用 kubectl 手动签发(允许) kubelet 的证书

bootstarp-3

当成功签发证书后,目标节点的 kubelet 会将证书写入到 –cert-dir= 选项指定的目录中;注意此时如果不做其他设置应当生成四个文件

bootstarp-4

而 kubelet 与 apiserver 通讯所使用的证书为 kubelet-client.crt,剩下的 kubelet.crt 将会被用于 kubelet server(10250) 做鉴权使用;注意,此时 kubelet.crt 这个证书是个独立于 apiserver CA 的自签 CA,并且删除后 kubelet 组件会重新生成它

四、证书文件作用

  • kubelet-client.crt
    该文件在 kubelet 完成 TLS bootstrapping 后生成,此证书是由 controller manager 签署的,此后 kubelet 将会加载该证书,用于与 apiserver 建立 TLS 通讯,同时使用该证书的 CN 字段作为用户名,O 字段作为用户组向 apiserver 发起其他请求
  • kubelet-client-current.pem
    这是一个软连接文件,会在证书总有效期的 70%~90% 的时间内发起续期请求,请求被批准后会生成一个 kubelet-client-时间戳.pem;kubelet-client-current.pem 文件则始终软连接到最新的真实证书文件,除首次启动外,kubelet 一直会使用这个证书同 apiserver 通讯
  • kubelet.crt
    该文件在 kubelet 完成 TLS bootstrapping 后并且没有配置 --rotate-server-certificates 时才会生成;这种情况下该文件为一个独立于 apiserver CA 的自签 CA 证书,有效期为 1 年;被用作 kubelet 10250 api 端口
  • kubelet-server.crt
    该文件在 kubelet 完成 TLS bootstrapping 后并且配置了 --rotate-server-certificates 时才会生成;这种情况下该证书由 apiserver CA 签署,默认有效期同样是 1 年,被用作 kubelet 10250 api 端口鉴权
  • kubelet-server-current.pem
    同样是一个软连接文件,当 kubelet 配置了 --rotate-server-certificates 选项后,会在证书总有效期的 70%~90% 的时间内发起续期请求,请求被批准后会生成一个 kubelet-server-时间戳.pem;kubelet-server-current.pem 文件则始终软连接到最新的真实证书文件,该文件将会一直被用于 kubelet 10250 api 端口鉴权

五、使用 Bootstrap Token

5.1、创建 Bootstrap Token

既然整个功能都时刻强调这个 Token,那么第一步肯定是生成一个 token,生成方式如下:

1
2
➜  ~ echo "$(head -c 6 /dev/urandom | md5sum | head -c 6)"."$(head -c 16 /dev/urandom | md5sum | head -c 16)"
47f392.d22d04e89a65eb22

这个 47f392.d22d04e89a65eb22 就是生成的 Bootstrap Token,保存好 token,因为后续要用;关于这个 token 解释如下:
Token 必须满足 [a-z0-9]{6}\.[a-z0-9]{16} 格式;以.分割,前面的部分被称作 Token ID,Token ID 并不是 “机密信息”,它可以暴露出去;相对的后面的部分称为 Token Secret,它应该是保密的。

5.2、创建 Bootstrap Token Secret

对于 Kubernetes 来说 Bootstrap Token Secret 也仅仅是一个特殊的 Secret 而已;对于这个特殊的 Secret 样例 yaml 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: v1
kind: Secret
metadata:
# Name MUST be of form "bootstrap-token-<token id>"
name: bootstrap-token-07401b
namespace: kube-system

# Type MUST be 'bootstrap.kubernetes.io/token'
type: bootstrap.kubernetes.io/token
stringData:
# Human readable description. Optional.
description: "The default bootstrap token generated by 'kubeadm init'."

# Token ID and secret. Required.
token-id: 47f392
token-secret: d22d04e89a65eb22

# Expiration. Optional.
expiration: 2018-09-10T00:00:11Z

# Allowed usages.
usage-bootstrap-authentication: "true"
usage-bootstrap-signing: "true"

# Extra groups to authenticate the token as. Must start with "system:bootstrappers:"
auth-extra-groups: system:bootstrappers:worker,system:bootstrappers:ingress

需要注意几点:

  • 作为Bootstrap Token Secrettype必须为bootstrap.kubernetes.io/token,name 必须为bootstrap-token-<token id>(Token ID 就是上一步创建的 Token 前一部分)
  • usage-bootstrap-authentication 表示令牌可以用于 API 服务器的认证。认证器会以 system:bootstrap:<Token ID> 认证。它被包含在 system:bootstrappers 组中。 命名和组是故意受限制的,以防止用户在启动引导后再使用这些令牌。
  • usage-bootstrap-signing 表示令牌应该被用于 cluster-info ConfigMap 的签名
  • expiration 字段是可选的,如果设置则 Secret 到期后将由 Controller Manager 中的 tokencleaner 自动清理
  • auth-extra-groups 是可选的,令牌的扩展认证组,组必须以 system:bootstrappers: 开头。在1.16后可以设置system:bootstrappers:kubeadm:default-node-token组,后面就不需要创建自动批准csr相关的操作。

最后使用 kubectl create -f bootstrap.secret.yaml 创建即可

5.3、创建 ClusterRoleBinding 将 ClusterRole 绑定到适当的用户组,以完成自动批准相关 CSR 请求

需要注意的是在使用 Bootstrap Token 进行引导时,Kubelet 组件使用 Token 发起的请求其用户名为 system:bootstrap:<token id>,用户组为 system:bootstrappers;所以我们在创建 ClusterRoleBinding 时要绑定到这个用户或者组上;

  • 允许 system:bootstrappers 组用户创建 CSR 请求
1
2
kubectl create clusterrolebinding kubeadm:kubelet-bootstrap \
--clusterrole system:node-bootstrapper --group system:bootstrappers
  • 自动批准 system:bootstrappers 组用户 TLS bootstrapping 首次申请证书的 CSR 请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat <<EOF | kubectl apply -f -
# Approve all CSRs for the group "system:bootstrappers"
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: auto-approve-csrs-for-group
subjects:
- kind: Group
name: system:bootstrappers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: system:certificates.k8s.io:certificatesigningrequests:nodeclient
apiGroup: rbac.authorization.k8s.io
EOF
  • 自动批准 system:nodes 组用户更新 kubelet 自身与 apiserver 通讯证书的 CSR 请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat <<EOF | kubectl apply -f -
# Approve renewal CSRs for the group "system:nodes"
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: auto-approve-renewals-for-nodes
subjects:
- kind: Group
name: system:nodes
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
apiGroup: rbac.authorization.k8s.io
EOF

六、调整 Controller Manager

根据官方文档描述,Controller Manager 需要启用 tokencleaner 和 bootstrapsigner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes
After=network.target

[Service]
ExecStart=/usr/local/bin/kube-controller-manager
--allocate-node-cidrs=true
--kubeconfig=/etc/kubernetes/controller-manager.conf
--authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
--authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
--client-ca-file=/etc/kubernetes/pki/ca.crt
--cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt
--cluster-signing-key-file=/etc/kubernetes/pki/ca.key
--bind-address=127.0.0.1
--leader-elect=true
--cluster-cidr=10.244.0.0/16
--service-cluster-ip-range=10.96.0.0/12
--requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
--service-account-private-key-file=/etc/kubernetes/pki/sa.key
--root-ca-file=/etc/kubernetes/pki/ca.crt
--use-service-account-credentials=true
--controllers=*,bootstrapsigner,tokencleaner
--experimental-cluster-signing-duration=86700h
--v=2
Restart=always
RestartSec=10s

[Install]
WantedBy=multi-user.target

七、生成 bootstrap.kubeconfig

前面所有步骤实际上都是在处理 Api Server、Controller Manager 这一块,为的就是 “老子启动后 TLS Bootstarpping 发证书申请你两个要立马允许,不能拒绝老子”;接下来就是比较重要的 bootstrap.kubeconfig 配置生成,这个 bootstrap.kubeconfig 是最终被 Kubelet 使用的,里面包含了相关的 Token,以帮助 Kubelet 在第一次通讯时能成功沟通 Api Server;生成方式如下:

7.1、设置 bootstrap , 创建 bootstrap 令牌(此步骤上面已执行,这里只是讲另外一种创建方法)

1
2
3
4
5
6
7
8
9
10
11
TOKEN_PUB=$(openssl rand -hex 3)
TOKEN_SECRET=$(openssl rand -hex 8)
BOOTSTRAP_TOKEN="${TOKEN_PUB}.${TOKEN_SECRET}"

kubectl -n kube-system create secret generic bootstrap-token-${TOKEN_PUB} \
--type 'bootstrap.kubernetes.io/token' \
--from-literal description="cluster bootstrap token" \
--from-literal token-id=${TOKEN_PUB} \
--from-literal token-secret=${TOKEN_SECRET} \
--from-literal usage-bootstrap-authentication=true \
--from-literal usage-bootstrap-signing=true

7.2、创建 bootstrap kubeconfig 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
CLUSTER_NAME="kubernetes"
KUBE_APISERVER="https://10.0.7.101:6443"
KUBE_USER="kubelet-bootstrap"
KUBE_CONFIG="bootstrap.conf"

# 设置集群参数
kubectl config set-cluster ${CLUSTER_NAME} \
--certificate-authority=/etc/kubernetes/pki/ca.crt \
--embed-certs=true \
--server=${KUBE_APISERVER} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG}

# 设置上下文参数
kubectl config set-context ${KUBE_USER}@${CLUSTER_NAME} \
--cluster=${CLUSTER_NAME} \
--user=${KUBE_USER} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG}

# 设置客户端认证参数
kubectl config set-credentials ${KUBE_USER} \
--token=${BOOTSTRAP_TOKEN} \
--kubeconfig=/etc/kubernetes/${KUBE_CONFIG}

# 设置当前使用的上下文
kubectl config use-context ${KUBE_USER}@${CLUSTER_NAME} --kubeconfig=/etc/kubernetes/${KUBE_CONFIG}

# 查看生成的配置文件
kubectl config view --kubeconfig=/etc/kubernetes/${KUBE_CONFIG}

八、调整 Kubelet

Kubelet 启动参数需要做一些相应调整,以使其能正确的使用 Bootstartp Token,完整配置如下(与使用 token.csv 配置没什么变化,因为主要变更在 bootstrap.kubeconfig 中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/kubernetes/kubernetes
After=docker.service
Requires=docker.service

[Service]
ExecStart=/usr/local/bin/kubelet
--bootstrap-kubeconfig=/etc/kubernetes/bootstrap.conf
--kubeconfig=/etc/kubernetes/kubelet.conf
--config=/var/lib/kubelet/config.yaml
--cgroup-driver=systemd
--pod-infra-container-image=kuops/pause-amd64:3.1
--allow-privileged=true
--network-plugin=cni
--cni-conf-dir=/etc/cni/net.d
--cni-bin-dir=/opt/cni/bin
--cert-dir=/etc/kubernetes/pki
--v=2

Restart=always
RestartSec=10s

[Install]
WantedBy=multi-user.target

九、TLS bootstrapping 证书自动续期

9.1、开启自动续期

在 kubelet 启动时增加一个 --rotate-certificates 参数后,kubelet client 才会自动重载新证书。

9.2、证书过期问题

需要重复强调一个问题是: TLS bootstrapping 时的证书实际是由 kube-controller-manager 组件来签署的,也就是说证书有效期是 kube-controller-manager 组件控制的; kube-controller-manager 组件提供了一个 --experimental-cluster-signing-duration 参数来设置签署的证书有效时间;默认为 8760h0m0s,将其改为 87600h0m0s 即 10 年后再进行 TLS bootstrapping 签署证书即可。

扩展知识:

在文档中多次提到过 kubelet server 这个东西;kubelet server 指的应该是 kubelet 的 10250 端口;在上面我们仅仅提到了两种类型的CSR 请求,其实实际上还有一种CSR请求。这种请求是用来更新kubelet server证书(10250端口)
注意:出于安全原因,核心Kubernetes中实现的CSR批准控制器不批准节点服务证书。需要手动批准服务证书请求。(此句摘自官方)

在kubelet中开启--rotate-server-certificates参数后会生成kubelet-server.crt证书,如果没开启此参数则仅生成kubelet-client.crt证书。

1
2
3
4
[root@k8s-m1 ~]# kubectl get csr
NAME AGE REQUESTOR CONDITION
csr-99l77 10s system:node:docker4.node Pending
node-csr-aGwaNKorMc0MZBYOuJsJGCB8Bg8ds97rmE3oKBTV-_E 11s system:bootstrap:5d820b Approved,Issued
1
2
[root@k8s-m1 ~]# kubectl certificate approve csr-99l77
certificatesigningrequest.certificates.k8s.io/csr-99l77 approved

bootstarp-5

bootstarp-6


Kubernetes TLS bootstrapping 那点事
https://system51.github.io/2019/08/26/Kubernetes-TLS-bootstrapping/
作者
Mr.Ye
发布于
2019年8月26日
许可协议