编辑
2023-11-14
Kubernetes
00
请注意,本文编写于 170 天前,最后修改于 158 天前,其中某些信息可能已经过时。

目录

非对称加密
数字签名
数字证书
双向认证
Kubernetes内的证书
etcd
apiserver
controller-manager
kubelet
kube-proxy
Service Account
总结
参考文章

k8s与安全相关的内容相当多,各种类型的证书、jwt token,相关的概念还有非对称加密、数字签名、数字证书各种内容,本篇的目的就是将这些一并学习。

非对称加密

非对称加密的基本原理是使用一些加密算法生成一对公钥和密钥,使用公钥对数据加密只能用与之相对应的密钥解密,反之则亦然。ssh、https都是非对称加密的使用场景。在https通信开始时,用户的浏览器需要获取对方服务器的公钥,而公钥就是存在对方的https证书里面;在ssh的场景中,把公钥复制到服务器的.ssh/authorized_keys就可以实现免密登录。如果没有手动复制公钥,如何交换公钥也是一个关键问题,这个问题由DH算法得到了解决。

下面使用openssl来演示下非对称加密过程:

shell
# 生成一对公钥和密钥 openssl genpkey -algorithm RSA -outform PEM -out private_key.pem openssl rsa -in private_key.pem -outform PEM -pubout -out public_key.pem # 生成一个文件用于加密 echo "hello world" > hello.txt # 用公钥加密 openssl rsautl -encrypt -inkey public_key.pem -pubin -in plain_text -out encrypted_text # 查看加密内容,会发现是无法阅读的内容 cat encrypted_text # 用密钥解密 openssl rsautl -decrypt -inkey private_key.pem -in encrypted_text -out decrypted_text # 查看解密后的内容 cat decrypted_text Hello world

数字签名

假设,A与B之间需要通信,他们使用对方的各自公钥加密内容,接受适在使用自己的密钥解,但是因为非对称加密当中公钥都是公开的,所以此时中间C可以拦截B->A的消息(反之也行),用A的公钥重新加密一段内容发送给A,这样A就会误以为这是B发的,示意图如下。C将信息篡改,让A误以为B给他发送了一条内容为bad的信息。

image-20230322140017147

所以这一环节的问题出现在,非对称加密只是中间人无法解密出消息的内容,但是不能保证中间人不会伪造数据,也就是说非对称加密无法解决消息的发送方是不是自己所期望的。类比到生活中,解决该问题的办法就是要求通信双方在它们的信件上都加上自己的签名来表明身份,并且该签名也只能保密只能让对方解密

数字签名的基本原理类似,A->B发送信息,A用B的公钥加密一段内容,但是用A的私钥结合消息内容生成数字签名连同加密的内容发送给B。B接收到消息后,用B的秘钥解密出内容,再用A的公钥验证数字签名。具体的签名过程为:

  • B先对内容进行Hash得到摘要
  • B用A的公钥对收到的数字签名进行解密也得到了一个摘要,接着对比这两个摘要是否相同即可判断数据是否被篡改

要对比摘要是否相等,所以必须要保证加密双方所用的摘要生成算法是完全一致的,目前使用较多的就是md5和sha1。有了数字签名后,如果中间人C需要伪造消息发送给B,B就可以通过签名来验证数据是否来自A,示意图如下,来自wiki:

image-20230322141441784

实际中的过程不是直接对消息的内容生成签名,而是先对消息内容进行Hash(也就是所谓摘要,digest)以后在生成签名。这里主要考虑是,摘要经过Hash后数据量相比消息内容已经短了很多,生成摘要更加高效。除此以外,Hash值还保证了数据是否被篡改,被篡改后的数据所生成的Hash肯定是不相同的。下面是一张相类似的图,但是内容相对详细一些,图来自

image-20230322143032776

数字证书

前面对于数字签名的讨论的前提是通信的双方已经知道对方的公钥,那么在通信的时候如何去确定所使用的公钥一定是对方的而不是别人的伪造的呢?这就需要第三方机构,确保通信所拿到的公钥一定是真的,这个机构就是CA(certification authority)机构。当一个服务期望通过https为外界提供时,就需要找ca机构申请数字证书。目前大部分的数字证书都是由RFC 5280定义的x.509标准,里边就包含着公钥信息,示意图如下,图来自

image-20230322154052852

上图的Key指代的就是证书的公钥,在https场景下就是对方服务器的公钥,浏览器就可以用服务器提供的公钥对内容加密消息内容。当我们访问一个https网站的时候,浏览器首先会对服务器的证书进行验证。那么如何确保证书本身是否篡改呢?这还是利用到了数字签名的作用,即上图证书的signature字段。如同前面描述过的数字签名流程一样,浏览器通过签名即可校验证书的完整性,不过实际比这个过程要复杂,因为大部分的https网站使用的都是二级证书。当我们往一个https网站发起链接时,如果CA证书有问题,浏览器就会提示形如证书不安全或者是该网站的证书不是由可靠的机构颁发的证书的提示信息,至于HTTPS链接建立的过程网上相关文章很多不在过多赘述。

在chrome上任意点开一个https加密的网站,点击地址栏的锁团,可以看到它的ca证书相关信息,如下:

image-20230322191056245

上面的证书表示根证书是来自Let's enctypt(ISRG Root X1),R3是一个中间证书(Intermediate Certificates),这就涉及到证书链的概念。新的问题是,如何验证二级证书本身是不是被篡改?还是数字签名的功劳,不过二级证书的签名是由根证书签署的,所以浏览器先需要从二级证书中反推找到根证书,用根证书(一般已经直接内嵌在浏览器或者操作系统)的签名来验证二级证书。在这个整个流程中可以发现根证书是十分重要的,一旦根证书坏了大家就不会认为它是不可靠的。这也是多级证书的原因,如果一个二级证书出现了问题,吊销该二级证书也不会影响到根证书,对于多级证书的内容可以参考这里这里

既然每一份证书都有来自CA机构的签名,那么根证书的签名从哪儿来呢?根证书的是自签证书,意思是它的签名是CA机构用自己的私钥生成的。使用OpenSSL来生成ca证书的样例可以参考:https://www.zhaohuabing.com/post/2020-03-19-pki/。

双向认证

目前的大多数浏览器-服务器之间的交互都是单项认证的,也就是浏览器需要认证服务器的ca证书,但是服务器却不需要校验客户端。与之相对的就是双向认证客户端与服务端需要完成各自认证以后才可以通信,认证的流程同单向认证是一样的。双向认证的内容可以参考:The magic of TLS, X509 and mutual authentication explained,下图就来自这篇medium文章描述了双向认证的流程,虽然在内容上有些简化,不过总体意思是正确的。

image-20230323143840975

Kubernetes内的证书

etcd

kubernetes内各种组件都有各种证书,经过上面的基本知识的理解,也就理解了k8s组件各种证书的使用。先查看etcd所使用的证书:

shell
$ kubectl get pods -n kube-system etcd-k8svm -o yaml | grep "cet\|key" - --cert-file=/etc/kubernetes/pki/etcd/server.crt - --key-file=/etc/kubernetes/pki/etcd/server.key - --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt - --peer-key-file=/etc/kubernetes/pki/etcd/peer.key - --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt - --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt

首先需要明确的是k8s集群当中的关键组件都是需要双向认证的,在etcd节点中一个etcd服务即会做客户端也会作为server,有这样的概念以后各个证书的就很好解释了:

  • server.crt: 对外提供服务时所用证书(对外提供加密用的公钥)
  • server.key: 服务证书密钥(也就是非对称加密当中的密钥,用于解密经过公钥加密后的数据)
  • peer.crt: 作为客户端时(etcd与etcd之间)所用的证书
  • peer.key: 客户端证书的密钥,同上
  • ca.crt: 分别是客户端证书和服务端证书各自的CA根证书(用于验证前面提到的证书是否有效,该证书是自签证书),从结果来看该根证书都是其他证书的根证书。

apiserver

接下来看看apiserver的证书,apiserver是整个k8s集群当中唯一与etcd直接通信的组件,很明显的是apiserver至少具备了与用于etcd访问的相关证书,直接去/etc/kubernetes/manifest查看apiserver的manifest,如下:

shell
$ cat kube-apiserver.yaml | grep --color "crt\|pem\|key" - --client-ca-file=/etc/kubernetes/pki/ca.crt # 用于验证访问kube-apiserver的client的根证书 - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt # 用于验证来自etcd的服务端证书的根证书 - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt # apiserver作为客户端访问etcd的证书 - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key # 访问etcd的客户端证书所对应的密钥 - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt # apiserver作为客户端访问kubelet的证书 - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key # 访问kubelet的客户端证书的密钥 - --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt # 不是特别了解 - --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt - --service-account-key-file=/etc/kubernetes/pki/sa.pub # 留到service count 小节再描述 - --service-account-signing-key-file=/etc/kubernetes/pki/sa.key - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt # apiserver作为服务端对外提供服务的证书 - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key # apiserver的服务端证书对应的密钥

从参数列表来看,总结一下它们的作用: 因为双向验证,apiserver需要传入客户端证书(如apiserver作为etcd的客户端)和服务端证书(让其他组件访问,普通pod,kubectl等),每一份证书都需要一份数字签名用于证书数据的验证。

controller-manager

front-proxy-client.crt是用于k8s聚合层的,目前没有了解过,跳过。接下来看一下kube-controller-manager的启动命令:

yaml
spec: containers: - command: - kube-controller-manager - --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf - --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf - --bind-address= 0.0.0.0 - --client-ca-file=/etc/kubernetes/pki/ca.crt # 验证client证书的根证书 - --cluster-name=kubernetes - --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt # 用于签发用户证书的根证书 - --cluster-signing-key-file=/etc/kubernetes/pki/ca.key #用户签发用户证书的签名 - --controllers=*,bootstrapsigner,tokencleaner - --kubeconfig=/etc/kubernetes/controller-manager.conf - --leader-elect=true - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt - --root-ca-file=/etc/kubernetes/pki/ca.crt # secret内证书,将会给pod用于访问apiserver - --service-account-private-key-file=/etc/kubernetes/pki/sa.key # 下面会讲解 - --use-service-account-credentials=true

--cluster-signing-cert-file--cluster-signing-key-file的的作用是签发用户证书,k8s提供了certificates.k8s.ioAPI目的就是用集群的根证书来签发用户证书。不过与重点还是关注controller-manager.conf的内容,如下:

yaml
apiVersion: v1 clusters: - cluster: # 用于验证 kube-apiserver 服务器证书的 CA 根证书,因为组件之间是双向认证的,所以apiserver也会往 # controller manager 发送一个证书,controller manager 就用下面这个配置项验证apiserver的证书 certificate-authority-data: xxxx.... kind: Config preferences: {} users: - name: system:kube-controller-manager user: # controller-manager 用于访问api-server的证书 client-certificate-data: xxxx.... # controller-manager 证书所对应的密钥 client-key-data: xxxxxx...

cluster:certificate-authority-data的内容实际上就/etc/kubernetes/pki/ca.crt只不过在controller-manager.conf以base64编码了,作用是验证apiserver的服务端证书。另外一个角度来说,还是因为双向认证的原因, controller-manager访问apisever也需要提供一份证书和签名,这也就是user:client-certificate-data的内容。

Note: 从文档找到对于--client-ca-file的描述如下:

  • --client-ca-file: When a request arrives to the Kubernetes apiserver, if this option is enabled, the Kubernetes apiserver checks the certificate of the request. If it is signed by one of the CA certificates in the file referenced by --client-ca-file, then the request is treated as a legitimate request, and the user is the value of the common name CN=, while the group is the organization O=. See the documentation on TLS authentication.

kubelet

kubelet的启动没有manifest,是以system.service形式运行,直接使用ps查看kubelet启动参数:

shell
$ ps -aux | grep kubelet /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --network-plugin=cni --pod-infra-container-image=k8s.gcr.io/pause:3.6

可以看到,比较关键的就是几个配置文件。kubelet.conf内容如下:

yaml
apiVersion: v1 clusters: - cluster: certificate-authority-data: xxxxx... server: https://10.246.204.219:6443 # 省略了一些不相关内容 users: - name: system:node:ygj-k8s-master user: client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem client-key: /var/lib/kubelet/pki/kubelet-client-current.pem

配置项server指明了要交互的apiserver地址,certificate-authority-data是集群的ca经过base64以后的内容,可以与master节点的ca证书内容进行对比确认。kubelet 需要与apiserver交互,所用的证书和私钥都位于 /var/lib/kubelet/pki/kubelet-client-current.pem,也就是说证书和私钥位于一份.pem文件,直接查看文件的内容就可以确认了。从这里引申出来的一项内容是k8s的TLS-boostrapping机制,目的是简化worker节点证书生成的过程,bootstrapping机制以利于bootstrap-kubelet.conf这个配置文件。

目录/var/lib/kubelet/pki还有kubelet.crt是kubelet的服务端证书,是kubelet对外提供服务所用的证书,因此与kubelet的基本功能无关,对于这个证书的使用参考: https://github.com/kubernetes/kubeadm/issues/2186

kube-proxy

kube-proxy不同于其他系统组件使用.conf文件,kube在证书的使用上和普通的pod没有差别,通过secret将集群的ca挂载到集群内部,它与apiserver之间是单向认证的,即只用集群ca校验apiserver的证书。下面是一个示例:

shell
$ k describe pod kube-proxy-ps9hf -n kube-system # 省略了一些内容 Mounts: /lib/modules from lib-modules (ro) /run/xtables.lock from xtables-lock (rw) /var/lib/kube-proxy from kube-proxy (rw) /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-dbdgd (ro) #... 省略了一些内容 kube-api-access-dbdgd: Type: Projected (a volume that contains injected data from multiple sources) TokenExpirationSeconds: 3607 ConfigMapName: kube-root-ca.crt ConfigMapOptional: <nil> DownwardAPI: true

所以在排查证书相关的问题时不需要关注kube-proxy。

Service Account

controller-manager与api-server有与service account相关的参数分别是:

shell
# controller manager - kube-controller-manager - --service-account-private-key-file=/etc/kubernetes/pki/sa.key # apiserver - --service-account-key-file=/etc/kubernetes/pki/sa.pub - --service-account-signing-key-file=/etc/kubernetes/pki/sa.key

按照文档描述,controller-magager的--service-account-private-key-file用于对service account token的签名。

Filename containing a PEM-encoded private RSA or ECDSA key used to sign service account tokens.

apiserver的--service-account-key-file用于对签名的校验

File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify ServiceAccount tokens. The specified file can contain multiple keys, and the flag can be specified multiple times with different files. If unspecified, --tls-private-key-file is used. Must be specified when --service-account-signing-key-file is provided

按照我个人的理解,这就是数字签名的过程,确保信息没有被修改。新的问题是: service account token到底什么? 从这里引申出service account、secret的内容。

service account的作用类似于账号密码,将会作为访问API server的凭证。每个namespace下都会有一个默认的default service accuont,当一个pod被创建时如果没有指定service account,那么就使用这个默认的service account,它只具有默认的权限。每个service account都有一个与之相对应的secret,下面是一个nfs provisioner示例:

shell
$ kubectl get serviceaccount nfs-client-provisioner -o yaml apiVersion: v1 kind: ServiceAccount metadata: creationTimestamp: "2023-08-07T06:06:23Z" name: nfs-client-provisioner namespace: default resourceVersion: "470582" uid: 9d89b220-1b2c-4820-8e38-bc60488ae8ee # 该service account所用secret secrets: - name: nfs-client-provisioner-token-b2vcn

而每个pod在启动时绑定一个secret volume,如果没有指定service account它绑定的是默认的secert。在老一点的版本中,k8s直接以 secret volume绑定到pod内,比较新的版本(1.2.x 具体未考证)使用了projected volume,作用是将多种类型的数据挂载到同一个目录,kubectl describe pod查看任意pod就可以看到。概括来说,虽然在pod的manifest中我们配置的是service account,但是它实际起效的是通过 secret挂载到了pod内部。再来看看secret,打开任意一个secret的内容:

shell
$ kubectl describe secret default-token-mg4kv Name: default-token-mg4kv Namespace: default Labels: <none> Annotations: kubernetes.io/service-account.name: default kubernetes.io/service-account.uid: 5ff4a69f-654a-48f2-b284-57f50ee07e7c Type: kubernetes.io/service-account-token Data ==== ca.crt: 1099 bytes namespace: 7 bytes token: eyJhbGciOiJSUzI1N.... # 省略了一些数据

如果直接用kubectl get得到的是被base64 编码后的数据,这些数据的是:

  • ca.crt: 用于校验服务端所用的证书。比如pod要访问apiserver的数据就需要用该证书去验证apiserve所提供的server证书,也就是apiserver的--tls-cert-file参数所指定的证书。实际上ca.crt的内容就是根证书ca.crt,而apiserver.crt又是由ca.crt签发的,所以用ca.crt来验证是显而易见的。

  • namespace: 该secret所处的namespace

  • token: 是一个jwt token,使用base64解码以后可以去jwt官网解码,其中的信息如下图,可以看到serviceaccount的信息尽在其中,apiserver就可以用这些信息来判断一个 pod 是否有权限访问一些资源,token在生成的时候会用/etc/kubernetes/pki/sa.key生成签名。

    引用文档的相关描述:

    A kubernetes.io/service-account-token type of Secret is used to store a token credential that identifies a service account.

第一个问题是,ca.crt的内容是从哪儿来的?**ca.crt的内容就是来自controller-manager参数中的--root-ca-file**也就是集群的ca。这一切都顺理成章了,secret里的ca.crt通过volume挂载到pod内部,pod用它来校验apiserver的https证书,apiserver的https证书又是从这个ca.crt签发 。

token是jwt token,下面是经过解码后的JWT token示例。所以回到最初的问题,service account token就是这个jwt token。By the way,我们日常使用kubectl访问apiserver总体的流程也是与上面描述的流程没什么区别,查看~/.kube/config内容也是类似,无非是证书和秘钥的内容。

image-20230908155014316

最后以一张图概括下上面的所有提到的内容,图来自:

kubernetes-service-accout-auth

总结

本篇草草的过了下与kubernetes证书相关的内容,无论是从证书与加密本身还是从k8s内容都不够详尽,并且是自学的所以在内容上可能有些纰漏和错误,希望大家指出。

参考文章

https://qingwave.github.io/k8s-tls

https://pandaychen.github.io/2020/04/09/A-KUBERNETES-CERTIFICATE-PRINCIPLE/

https://www.zhaohuabing.com/post/2020-05-19-k8s-certificate/

本文作者:strickland

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!