什么是服务账号?

服务账号是在 Kubernetes 中一种用于非人类用户的账号,在 Kubernetes 集群中提供不同的身份标识。 应用 Pod、系统组件以及集群内外的实体可以使用特定 ServiceAccount 的凭据来将自己标识为该 ServiceAccount。 这种身份可用于许多场景,包括向 API 服务器进行身份认证或实现基于身份的安全策略。

如何使用服务账号

要使用 Kubernetes 服务账号,你需要执行以下步骤:

  1. 使用像 kubectl 这样的 Kubernetes 客户端或定义对象的清单(manifest)创建 ServiceAccount 对象。
  2. 使用鉴权机制(如 RBAC)为 ServiceAccount 对象授权。
  3. 在创建 Pod 期间将 ServiceAccount 对象指派给 Pod。

如果你所使用的是来自外部服务的身份,可以获取 ServiceAccount token,并在该服务中使用这一令牌。

ServiceAccount token 变化

  1. 1.20(含 1.20)之前的版本,在创建 sa 时会自动创建一个 secret,然后这个会把这个 secret 通过投射卷挂载到 pod 里,该 secret 里面包含的 token 是永久有效的。
  2. 1.21~1.23 版本,在创建 sa 时也会自动创建 secret,但是在 pod 里并不会使用 secret 里的 token,而是由 kubelet 到 TokenRequest API 去申请一个 token,该 token 默认有效期为一年,但是 pod 每一个小时会更新一次 token。
  3. 1.24 版本及以上,在创建 sa 时不再自动创建 secret 了,只保留由 kubelet 到 TokenRequest API 去申请 token。

创建 ServiceAccount token

创建一个 ServiceAccount

创建一个 sa-demo 的 ServiceAccount,文件名为 build-robot.yaml:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: build-robot
automountServiceAccountToken: false

创建并查看资源:

# kubectl create -f build-robot.yaml 
serviceaccount/build-robot created
# kubectl get serviceaccounts build-robot 
NAME          SECRETS   AGE
build-robot   0         11s

使用 RBAC 为 ServiceAccount 对象授权

这里为做演示,直接使用 cluster-admin 这个角色:

kubectl create clusterrolebinding build-robot-clusterrolebinding --clusterrole=cluster-admin --serviceaccount=default:build-robot

如果需要绑定其他权限,则可以自定义 Role 或 ClusterRole,再绑定到这个 ServiceAccount 上。

创建 token

token 分为临时请求 token 和永久 token。相对于永久 token,临时请求token 更为安全。临时请求 token 挂载与 Pod 内,kubelet 组件会替 Pod 请求 token 并将其保存起来;通过将 token 存储到一个可配置的路径以使之在 Pod 内可用;kubelet 会在 token 存在期达到其 TTL 的 80% 的时候或者令牌生命期超过 24 小时的时候主动请求将其轮换掉,并且能够在挂载它们的 Pod 被删除时自动被废弃。

创建临时 token

要启用和使用 request token 映射,必须向kube-apiserver指定以下每个命令行参数:

  1. --service-account-issuer:定义 ServiceAccount token 发放者是谁。
  2. --service-account-key-file:指定公钥文件路径。
  3. --service-account-signing-key-file:指定私钥文件路径。
  4. --api-audiences(可以省略):为 ServiceAccount token 定义其受众。

使用 nginx 镜像来创建一个名为 vault-token 的 token,挂载名为 build-robot 的 ServiceAccount,文件名为 pod-projected-svc-token.yaml:

apiVersion: v1
kind: Pod 
metadata:
  name: nginx-1
spec:
  containers:
  - image: nginx:1.22.0
    name: nginx-1
    volumeMounts:
    - mountPath: /var/run/secrets/tokens
      name: vault-token
  serviceAccountName: build-robot
  volumes:
  - name: vault-token
    projected:
      sources:
      - serviceAccountToken:
          path: vault-token
          expirationSeconds: 7200
      - configMap:
          items:
            - key: ca.crt
              path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
            - fieldRef:
                apiVersion: v1
                fieldPath: metadata.namespace
              path: namespace

创建并查看:

# kubectl create -f pod-projected-svc-token.yaml 
pod/nginx-1 created
# kubectl get -f pod-projected-svc-token.yaml 
NAME      READY   STATUS    RESTARTS   AGE
nginx-1   1/1     Running   0          12s

创建资源后,会在 /var/run/secrets/tokens/ 下创建一个 vault-token 文件:

# kubectl exec -it nginx-1 -- ls /var/run/secrets/tokens/
ca.crt	namespace  vault-token
# kubectl exec -it nginx-1 -- cat /var/run/secrets/tokens/vault-token
eyJhbGciOiJSUzI1NiIsImtpZCI6Impib1RRNHIzMGpfMmpWXzBXS3FNT0FtdFlBNTRSbTdSdEJfbFNEQ25jdVkifQ.eyJhdWQiOlsidmF1bHQiXSwiZXhwIjoxNzI1MDM5NjQxLCJpYXQiOjE3MjUwMzI0NDEsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiZDQ4MjJhNGUtZGRlMS00NDQxLTllY2UtZjNiNjg1MDNmYjdlIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwibm9kZSI6eyJuYW1lIjoiazhzIiwidWlkIjoiMmU4NWMwZWMtZmEyMi00NjRkLWI2ZGUtMDRkMGFlMzA5YTU5In0sInBvZCI6eyJuYW1lIjoibmdpbngiLCJ1aWQiOiI3OGM0ZDA5Ny1hYTRiLTQ0MmYtYjk5OS01YjJmZjYzNmMwNWYifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImJ1aWxkLXJvYm90IiwidWlkIjoiMzQxNTIxYjQtYzA0Ny00ZGRmLTgyNGYtOTMxZjEzZGJhNmVhIn19LCJuYmYiOjE3MjUwMzI0NDEsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmJ1aWxkLXJvYm90In0.NXigSqMdBzPowK2Zoqc9bsbS3NgPgq7IUUw9ohpSBjz9ies3ydxvnRkdIqYSOA_pAn4QNORybHWOEIXq69ic7osXjuY6xVN54Ct6_KJyIF2gp24VKLEV7B_iLbh575k5ZjhPa00HHgppCe_cnXn9IoBP5V8oX2YLm1Jo4rld6QGoKNFm54VGL-46m6w559_zMLsKBorSY5k1F7B9NeZ9rAgcu_Aa3KiTej_lnAyWbWQPAMgx0t7LO5IW55EU6FhhZcicRwq8fOfUTM9lutHqUH5D8_gONMzaS4sbhkgyHMZeki0Po4i6oJe_seSwP968gbhcCPfm0Q40pE6U6jn6hA

把这个 token 复制到 https://jwt.io/ 上解析这个 token:
ServiceAccountToken-1.png

其中里面有个 exp 字段和 iax 字段,分别指过期时间和生效时间,刚好相差 7200 秒,就是 2 个小时。符合上面 yaml 指定的 expirationSeconds 值。

使用 token 去访问 api Server

进入 pod 中:

# kubectl exec -it nginx-1 -- bash
root@nginx-1:/# ls /var/run/secrets/tokens/
ca.crt	namespace  vault-token
root@nginx-1:/# export CURL_CA_BUNDLE=/var/run/secrets/tokens/ca.crt
root@nginx-1:/# TOKEN=$(cat /var/run/secrets/tokens/vault-token)
root@nginx-1:/# curl -H "Authorization: Bearer $TOKEN" https://kubernetes.default/api/
{
  "kind": "APIVersions",
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "192.168.0.2:6443"
    }
  ]
}

能正常访问到 api Server。

创建长期 token

如果需要为 ServiceAccount 获得一个 API 令牌,你可以创建一个新的、带有特殊注解 kubernetes.io/service-account.name 的 Secret 对象。

kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: build-robot-secret
  annotations:
    kubernetes.io/service-account.name: build-robot
type: kubernetes.io/service-account-token
EOF

然后通过kubectl describe即可获取一个长期的 token:

# kubectl describe secrets build-robot-secret
Name:         build-robot-secret
Namespace:    default
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: build-robot
              kubernetes.io/service-account.uid: 341521b4-c047-4ddf-824f-931f13dba6ea

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1107 bytes
namespace:  7 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6Impib1RRNHIzMGpfMmpWXzBXS3FNT0FtdFlBNTRSbTdSdEJfbFNEQ25jdVkifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImJ1aWxkLXJvYm90LXNlY3JldCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJidWlsZC1yb2JvdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjM0MTUyMWI0LWMwNDctNGRkZi04MjRmLTkzMWYxM2RiYTZlYSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmJ1aWxkLXJvYm90In0.ZEd-xx8khQmuAKY7gryuHMgbqrvAQwctJA7ztXyCd9eWweUOj99wi2ILHUckt7-MWCu47ad9pZ1pSsUBVnXBdWpjeUzOgX_s7FrMNz8M8m8EfWWv1xxp9cdBQWdwqDNBxa4X6isCS-OBdYMSwcHgvAKFVOkZ1rMjXXgMnYXs_DAoqSNlFnSSJLGUpa6zowvpzNjCn5chfqS1rUwBDpBvB_vg-WU5qnaXRAhNRY22shjtY230fQO2Vc53244mM_1mlBwlWzIjLoFKXZv-v5xhPPDAbIsINe8YmC6pBkSIXwqCHRo1Y1Chh2Are6U6ltWl2sLLAHSdoNiJtYgpos5Aug

把这个 token 拿到 https://jwt.io/ 上解析这个 token:
ServiceAccountToken-2.png

已经没了exp 字段。

使用 token 去访问 api Server

创建一个 pod 把 build-robot-secret 给 nginx 使用,文件名为pod-token-forever-valid.yaml:

apiVersion: v1
kind: Pod 
metadata:
  name: nginx-2
spec:
  containers:
  - image: nginx:1.22.0
    name: nginx-2
    volumeMounts:
    - name: secret-volume
      mountPath: /etc/secret
      readOnly: true
  volumes:
  - name: secret-volume
    secret:
      secretName: build-robot-secret

由于在创建 ServiceAccount 的时候,指定了automountServiceAccountToken: false,所以不会自动挂载 secret,需要手动挂载上去。

如果没指定 automountServiceAccountToken,默认为 true,则不用手动挂载。具体挂载目录可通过创建 pod 后,通过 describe pod 进行查看。

创建并查看资源:

# kubectl create -f pod-token-forever-valid.yaml 
pod/nginx-2 created
# kubectl get -f pod-token-forever-valid.yaml 
NAME      READY   STATUS    RESTARTS   AGE
nginx-2   1/1     Running   0          12s

进入到 pod 中使用 token 去访问 api server:

# kubectl exec -it nginx-2 -- bash
root@nginx-2:/# ls /etc/secret/
ca.crt	namespace  token
root@nginx-2:/# export CURL_CA_BUNDLE=/etc/secret/ca.crt
root@nginx-2:/# TOKEN=$(cat /etc/secret/token)
root@nginx-2:/# curl -H "Authorization: Bearer $TOKEN" https://kubernetes.default/api/
{
  "kind": "APIVersions",
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "192.168.0.2:6443"
    }
  ]
}

经测试能成功访问。

参考:
https://mp.weixin.qq.com/s/F0V8nyo3LtATFmS7pHuxXw
https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/service-accounts-admin/
https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/configure-service-account/