跳至内容

一只安静的猫

想啥呢

  • 首页
  • 文章列表
  • 打赏
  • 收藏
  • 留言板

近期文章

  • 一次网络延迟高的问题排查
  • runc 的输入输出
  • kubelet PLEG 的实现与优化
  • Traceroute 的实现原理
  • Ping 与 ICMP 协议

近期评论

  • 猫友 发表在《一次网络延迟高的问题排查》
  • jiangpengfei 发表在《留言板》
  • a 发表在《留言板》
  • a 发表在《留言板》
  • 一次网络延迟高的问题排查 - 一只安静的猫 发表在《linux 网络数据包接收流程(一)》

分类目录

  • c++ (11)
    • levelDB源码阅读 (7)
  • go (10)
  • java (13)
    • java虚拟机学习 (9)
  • k8s (29)
    • controller的实现 (2)
  • linux (20)
  • php (8)
    • FastCGI协议 (1)
  • tensorflow (1)
  • 分布式系统 (4)
    • etcd (1)
    • 分布式存储 (2)
  • 前端 (2)
  • 图像相关 (2)
  • 存储 (1)
  • 容器技术 (8)
  • 工作 (9)
  • 数据库 (4)
    • redis (3)
  • 架构设计 (4)
  • 编程相关 (2)
  • 网络 (15)
    • 网络协议 (4)
  • 计算机 (2)
  • 问题 (5)

归档

  • 2023 年 7 月
  • 2023 年 3 月
  • 2023 年 2 月
  • 2023 年 1 月
  • 2022 年 12 月
  • 2022 年 11 月
  • 2022 年 2 月
  • 2021 年 7 月
  • 2021 年 6 月
  • 2021 年 5 月
  • 2021 年 3 月
  • 2020 年 12 月
  • 2020 年 10 月
  • 2020 年 9 月
  • 2020 年 7 月
  • 2020 年 6 月
  • 2020 年 5 月
  • 2020 年 4 月
  • 2020 年 3 月
  • 2020 年 2 月
  • 2020 年 1 月
  • 2019 年 12 月
  • 2019 年 11 月
  • 2019 年 10 月
  • 2019 年 9 月
  • 2019 年 7 月
  • 2019 年 6 月
  • 2018 年 12 月
  • 2018 年 11 月
  • 2018 年 10 月
  • 2018 年 9 月
  • 2018 年 8 月
  • 2018 年 7 月
  • 2018 年 5 月
  • 2018 年 4 月
  • 2018 年 3 月
  • 2018 年 2 月
  • 2018 年 1 月
  • 2017 年 12 月
  • 2017 年 11 月
  • 2017 年 8 月
  • 2017 年 7 月
  • 2017 年 6 月
  • 2017 年 5 月
  • 2017 年 4 月

外链

赖同学

小亮

我的github

工具

标签: service account

k8s 1.24 ServiceAccount Token 的行为变化

起因

有一个 CNI 组件以 DaemonSet 的方式运行在所有的 node 上,这个 CNI Pod 会将自己的 Service Account Token 转换成 kubeconfig 并存储到主机的目录下。当 kubelet 调用 cni 插件时,cni 插件会使用这个 kubeconfig 去获取集群 Pod 的一些信息。

在 k8s 1.24 上出现了问题,当 CNI Pod 重启后,使用生成的 kubeconfig 就会返回 Unauthorized 的错误,即这个 token 已经过不了 APIServer 的认证了。

原因

k8s 1.24 上,ServiceAccount(下文缩写为 SA) 的 token 生成逻辑已经发生了变化,不再会自动为 SA 生成 token 并保存到 secret 中,Pod 中使用 token 时也不会再挂载这个 secret。当 Pod 使用 SA 时,默认行为如下:

  1. Pod 创建出来后,在 admission 阶段,有一个 serviceaccount admission 会为 Pod 挂载 token,路径同样还是在 /var/run/secrets/kubernetes.io/serviceaccount 下。但是 volume 字段不再是通过 secret,而是通过 projected。
projected:
  defaultMode: 420
  sources:
    # source 类型是 serviceAccountToken
  - serviceAccountToken:
      expirationSeconds: 3607
      path: token
  - configMap:
      items:
      - key: ca.crt
        path: ca.crt
      name: kube-root-ca.crt
  - downwardAPI:
      items:
      - fieldRef:
          apiVersion: v1
          fieldPath: metadata.namespace
        path: namespace
  1. Pod 调度到 Node 上后,kubelet 中的 projected volume mounter 会根据 volumesMount 中的 volume 类型,为 Pod 挂载对应的文件。当发现存在 ServiceAccountToken 类型的 projected source 时,就会调用 apiserver 的 TokenRequest 接口,为当前 Pod 请求临时的 Token。并且这个 token 的有效期只有 3607s。kubelet 会自动刷新这个 token 来保证它不会过期。
    case source.ServiceAccountToken != nil:
                tp := source.ServiceAccountToken
    
                // When FsGroup is set, we depend on SetVolumeOwnership to
                // change from 0600 to 0640.
                mode := *s.source.DefaultMode
                if mounterArgs.FsUser != nil || mounterArgs.FsGroup != nil {
                    mode = 0600
                }
    
                var auds []string
                if len(tp.Audience) != 0 {
                    auds = []string{tp.Audience}
                }
                tr, err := s.plugin.getServiceAccountToken(s.pod.Namespace, s.pod.Spec.ServiceAccountName, &authenticationv1.TokenRequest{
                    Spec: authenticationv1.TokenRequestSpec{
                        Audiences:         auds,
                        ExpirationSeconds: tp.ExpirationSeconds,
                        BoundObjectRef: &authenticationv1.BoundObjectReference{
                            APIVersion: "v1",
                            Kind:       "Pod",
                            Name:       s.pod.Name,
                            UID:        s.pod.UID,
                        },
                    },
                })
                if err != nil {
                    errlist = append(errlist, err)
                    continue
                }
                payload[tp.Path] = volumeutil.FileProjection{
                    Data:   []byte(tr.Status.Token),
                    Mode:   mode,
                    FsUser: mounterArgs.FsUser,
                }
    

    这样带来的好处就是 service account 默认不再会有永久性 token,而是每个 Pod 有一个临时的 token,这个 token 默认有效期是 3607s,由 kubelet 自动刷新。并且当 Pod 删除后,该 token 也会自动失效。这在安全性上带来了很大的提升。

    解决

    为了和之前组件的行为保持一致,需要保证这个 token 是永久有效的。最简单的解决办法就是手动创建 service account 的 token secret。例如:

    apiVersion: v1
    kind: Secret
    # 表示这个 secret 类型
    type: kubernetes.io/service-account-token
    metadata:
      name: mycontroller
      namespace: kube-system
      annotations:
        # service account 名称
        kubernetes.io/service-account.name: "mycontroller"
    

    k8s 的 tokens-controller 在 watch 到该 secret 时,会发现 ca, namespace, token 字段均为空,因此会自动为该 secret 填充这些字段。这样我们就获得了永久性的 token,并使用该 token 生成 kubeconfig 了。

    func (e *TokensController) secretUpdateNeeded(secret *v1.Secret) (bool, bool, bool) {
        caData := secret.Data[v1.ServiceAccountRootCAKey]
        needsCA := len(e.rootCA) > 0 && !bytes.Equal(caData, e.rootCA)
    
        needsNamespace := len(secret.Data[v1.ServiceAccountNamespaceKey]) == 0
    
        tokenData := secret.Data[v1.ServiceAccountTokenKey]
        needsToken := len(tokenData) == 0
    
        return needsCA, needsNamespace, needsToken
    }
    

Token 是如何做身份认证的

service account token 在不同版本下的行为不同,那么 token 本身又是如何做身份认证的呢?

token 是一个符合 JWT 规范的字符串。

对于永久性 token 来说,其中保存了 service account 的信息。

{
  "iss": "kubernetes/serviceaccount",
  "kubernetes.io/serviceaccount/namespace": "kube-system",
  "kubernetes.io/serviceaccount/secret.name": "mycontroller",
  "kubernetes.io/serviceaccount/service-account.name": "mycontroller",
  "kubernetes.io/serviceaccount/service-account.uid": "2f0ab840-064c-4168-b9b2-932c361e13d6",
  "sub": "system:serviceaccount:kube-system:mycontroller"
}

apiserver 在获取到这个 token 后,根据 JWT 的规范对内容进行完整性校验。校验通过后就根据 token 中 service account 进行认证鉴权了。

对于临时性(pod) token 来说,内容就稍有不同了。

{
  "aud": [
    "https://kubernetes.default.svc.cluster.local"
  ],
  "exp": 1705344168,
  "iat": 1673808168,
  "iss": "https://kubernetes.default.svc.cluster.local",
  "kubernetes.io": {
    "namespace": "kube-system",
    "pod": {
      "name": "mycontroller-lr99n",
      "uid": "f8a3c6c7-c41c-4a33-9329-f40d208a03e6"
    },
    "serviceaccount": {
      "name": "mycontroller",
      "uid": "2f0ab840-064c-4168-b9b2-932c361e13d6"
    },
    "warnafter": 1673811775
  },
  "nbf": 1673808168,
  "sub": "system:serviceaccount:kube-system:mycontroller"
}

可以看到 token 中除了 service account 的信息,还有 pod 的信息。这样 token 的有效期是由 pod 的生命周期以及 nbf, exp 来确定了。nbf 代表 Not valid before,exp 代表 Expiration time,都是使用 unix time 来保存的。并且在 pod 删除后,token 就自动失效了。同时鉴权还是使用 service account 进行。

发布于 2023年1月18日2023年7月5日分类 k8s标签 k8s、service account于k8s 1.24 ServiceAccount Token 的行为变化留下评论
皖ICP备2020019260号-1 自豪地采用WordPress