Amazon EKS Pod Identity 新特性探索

本文将简单探索一下 AWS 本周新发布的名为 Amazon EKS Pod Identity 的 EKS 安全特性。

功能介绍

Amazon EKS Pod Identity 是 AWS 对 EKS 原有的 IAM roles for service accounts (IRSA) 功能的补充,通过新增的 EKS Pod Identity 功能,用户可以用更简便的方式实现为 Pod 安全的授予 AWS API 访问权限, 并且所有的配置管理操作都可以通过 AWS API 或者控制台完成。

使用方法

用户只需执行如下操作, 即可基于 EKS Pod Identity 特性实现为 Pod 内应用安全的授予 AWS API 访问权限。

  1. 首先,需要创建一个 IAM 角色,确保该角色信任 pods.eks.amazonaws.com 。角色信任策略示例如下。
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "pods.eks.amazonaws.com"
            },
            "Action": [
                "sts:AssumeRole",
                "sts:TagSession"
            ]
        }
    ]
}
  1. 其次,需要在 EKS 集群内安装 eks-pod-identity-agent 组件(支持通过控制台安装)。
aws eks create-addon \
--cluster-name <CLUSTER_NAME> \
--addon-name eks-pod-identity-agent \
--addon-version v1.0.0-eksbuild.1
  1. 然后,需要配置应用 Pod 所使用的 Service Account 与 AWS IAM 角色之间的关联关系, 允许使用该 Service Account 的应用扮演特定的 IAM 角色(支持通过控制台配置)。
aws eks create-pod-identity-association \
  --cluster-name <CLUSTER_NAME> \
  --namespace <NAMESPACE> \
  --service-account <SERVICE_ACCOUNT_NAME> \
  --role-arn <IAM_ROLE_ARN>
  1. 最后,应用程序需要更新使用最新的支持 EKS Pod Identity 特性的 AWS SDK , 并且代码里需要使用 SDK 提供的 默认凭证搜索逻辑 或者显式调用 EKS Pod Identity 依赖的 Container credential provider

工作流程

EKS Pod Identity 特性的工作流程如下。

image

  1. 当用户/Controller 向 apiserver 提交 Pod 时,会触发 eks-pod-identity-webhook 的 mutating webhook 流程。
  2. eks-pod-identity-webhook 的 mutating webhook 流程会为 Pod 挂载 service account oidc token 文件以及配置环境变量 (AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE, AWS_CONTAINER_CREDENTIALS_FULL_URI )。
 - env:
  - name: AWS_STS_REGIONAL_ENDPOINTS
    value: regional
  - name: AWS_DEFAULT_REGION
    value: us-west-2
  - name: AWS_REGION
    value: us-west-2
  - name: AWS_CONTAINER_CREDENTIALS_FULL_URI
    value: http://169.254.170.23/v1/credentials
  - name: AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE
    value: /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token
  volumeMounts:
  - mountPath: /var/run/secrets/pods.eks.amazonaws.com/serviceaccount
    name: eks-pod-identity-token
    readOnly: true
volumes:
- name: eks-pod-identity-token
  projected:
    defaultMode: 420
    sources:
    - serviceAccountToken:
        audience: pods.eks.amazonaws.com
        expirationSeconds: 86400
        path: eks-pod-identity-token
  1. Pod 容器内的应用使用的 AWS SDK 将使用通过环境变量 AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE 获取的 service account oidc token 访问环境变量 AWS_CONTAINER_CREDENTIALS_FULL_URI 指向的地址 (http://169.254.170.23/v1/credentials)获取 AWS sts token。
$ curl $AWS_CONTAINER_CREDENTIALS_FULL_URI -H "Authorization: $(cat $AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE)" 2>/dev/null |jq
{
  "AccessKeyId": "ASXXXXXXXXXXXXX",
  "SecretAccessKey": "+j5XXXXXXXX",
  "Token": "IQoJb3JpXXXXXXX",
  "AccountId": "5XXXXXXXXXXX",
  "Expiration": "2023-12-03T13:13:26Z"
}
  1. AWS_CONTAINER_CREDENTIALS_FULL_URI 指向的其实是 Pod 所在节点上部署的 eks-pod-identity-agent 组件 Pod 所暴露的服务。 eks-pod-identity-agent 收到请求后,将使用传递过来的 oidc token 访问 EKS 新增的 AssumeRoleForPodIdentity API 获取所需的 AWS sts token,然后将获取到的 sts token 返回给客户端。
$ aws eks-auth assume-role-for-pod-identity --cluster-name test --token $(cat $AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE) |jq
{
  "subject": {
    "namespace": "default",
    "serviceAccount": "default"
  },
  "audience": "pods.eks.amazonaws.com",
  "podIdentityAssociation": {
    "associationArn": "arn:aws:eks:us-west-2:5XXXXXXXXXXX:podidentityassociation/test/a-6aaXXXXXXXXXXXXXX",
    "associationId": "a-6aaXXXXXXXXXXXXXX"
  },
  "assumedRoleUser": {
    "arn": "arn:aws:sts::5XXXXXXXXXXX:assumed-role/test-eks-pod-identity/eks-test-XXXXXXXXXXXXXXXX",
    "assumeRoleId": "ARXXXXXXXXXXXXXXXXXXX:eks-test-XXXXXXXXXXXXXXXXXX"
  },
  "credentials": {
    "sessionToken": "IQoXXXXX",
    "secretAccessKey": "nR4XXXXXX",
    "accessKeyId": "ASXXXXXXXXXXXXXXXXXX",
    "expiration": "2023-12-03T13:37:22+00:00"
  }
}
  1. 应用调用的 AWS SDK 使用获取到的 sts token 访问应用所需的 AWS 云产品 API。

这个流程中有几个关键的组件和信息需要重点关注,下面将逐个说明。

eks-pod-identity-webhook

EKS Pod Identity 功能直接复用 IRSA 功能所依赖的 eks-pod-identity-webhook 组件, 使用这个组件实现为 Pod 自动注入所需的 oidc token 配置以及环境变量配置。并且, EKS 集群控制面中部署的 eks-pod-identity-webhook 为 EKS Pod Identity 做了特殊优化, 只有当对应的 service account 未包含 IRSA 相关配置,并且存在与之关联 的 IAM 角色信息(通过前面的使用方法中介绍的方法关联角色)时,才会为 Pod 注入 EKS Pod Identity 相关配置。

eks-pod-identity-webhook 为了支持 EKS Pod Identity 所作的修改可以参考开源实现(开源实现缺少判断存在关联角色的逻辑) 对应的 PR #189 以及 #196

eks-pod-identity-agent

官方推荐的 EKS Pod Identity 实现方案依赖在集群里安装名为 eks-pod-identity-agent 的组件。该组件的工作负载 YAML 如下。

apiVersion: apps/v1
kind: DaemonSet
metadata:
  annotations:
    deprecated.daemonset.template.generation: "1"
  creationTimestamp: "2023-12-03T04:24:15Z"
  generation: 1
  labels:
    app.kubernetes.io/instance: eks-pod-identity-agent
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: eks-pod-identity-agent
    app.kubernetes.io/version: 0.0.25
    helm.sh/chart: eks-pod-identity-agent-1.0.0
  name: eks-pod-identity-agent
  namespace: kube-system
  resourceVersion: "8978"
  uid: 08c03e91-69d4-460b-8acb-b9f9f546dd87
spec:
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app.kubernetes.io/instance: eks-pod-identity-agent
      app.kubernetes.io/name: eks-pod-identity-agent
  template:
    metadata:
      creationTimestamp: null
      labels:
        app.kubernetes.io/instance: eks-pod-identity-agent
        app.kubernetes.io/name: eks-pod-identity-agent
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/os
                operator: In
                values:
                - linux
              - key: kubernetes.io/arch
                operator: In
                values:
                - amd64
                - arm64
              - key: eks.amazonaws.com/compute-type
                operator: NotIn
                values:
                - fargate
      containers:
      - args:
        - --port
        - "80"
        - --cluster-name
        - test
        - --probe-port
        - "2703"
        command:
        - /go-runner
        - /eks-pod-identity-agent
        - server
        env:
        - name: AWS_REGION
          value: us-west-2
        image: 602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/eks-pod-identity-agent:0.0.25
        imagePullPolicy: Always
        livenessProbe:
          failureThreshold: 3
          httpGet:
            host: localhost
            path: /healthz
            port: probes-port
            scheme: HTTP
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 10
        name: eks-pod-identity-agent
        ports:
        - containerPort: 80
          name: proxy
          protocol: TCP
        - containerPort: 2703
          name: probes-port
          protocol: TCP
        readinessProbe:
          failureThreshold: 30
          httpGet:
            host: localhost
            path: /readyz
            port: probes-port
            scheme: HTTP
          initialDelaySeconds: 1
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 10
        resources: {}
        securityContext:
          capabilities:
            add:
            - CAP_NET_BIND_SERVICE
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      hostNetwork: true
      initContainers:
      - command:
        - /go-runner
        - /eks-pod-identity-agent
        - initialize
        image: 602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/eks-pod-identity-agent:0.0.25
        imagePullPolicy: Always
        name: eks-pod-identity-agent-init
        resources: {}
        securityContext:
          privileged: true
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      priorityClassName: system-node-critical
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
  updateStrategy:
    rollingUpdate:
      maxSurge: 0
      maxUnavailable: 10%
    type: RollingUpdate
status:
  currentNumberScheduled: 1
  desiredNumberScheduled: 1
  numberAvailable: 1
  numberMisscheduled: 0
  numberReady: 1
  observedGeneration: 1

eks-pod-identity-agent 组件具有如下特点:

  • 依赖节点 IAM 角色被授予如下 IAM 权限策略。
{
   "Version": "2012-10-17",
   "Statement": [
       {
           "Effect": "Allow",
           "Action": [
               "eks-auth:AssumeRoleForPodIdentity",
           ],
           "Resource": "*"
       }
   ]
}
  • 组件 Pod 使用 hostNetwork
  • 组件内的服务将监听 80 和 2703 端口。
  • 其中 80 端口监听的 IPv4 地址为 169.254.170.23, IPv6 地址为 [fd00:ec2::23]
$ ss -anltp |grep eks-pod
LISTEN 0      4096        127.0.0.1:2703       0.0.0.0:*    users:(("eks-pod-identit",pid=3798,fd=5))
LISTEN 0      4096   169.254.170.23:80         0.0.0.0:*    users:(("eks-pod-identit",pid=3798,fd=4))
LISTEN 0      4096   [fd00:ec2::23]:80            [::]:*    users:(("eks-pod-identit",pid=3798,fd=3))
  • 组件将通过 init 容器在节点上创建一个 IPv4 地址为 169.254.170.23, IPv6 地址为 [fd00:ec2::23] 的网络接口 pod-id-link0
pod-id-link0: flags=195<UP,BROADCAST,RUNNING,NOARP>  mtu 1500
        inet 169.254.170.23  netmask 255.255.255.255  broadcast 0.0.0.0
        inet6 fe80::2078:53ff:fe2f:c723  prefixlen 64  scopeid 0x20<link>
        inet6 fd00:ec2::23  prefixlen 128  scopeid 0x0<global>
        ether 22:78:53:2f:c7:23  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 10  bytes 700 (700.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
  • init 容器还会设置下面这样的的路由规则,确保在本机处理发往 169.254.170.23 的流量。
169.254.170.23 dev pod-id-link0

AssumeRoleForPodIdentity

EKS 提供了一个新的 API AssumeRoleForPodIdentity , 用于使用 service account oidc token 获取扮演 service account 关联 IAM 角色的 sts token。

service account oidc token 中的 payload 的内容示例如下。

{
  "aud": [
    "pods.eks.amazonaws.com"
  ],
  "exp": 1701672885,
  "iat": 1701586485,
  "iss": "https://oidc.eks.us-west-2.amazonaws.com/id/DA16E524AXXXXXX",
  "kubernetes.io": {
    "namespace": "default",
    "pod": {
      "name": "test-596475c6d5-xgvml",
      "uid": "924e66b2-841e-4969-9fa7-a19f3f6f1029"
    },
    "serviceaccount": {
      "name": "default",
      "uid": "eca98cb1-ca2e-469b-8f9d-2be9f5a3354f"
    }
  },
  "nbf": 1701586485,
  "sub": "system:serviceaccount:default:default"
}

可以看到这个 oidc token 跟 IRSA 方案中的 oidc token 相比,除了 aud 不一样外(IRSA 中是 sts.amazonaws.com ), 其他内容都是一样的。

因此,据我猜测,AssumeRoleForPodIdentity 的后端逻辑可能类似下面这样:

  1. 首先,基于 oidc 协议解析和校验客户端传递过来的 oidc token 的有效性和合法性。
  2. 然后,基于用户配置的 service account 与 IAM 角色关联关系,获取需要扮演的角色信息。
  3. 最后,调用 sts API 扮演对应角色,获取客户端所需的 sts token。

service account 关联 IAM 角色

为了简化用户为 service account 关联 IAM 角色的操作,EKS 新增了一组用于管理 service account 与 IAM 角色关联关系的 API。 同时还增加了对应的 EKS 控制台操作页面,并且关联关系都存储在 EKS 侧,并没有去修改角色的信任策略。

权限控制

在 IRSA 方案中,我们需要在 IAM 角色的信任策略中配置允许的集群 oidc provider 以及 service account 信息。 但是,在 EKS Pod Identity 方案中,我们不再需要修改角色的信任策略,只需要使用前面介绍的方法使用 EKS 控制台或者 EKS API 即可完成控制角色能被哪些集群的哪些 service account 使用,这些关联关系存储在 EKS 侧,不再需要频繁的更新角色的信任策略。

通过这种方式, EKS Pod Identity 解决了 IRSA 中因为角色信任策略的内容大小限制导致一个角色只能被有限的几个 service account 关联使用的问题 (详见 #148 ), 在 EKS Pod Identity 方案中,一个角色可以与不限数量的 service account 进行关联。

attribute-based access control (ABAC)

EKS Pod Identity 方案还引入了新的基于属性的访问控制能力(attribute-based access control (ABAC)): 通过 EKS Pod Identity 方案获取到的 sts token 默认都携带了集群信息、命名空间以及 service account 等 tag 属性, 用户可以基于 IAM 提供的 tag 鉴权 特性, 在为角色配置权限策略时,实现基于属性的访问控制能力。

通过 EKS Pod Identity 获取的 sts token 默认携带了如下 tag :

  • eks-cluster-arn :当前集群的 ARN。
  • eks-cluster-name :当前集群的名称。
  • kubernetes-namespace: service account 所在的命名空间名称。
  • kubernetes-service-account :service account 的名称。
  • kubernetes-pod-name :使用 service account 的 pod 的名称。
  • kubernetes-pod-uid :使用 service account 的 pod 的 uid。
"tags": [
    {
        "key": "eks-cluster-arn",
        "value": "arn:aws:eks:us-west-2:5XXXXXXXXXXX:cluster/test"
    },
    {
        "key": "eks-cluster-name",
        "value": "test"
    },
    {
        "key": "kubernetes-namespace",
        "value": "default"
    },
    {
        "key": "kubernetes-service-account",
        "value": "default"
    },
    {
        "key": "kubernetes-pod-name",
        "value": "test-596475c6d5-xgvml"
    },
    {
        "key": "kubernetes-pod-uid",
        "value": "924e66b2-841e-4969-9fa7-a19f3f6f1029"
    }
]

我们在定义角色权限策略的时候,可以通过 ${aws:PrincipalTag/<tag-key>} 的方式 在权限策略的 Condition 配置中表示凭证属性中包含的特定 tag 的值。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:GetObjectTagging"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "s3:ExistingObjectTag/eks-cluster-name": "${aws:PrincipalTag/eks-cluster-name}"
                },
                "StringEqualsIfExists": {
                    "aws:ResourceTag/kubernetes-namespace": "s3-demo",
                    "aws:Resourcelag/kubernetes-service-account": "${aws:PrincipalTag/kubernetes-service-account}"
                }
            }
        }
    ]
}

通过上面这个示例权限策略,我们可以对通过 EKS Pod Identity 特性扮演角色获取的 sts token 的权限做如下限制:

  • 只允许访问存在名为 eks-cluster-name 的 tag 的 s3 object ,并且 tag 的值必须是当前 sts token 关联集群的名称。
  • 如果 s3 object 存在名为 kubernetes-namespace 的 tag,只允许访问这个 tag 的值是 s3-demo 的 object。
  • 如果 s3 object 存在名为 kubernetes-service-account 的 tag,只允许访问这个 tag 的值是当前 sts token 关联的 service account 名称的 object。

与 IRSA 的区别

根据上面介绍的信息,我们可以比较一下 EKS Pod Identity 与 IRSA 特性的区别,它们主要的区别如下:

比较项 EKS Pod Identity IRSA
节点 IAM 角色增加额外权限 需要增加 eks-auth:AssumeRoleForPodIdentity 权限 不需要
创建 IAM oidc provider 不需要 需要为每个集群创建一个 oidc provider
service account 关联角色 通过 EKS API/控制台 配置 service account annotation + 修改角色信任策略
一个角色可以关联的 service account 数量 无限制 角色信任策略有 4096 个字符限制, 最多关联 10 个左右 service account 或 集群
一个 service account 可以关联的角色数量 一个 无限制
角色信任策略修改次数 只需修改一次 确保信任 pods.eks.amazonaws.com 以及包含所需的 action 需要为每个关联的集群修改至少一次
基于 service account 相关属性进行访问控制 通过 ABAC 进行支持 不支持
集群类型 只支持 EKS 集群,不支持 AWS Outposts、 不支持 EKS Anywhere、AWS Fargate (Fargate)、自建集群 仅不支持 AWS Outposts
数据面依赖 kubelet + eks-pod-identity-agent kublet
稳定性风险 依赖集群控制面和数据面的稳定性 + EKS API; 同时还依赖 eks-pod-identity-agent 的稳定性; 应用 pod 如果先于 eks-pod-identity-agent pod 启动/就绪会出现短暂的无法获取到 sts token 的问题 (比如,同样配置了 hostNetwork 的 pod 在 自动伸缩场景可能会遇到这种情况,以及不建议 CNI、CSI 这些常常会先于 agent pod 启动的组件使用); 依赖集群控制面和数据面的稳定性 + STS API

BTW, 虽然 EKS Pod Identity 方案的官方教程和文档中都是说的需要依赖 eks-pod-identity-agent 这个组件, 但是从前面的内容中我们也可以看到:我们其实也可以在不安装 eks-pod-identity-agent 组件的情况下,使用该方案。 具体来说就是,我们可以通过在应用程序内直接访问 EKS 提供的 AssumeRoleForPodIdentity API 的方式来解除对该组件的依赖。


Comments