从外部访问Apiserver的安全端口时,客户端需要携带一个身份凭证,能够让apiserver认证为某个用户以及所属的组。

在集群的容器中,有时也需要访问apiserver。既然要访问API,那么就必须要携带身份凭证,然后能够被apiserver认证出来并进行授权。这就是要介绍的内容ServiceAccount

 

一、使用流程

我们简化一个场景:我现在要创建一个Pod,从Pod中去获取集群的节点列表(GET /api/v1/nodes)。那么使用ServiceAccount如何满足这个需要呢?很简单,只需要以下四步:

1.1 创建ServiceAccount
kubectl create serviceaccount peng --namespace default
  • 1.
1.2 授权ServiceAccount

创建以下的ClusterRole与ClusterRoleBinding

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: list-nodes
rules:
- apiGroups:
  - ""
  resources: 
  - nodes
  verbs:
  - list
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: approve-list-nodes
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: list-nodes
subjects:
- kind: ServiceAccount
  name: peng
  namespace: default
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
1.3 创建Pod,使用ServiceAccount

使用以下的yaml文件创建Pod

apiVersion: v1
kind: Pod
metadata:
  namespace: default
  name: pod-nginx
spec:
  serviceAccountName: peng
  containers:
  - image: nginx:1.11.5
    name: pod-nginx
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
1.4 从Pod中访问API

在Pod中执行以下的脚本,就能够获取集群的节点列表

#!/bin/bash

ServiceAccountToken=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CaCrt="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"

curl -H "Authoriztion: Bearer ${ServiceAccountToken}" --cacert ${CaCrt} https://kubernetes:443/api/v1/nodes
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

 

 

二、原理

接下来,我们详细地介绍一下上面每一步发生的事情,以及API请求能够被通过的原理。

注意:此内容只适用于k8s 1.22以前的版本,k8s 1.22开始,已开始变化。(见如下说明:  https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#manually-create-an-api-token-for-a-serviceaccount)

2.1 创建ServiceAccount
kubectl create serviceaccount sa1 --namespace default
  • 1.

我们使用如上的命令在default命令空间中创建了一个名字为peng的SerivceAccount后,我们来看一下这个ServiceAccount的内容。创建之后ServiceAccount的内容如下,它有名字、命名空间,以及一个Secret

$ kubectl get serviceaccount peng -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: peng
  namespace: default
  uid: 75532a3a-f936-11e8-91da-000c29719bda
secrets:
- name: peng-token-hps6w
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

那么这个Secret是如何来的呢?答案是,对于每一个ServiceAccount,controller-manager都会为它创建一个Secret。我们来看一下这个Secret的内容,如下。我们可以看到,该Secret的类型为kubernetes.io/service-account-token,然后data中有三个字段,这三个字段的值都是经过base64编码的

apiVersion: v1
kind: Secret
metadata:
  name: peng-token-hps6w
  namespace: default
type: kubernetes.io/service-account-token
data:
  ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNE1URXlNVEEzTkRBMU0xb1hEVEk0TVRFeE9EQTNOREExTTFvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTVQxCmRMTm9TYWQwK1J3MjEzdWhiZjNXM0l5UE5hZUxmQ3dDWko3bnVNVGZlSzBqeGdveHNnczQybVF5MkNkbEdsdzQKVWJPejhTcFhlSW4yRC9MREFmeEtqZGc0cWh5YllISmJjK1ZNVGVRRXNQb2VBejdBVWJDQ2hqMmlWdjlHK3p2QgpHWmtobXIzOUI4NWxuSklJTSsvRlliazBTbjBNdVhPc3BGZVJLK3lqbklZeXpxSEtTWVk4aW5SSDhhaExlUlNNCkFCTUVtRndyME1EdWdCR2YrbzFlQU5hamI0QzZ2K3NXV3B6Z01EelhEV1VoSTRpNzRVWHE5KzZOTDlMUGVFOTcKYTY3Yit1SE1DSTl1emg5WjlPcTV3QkY1OS8ram1QT1BzY2lrTnVSVkNEZHZ6enF2VzdjRERLUU1YcXpCYUxKQgpVVENZcDl2aml6c3J1bkpFTDNFQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFGMThHR3JvQmRCNytxa0xvM1RHOEJqTDE2SmcKTm9tL1E0cWJGdHZsaTQwbjZ5Z001cUdqNmI3VTRVT2NaRjl4aDd6cGxWVXJkM0Z0OGhKYjhnOThRaFYxbDVHNwo4a1RQWThQcnAxN2UxWlRjOVdGMXUzZXM4Y2p6VmVFOFAxMEE5c3JqY3ZDNldONnhiK2FNc2E0aEFJbEJ3WjNwCmErSFhhTVhLbVRGaE9KZHJ5QTBNcFJLdTB2eEl5VlNVVU5BdUtYZlk4L3d0bk1VR0JLK0FJTDF3OTRUZ2wxb2sKQ1J1MGhkWEtmek0xWnU0d0hzQVQrcVhWcWR2cUg4UGFWaHc0U2NRcmRlL2o5anBFckwvMUJaSXlBU0J2MndSdwpBZ2VOc3pnZDlRaVRmTWh6QXBLU3gyN2VseHhYRStObGJKbmJBeFJXdkQ2WHZJTmlKUDZuUFQ2eUF3UT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
  namespace: ZGVmYXVsdA==
  token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklpSjkuZXlKcGMzTWlPaUpyZFdKbGNtNWxkR1Z6TDNObGNuWnBZMlZoWTJOdmRXNTBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5dVlXMWxjM0JoWTJVaU9pSmtaV1poZFd4MElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WldOeVpYUXVibUZ0WlNJNkluQmxibWN0ZEc5clpXNHRhSEJ6Tm5jaUxDSnJkV0psY201bGRHVnpMbWx2TDNObGNuWnBZMlZoWTJOdmRXNTBMM05sY25acFkyVXRZV05qYjNWdWRDNXVZVzFsSWpvaWNHVnVaeUlzSW10MVltVnlibVYwWlhNdWFXOHZjMlZ5ZG1salpXRmpZMjkxYm5RdmMyVnlkbWxqWlMxaFkyTnZkVzUwTG5WcFpDSTZJamMxTlRNeVlUTmhMV1k1TXpZdE1URmxPQzA1TVdSaExUQXdNR015T1RjeE9XSmtZU0lzSW5OMVlpSTZJbk41YzNSbGJUcHpaWEoyYVdObFlXTmpiM1Z1ZERwa1pXWmhkV3gwT25CbGJtY2lmUS5hWVZzSkROYkFBWVU5bDJZUWtBSkpxNHZ5Z0hjSmVVYlkwa0lzOEtCRG81WFcwcS1XR3prc1BCMjdPTFFhWWpSd2lwRmNpLUdXM2lXZ2QtMDdVaWVkempJbnFyU3dWZExkRjhDSTJHaWFaT3FrNERGWTM2ZDd3RFFNY0xObDJ1aHFxbEVjWk1lQ2VWbnMwaHQxNGJQRFpGaFJiUmFTbWRTek9UT3NoclFFQ3UzRzBNRkVWVFg5V3U3em5Rdm5Kd0hHU0dOQjF1MmZiSVFkS0xMcHA1ZUlEWEZLaEMtZHVqUFNydmxITXVWRjdISmVvQ1dPWXNLYUdSTFN2dl82Z1BJZm5DeEY1eEZDajdzTmgzOHZha2g5cWpHcDBHUWJHUTBuclFNWGZCbjhKV3pzR2xtQ1Z6TjdMWjlBSF9HOEFwS0UzQjV3b1k2MXNsLTVtTTFrM19la2c=
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

我们来介绍一下data中的三个字段是怎么生成的

  • ca.crt:它来自于controller-manager的启动参数--root-ca-file指定的文件内容
  • namespace:该Secret的namespace,与其所属的ServiceAccount的namespace一致
  • token:把该字段解码后,你会发现其实它是一个JWT(可以把它进行base64解码后,拷贝到  jwt.io 网站查看原始内容),它的signature加密所使用的密钥是由controller-manager的启动参数--service-account-private-key-file所指定,它的payload如下,
{
 "iss": "kubernetes/serviceaccount",
 "kubernetes.io/serviceaccount/namespace": "default",
 "kubernetes.io/serviceaccount/secret.name": "peng-token-hps6w",
 "kubernetes.io/serviceaccount/service-account.name": "peng",
 "kubernetes.io/serviceaccount/service-account.uid": "75532a3a-f936-11e8-91da-000c29719bda",
 "sub": "system:serviceaccount:default:peng"
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
2.2 创建Pod,使用ServiceAccount

我们按照前面yaml文件创建Pod,那么apiserver将会把该ServiceAccount的Secret挂载到Pod的路径/var/run/secrets/kubernetes.io/servicaccount下。创建之后我们kubectl get一下这个Pod,发现yaml文件如下

apiVersion: v1
kind: Pod
metadata:
  name: pod-nginx
  namespace: default
spec:
  containers:
  - image: nginx:1.11.5
    name: pod-nginx
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: peng-token-hps6w
      readOnly: true
  serviceAccount: peng
  serviceAccountName: peng
  volumes:
  - name: peng-token-hps6w
    secret:
      defaultMode: 420
      secretName: peng-token-hps6w
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

我们去到这个pod的内部,查看目录/var/run/secrets/kubernetes.io/servicaccount下的内容,发现该目录下有三个文件ca.crt、namesapce与token,且它们的内容就是secret中的对应字段的值通过base64解码得到的

$ pwd
/var/run/secrets/kubernetes.io/servicaccount
$ ls
ca.crt namespace token
$ cat namespace
default
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

这个时候,我们就可以按照前面给的方法从Pod中去访问apiserver,如下

#!/bin/bash

ServiceAccountToken=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CaCrt="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"

curl -H "Authoriztion: Bearer ${ServiceAccountToken}" --cacert ${CaCrt} https://kubernetes:443/api/v1/nodes
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
2.3 认证

apiserver收到了Pod里面的curl进程发来的请求,Header中包含了"Authoriztion: Bearer ${ServiceAccountToken}"。那么apiserver如何认证这个ServiceAccountToken呢?首先,apiserver必须要开启serviceaccount的准入机制,即在启动参数中声明--admission-control=*,serviceaccount。然后,然后apiserver的serviceaccount认证器拿到这个ServiceAccountToken后,利用参数--service-account-key-file指定的公钥文件对这个token进行验证。验证通过后,该token就会被认证为subject: { kind: ServiceAccount, name: peng, namesapce: default}以及subject: { kind: User, name: system:serviceaccount:default:peng}

2.4 授权

在第二步中,我们创建了相应的授权规则,对subject: { kind: ServiceAccount, name: peng, namesapce: default}进行了授权,所以能够curl的请求能够通过。当然,由于该token也会被认证为subject: { kind: User, name: system:serviceaccount:default:peng},我们也可以对该用户授权,curl的访问也是可以通过的。