【EKS】Pod Identityの流れと仕組みを理解する

Proxy環境で Pod Identityを利用するにあたって、Pod Identityの流れや通信を詳しく調べる必要があったので、調査結果を残しておきます。

- 参考
    - [Amazon EKS Pod Identity: a new way for applications on EKS to obtain IAM credentials | AWSブログ](https://aws.amazon.com/jp/blogs/containers/amazon-eks-pod-identity-a-new-way-for-applications-on-eks-to-obtain-iam-credentials/)
    - [aws/eks-pod-identity-agent | GitHub](https://github.com/aws/eks-pod-identity-agent/tree/main)
    - [aws/amazon-eks-pod-identity-webhook | GitHub](https://github.com/aws/amazon-eks-pod-identity-webhook/)
    - [EKS Pod Identity の仕組みを理解する | AWS](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/pod-id-how-it-works.html)

# ■ 結論

Pod Identity を利用する場合、以下にアクセスできる必要があります。

- ロールを引き受けるPod  
    - `169.254.170.23` (pod-identity-agentのエンドポイント)  
    Pod Identityで関連付けを作成したIAMロールの認証情報を取得するのに必要
- pod-identity-agent  
    - `169.254.169.254` (インスタンスメタデータサービスのエンドポイント)  
    ノードロールの認証情報を取得するのに必要
    - `eks-auth.ap-northeast-1.api.aws` (EKS Authエンドポイント)  
   AssumeRoleForPodIdentity APIを呼び出すために必要
   [EKSサービスエンドポイント](https://docs.aws.amazon.com/ja_jp/general/latest/gr/eks.html)

# ■ Pod Identityの処理の流れ

![pod-identity.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/0b2e30f6-2f11-4d79-9607-3717df75de46.png)



## 1. Podに適用するIAM Roleの作成

Podに適用するIAM RoleにはEKS Pod Identityによる使用を許可する信頼ポリシーを設定します。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowEksAuthToAssumeRoleForPodIdentity",
            "Effect": "Allow",
            "Principal": {
                "Service": "pods.eks.amazonaws.com"
            },
            "Action": [
                "sts:AssumeRole",
                "sts:TagSession"
            ]
        }
    ]
}
```

## 2. Pod Identityの関連付けを作成

作成したIAMロールでPod Identityの関連付けを作成します。  
Pod Identityの関連付けでは、どのNamespaceのどのServiceAccountがIAM Roleを使用できるかを設定します。

```bash
aws eks create-pod-identity-association \
  --cluster-name my-cluster \
  --role-arn arn:aws:iam::111122223333:role/my-role \
  --namespace pod-identity-test \
  --service-account my-service-account
```

## 3. Podの作成

Pod Identityの関連付けで指定した Namespace と ServiceAccount でPodを起動します。

```yml:app.yaml
---
apiVersion: v1
kind: Namespace
metadata:
  name: pod-identity-test
---
apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: pod-identity-test
  name: my-service-account
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: pod-identity-test
  name: awscli
spec:
  selector:
    matchLabels:
      app: awscli
  replicas: 2
  template:
    metadata:
      labels:
        app: awscli
    spec:
      serviceAccountName: my-service-account
      containers:
      - name: aws-cli
        image: public.ecr.aws/aws-cli/aws-cli:2.17.37
        command: ["sleep", "infinity"]
```

```bash
kubectl apply -f app.yaml
```

## 4~6. EKS Pod Identity Webhookでマニフェストを変更

マニフェストがapplyされるとEKS Pod Identity Webhookが処理をインターセプトします。  

※  [aws/amazon-eks-pod-identity-webhook | GitHub](https://github.com/aws/amazon-eks-pod-identity-webhook/)
※ EKS Pod Identity WebhookはEKSのマネージドです。

EKS Pod Identity WebhookはPod Identityの関連付けにマニフェストで指定されたNamespaceとServiceAccountのが存在するかをチェックします。  
Pod Identity の関連付けが存在する場合、Podのマニフェストに以下を追加します。

```yml
    env:
    - name: AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE
      value: "/var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token"
    - name: AWS_CONTAINER_CREDENTIALS_FULL_URI
      value: "http://169.254.170.23/v1/credentials"
    volumeMounts:
    - mountPath: "/var/run/secrets/pods.eks.amazonaws.com/serviceaccount/"
      name: eks-pod-identity-token
  volumes:
  - name: eks-pod-identity-token
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          audience: pods.eks.amazonaws.com
          expirationSeconds: 86400 # 24 hours
          path: eks-pod-identity-token
```

- **AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE**
    - value: `/var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token`
    - IAM認証情報を取得するためのサービスアカウントトークンが格納されたファイルのパス
    このサービスアカウントトークンは EKS Auth APIにAWS認証情報を要求するときに利用されます。
    - EKS Auth のAssumeRoleForPod Identityへのリクエスト
    [internal/cloud/eksauth/service.go#L46 - aws/eks-pod-identity-agent | GitHub](https://github.com/aws/eks-pod-identity-agent/blob/v0.1.25/internal/cloud/eksauth/service.go#L46)
- **AWS_CONTAINER_CREDENTIALS_FULL_URI**
    - value: `http://169.254.170.23/v1/credentials`
    -  pod-identity-agent(Daemonset)がListenしているIAM認証情報取得URL  
pod-identity-agentは`hostNetwork: true` で起動されており、ホストと同じネットワークネームスペースを使っているため、ノード上に `pod-id-link0` というインターフェースを作成し、IP `169.254.170.23` を設定、ポート `80` でListenします。
        - pod-identity-agent は`hostNetwork: true` に設定されている
        [charts/eks-pod-identity-agent/templates/daemonset.yaml#L34 - eks-pod-identity-agent | GitHub](https://github.com/aws/eks-pod-identity-agent/blob/main/charts/eks-pod-identity-agent/templates/daemonset.yaml#L34)
        - ListenするIPとポートの定義  
        [configuration/config.go#L3 - aws/eks-pod-identity-agent | GitHub](https://github.com/aws/eks-pod-identity-agent/blob/v0.1.25/configuration/config.go#L3)
        - インターフェースの作成とIPの設定処理  
        [pkg/initalizer/executor.go#L30 - aws/eks-pod-identity-agent | GitHub](https://github.com/aws/eks-pod-identity-agent/blob/v0.1.25/pkg/initalizer/executor.go#L30)
        - HTTPサーバーの起動処理
        [cmd/server.go#L97 - aws/eks-pod-identity-agent | GitHub](https://github.com/aws/eks-pod-identity-agent/blob/v0.1.25/cmd/server.go#L97)


## 7~8. Podを起動

変換されたマニフェストでkubeletがPodを起動します。


## 9. Podからpod-identity-agentの認証エンドポイントにIAM認証情報を要求

Pod内でAWS SDKを実行すると、SDKは一時的なIAM認証情報を取得するため 、Authorizationヘッダにサービスアカウントトークンを含めて `AWS_CONTAINER_CREDENTIALS_FULL_URI` にリクエストを送信します。

Curlで再現するとこんな感じです。

```bash
# セッショントークンを変数に格納
SERVICE_ACCOUNT_TOKEN=$(cat $AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE)

# pod-identity-agentのエンドポイントにアクセスしてAWSの認証情報を取得
curl -H "Authorization: $SERVICE_ACCOUNT_TOKEN" "$AWS_CONTAINER_CREDENTIALS_FULL_URI" | jq .
# {
#   "AccessKeyId": "XXXXXXXXXXXXXXXXXXXX",
#   "SecretAccessKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
#   "Token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
#   "AccountId": "xxxxxxxxxxxxxx",
#   "Expiration": "2025-03-28T13:12:52Z"
# }
```

## 10~11. pod-identity-agentがIMDSにノードロールのIAM認証情報を要求

EKS AuthのAssumeRoleForPodIdentity APIを実行してAWSの認証情報を取得するには、IAMの `eks-auth:AssumeRoleForPodIdentity` 権限が必要となるため、pod-identity-agentはインスタンスメタデータサービス( IMDS)( `169.254.169.25` ) にアクセスしてノードロールの認証情報を取得します。

ノード上でインスタンスメタデータサービス( IMDS)( `169.254.169.25` ) への通信をtcpdumpでキャプチャすると以下のリクエストを確認できます。

```bash
tcpdump -tnlA -i any "tcp and dst port 80 and dst 169.254.169.254"
```

- `PUT /latest/api/token HTTP/1.1`
 IMDSv2 のトークン取得リクエスト
IMDSv2 では最初に PUT リクエストでトークンを取得し、それ以降のメタデータ取得にこのトークンを使う仕組みです。
- `GET /latest/meta-data/iam/security-credentials/`
IAM ロール名の一覧を取得しています
- `GET /latest/meta-data/iam/security-credentials/KarpenterNodeRole`
指定された IAM ロール (KarpenterNodeRole) のクレデンシャル情報を取得しています。
これには一時的なアクセスキー、シークレットキー、セッショントークンなどが含まれます。

```
IP 10.32.1.37.34244 > 169.254.169.254.http: Flags [P.], seq 0:277, ack 1, win 491, options [nop,nop,TS val 2945826404 ecr 3130553910], length 277: HTTP: PUT /latest/api/token HTTP/1.1
E..I.|@...x.
 .%.......P^,.....a....`}.....
...d..v6PUT /latest/api/token HTTP/1.1
Host: 169.254.169.254
User-Agent: aws-sdk-go-v2/1.26.1 os/linux lang/go#1.22.5 md/GOOS#linux md/GOARCH#amd64 ft/ec2-imds
Content-Length: 0
Amz-Sdk-Request: attempt=1; max=3
X-Aws-Ec2-Metadata-Token-Ttl-Seconds: 300
Accept-Encoding: gzip

................

IP 10.32.1.37.oracleas-https > 169.254.169.254.http: Flags [P.], seq 0:325, ack 1, win 491, options [nop,nop,TS val 2945826405 ecr 3130553911], length 325: HTTP: GET /latest/meta-data/iam/security-credentials/ HTTP/1.1
E..y.M@...j.
 .%.......P$..\........`......
...e..v7GET /latest/meta-data/iam/security-credentials/ HTTP/1.1
Host: 169.254.169.254
User-Agent: aws-sdk-go-v2/1.26.1 os/linux lang/go#1.22.5 md/GOOS#linux md/GOARCH#amd64 ft/ec2-imds
Amz-Sdk-Request: attempt=1; max=3
X-Aws-Ec2-Metadata-Token: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Accept-Encoding: gzip

................
IP 10.32.1.37.49431 > 169.254.169.254.http: Flags [P.], seq 0:355, ack 1, win 491, options [nop,nop,TS val 2945826406 ecr 3130553912], length 355: HTTP: GET /latest/meta-data/iam/security-credentials/KarpenterNodeRole HTTP/1.1
E...X.@.....
 .%.......P..:..*......`......
...f..v8GET /latest/meta-data/iam/security-credentials/KarpenterNodeRole HTTP/1.1
Host: 169.254.169.254
User-Agent: aws-sdk-go-v2/1.26.1 os/linux lang/go#1.22.5 md/GOOS#linux md/GOARCH#amd64 ft/ec2-imds
Amz-Sdk-Request: attempt=1; max=3
X-Aws-Ec2-Metadata-Token: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Accept-Encoding: gzip

................
```

※ 補足: [インスタンスメタデータサービス(IMDS)について](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html)

## 12~14. pod-identity-agentがEKS Auth APIエンドポイントにPodのIAM認証情報を要求

pod-identity-agentがノードロールの認証情報を取得できたら、EKS Auth ( `eks-auth.ap-northeast-1.api.aws` ) の AssumeRoleForPodIdentity API にサービスアカウントトークンとクラスタ名を指定してリクエストを送信し、Pod Identityでサービスアカウントに関連付けられたIAMロールの認証情報を取得します。

pod-identity-agentのソースコードだとこの辺りです
 [internal/cloud/eksauth/service.go#L46 - aws/eks-pod-identity-agent | GitHub](https://github.com/aws/eks-pod-identity-agent/blob/v0.1.25/internal/cloud/eksauth/service.go#L46)


※ 補足1: [EKS Authのエンドポイントの一覧](https://docs.aws.amazon.com/ja_jp/general/latest/gr/eks.html?utm_source=chatgpt.com#eks_region)
※ 補足2: [AssumeRoleForPodIdentity API (boto3)](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/eks-auth/client/assume_role_for_pod_identity.html)

## 15. PodがPod Identityに関連付けたIAMロールの認証情報を取得

Pod Identityに関連付けられたIAMロールの認証情報をレスポンスとして受け取ります。


```json
{
  "AccessKeyId": "XXXXXXXXXXXXXXXXXXXX",
  "SecretAccessKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "Token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "AccountId": "xxxxxxxxxxxxxx",
  "Expiration": "2025-03-28T13:12:52Z"
}
```

# ■ さらに調査


## pod-identity-agentのエンドポイント( `169.254.170.23` )について

### エンドポイントの存在確認

pod-identity-agentのDaemonsetは `hostNetwork: true` で起動しているため、エンドポイントはkubernetes上から確認できません。

まずはこのエンドポイントがホスト上に存在していて、pod-identity-agentプロセスがそのエンドポイントをListenしているかを確認します。

ノードにログインして下記コマンドを実行します。

```bash
# ノードが169.254.170.23:80 をListenしていることを確認します。
netstat -tuln | grep 169.254.170.23:80
# tcp        0      0 169.254.170.23:80       0.0.0.0:*               LISTEN

# どのプロセスがポート80を利用しているかを確かめる
lsof -i :80 
# COMMAND    PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
# eks-pod-i 2699 root    6u  IPv6  18423      0t0  TCP [fd00:ec2::23]:http (LISTEN)
# eks-pod-i 2699 root    7u  IPv4  19305      0t0  TCP ip-10-32-1-37.ap-northeast-1.compute.internal:http (LISTEN)

# プロセスツリーでpod-identity-agentがポートを利用しているプロセスであることを確認
pstree -pa
#  |-containerd-shim,1568 -namespace k8s.io -id5a5f80457fd7f9323a63824372c9182f8fb6ca 
#  |   |-go-runner,2676 /eks-pod-identity-agent server --port 80 --cluster-name baseport-prd --probe-port 2703
#  |   |   |-eks-pod-identit,2699 server --port 80 --cluster-name baseport-prd --probe-port 2703
```

ipコマンドで `pod-id-link0` インターフェースに `169.254.170.23` が設定されているかを確認します。

```bash
ip a | grep -B 2 -A 5  169.254.170.23
# 3: pod-id-link0: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
#     link/ether 4e:a0:b6:ea:bf:2e brd ff:ff:ff:ff:ff:ff
#     inet 169.254.170.23/32 scope global pod-id-link0
#        valid_lft forever preferred_lft forever
#     inet6 fd00:ec2::23/128 scope global
#        valid_lft forever preferred_lft forever
#     inet6 fe80::4ca0:b6ff:feea:bf2e/64 scope link
#        valid_lft forever preferred_lft forever
```

### pod-identity-agentのエンドポイントへのリクエストをtcpdump

ノード上で`169.254.170.23` へのリクエストをtcpdumpして、pod側で`aws sts get-caller-identity` を実行すると、`AWS_CONTAINER_CREDENTIALS_FULL_URI` へのリクエストが確認できます。

```bash
tcpdump -tnlA -i any "tcp and port 80 and dst 169.254.170.23"
```
```
IP 10.32.1.213.53620 > 169.254.170.23.http: Flags [P.], seq 0:1385, ack 1, win 491, options [nop,nop,TS val 1679748253 ecr 1911346896], length 1385: HTTP: GET /v1/credentials HTTP/1.1
E....H@.....
 .......t.P'VRB..M.....e......
d...q...GET /v1/credentials HTTP/1.1
Host: 169.254.170.23
Accept-Encoding: identity
Accept: application/json
Authorization: [サービスアカウントトークン(JWT)]
User-Agent: python-urllib3/1.26.19
```