はじめに
- こんにちは、新川です。先日執筆した以下の記事「Amazon EKS + Karpenter ハンズオン」では、EKSクラスタと高性能なオートスケーラーであるKarpenterの構築手順をハンズオン形式でご紹介しました。今回はその続編として、EKSの重要なセキュリティ機能であるPod Identity に焦点を当て、その仕組みと手順を紹介します。
[ハンズオン] EKS + Karpenter のデプロイ・トラブルシューティング演習
- EKSを運用する上で、各アプリケーション(Pod)に必要かつ最小限の権限だけを与える「最小権限の原則」は非常に重要です。しかし、デフォルトではPodはノード自身のIAMロールを継承してしまい、意図せず大きな権限を持ってしまうことがあります。Pod Identityは、この課題を解決し、Pod単位でAWSリソースへのアクセスを安全に制御するためのモダンな仕組みです。
- 本記事では、上記のハンズオンで作成したEKS + Karpenter環境があることを前提に進めます。S3バケットへのアクセスを例に、特定のPodにだけ権限を付与し、もう一方のPodからはアクセスを拒否するシナリオを通じて、Pod Identityの機能を体感していきましょう!
EKS Pod Identityとは?
- Pod Identityは、EKS(Kubernetes)のPodに対してAWSのIAM権限を安全かつ簡単に割り当てるための、EKSの機能です。
なぜPod単位の権限が必要なのか?(ノードロールの問題点)
- 従来、EKSのPodがAWSリソース(S3やDynamoDBなど)にアクセスする場合、そのPodが動作しているEC2ノード自身のIAMロール(ノードロール)の権限を使っていました。これには、セキュリティ上の大きな課題があります。
- 過剰な権限の付与:あるノード上で、S3にアクセスしたいPod(A)と、DynamoDBにアクセスしたいPod(B)が同時に動いていると仮定します。この場合、ノードロールにはS3とDynamoDBの両方の権限を付与する必要があります。その結果、Pod Aは必要ないはずのDynamoDBへの権限を持ってしまい、Pod BはS3への権限を持ってしまいます。
- 最小権限の原則:上記はセキュリティのベストプラクティスである「最小権限の原則」を守っていません。万が一、Pod Aが乗っ取られた場合、攻撃者はそのPodからDynamoDBを不正に操作できてしまい、被害が拡大する恐れがあります。
Pod Identityの仕組みとメリット
- Pod Identityは、このノードロールの問題を解決します。
Pod Identityの仕組み
- Pod Identityは、IAMロールをEC2ノードではなく、Kubernetesのサービスアカウントに直接関連付けます。
- 関連付け:EKS管理者は、事前に「このサービスアカウント(例: s3-reader-sa)は、このIAMロール(例: S3ReaderRole)を使える」という関連付けをEKSクラスタに登録します。
- エージェントによる仲介:各ノードで動作しているeks-pod-identity-agentが、PodからのAWS認証情報のリクエストを監視します。
- 認証情報の提供:Podが特定のサービスアカウントを使って起動すると、エージェントはそのPodからのリクエストを検知し、関連付けられたIAMロールの一時的な認証情報を安全にPodへ提供します。
- この仕組みにより、Podはノードロールではなく、自分専用に許可されたIAMロールの権限だけを使ってAWSサービスにアクセスできるようになります。
メリット
- Podに必要な最小限の権限だけを付与できるため、クラスター全体のセキュリティが大幅に向上します。
- IAMロールとアプリケーション(Pod)の対応が明確になり、権限管理がシンプルになります。ノードロールに様々なポリシーを追加していく必要がなくなります。
- 厳格なセキュリティ要件やコンプライアンス基準を満たす必要があるシステムにおいて、アクセス証跡の追跡や権限分離が容易になります。
今回の検証シナリオ
- 今回のハンズオンでは、Pod Identityのアクセス制御を検証するために、S3バケットを対象としたシンプルなシナリオをテストします。
- EKSクラスター上に2種類のPodを準備し、それぞれがS3バケットに対してどのように振る舞うかを確認します。
- Pod A(s3-reader-pod):専用のサービスアカウントとPod Identityを通じて、S3の読み取り権限を付与します。このPodからは、S3バケット内のオブジェクト一覧が正しく取得できるはずです。
- Pod B(no-s3-access-pod):特別な権限を何も付与しない、デフォルトの状態で起動します。このPodからは、同じS3バケットへのアクセスがAccess Deniedとなり、意図通りにアクセスが拒否されることを確認します。
【ハンズオン】Pod Identityによるアクセス制御の実践
手順1:検証用のS3バケットとIAMリソースの準備
- 検証用に、S3バケットとバケット内にテストファイルを作成します。以下は、CloudShell から操作したコマンド例になります。コマンドの説明は、省略します。
~ $ export AWS_REGION="us-west-2" ~ $ export CLUSTER_NAME="niikawa-karpenter-demo" ~ $ export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) ~ $ export S3_BUCKET_NAME="pod-identity-test-bucket-${CLUSTER_NAME}" ~ $ aws s3 mb "s3://${S3_BUCKET_NAME}" --region ${AWS_REGION} make_bucket: pod-identity-test-bucket-niikawa-karpenter-demo ~ $ echo "Hello Pod Identity" > test.txt ~ $ aws s3 cp test.txt "s3://${S3_BUCKET_NAME}/test.txt" upload: ./test.txt to s3://pod-identity-test-bucket-niikawa-karpenter-demo/test.txt ~ $
- マネージメントコンソールから確認します。S3バケットとテストファイルが作成されました。
- 次に、Pod用のIAMポリシーとIAMロールを作成します。
- 今回ポリシーに使用するJSON は、以下となります。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::${S3_BUCKET_NAME}", "arn:aws:s3:::${S3_BUCKET_NAME}/*" ] } ] }
- 以下コマンド例では、cat コマンドで次に上記コードを貼り付け、続けて”EOF” を入力し、JSONファイルを作成します。
cat << EOF > s3-reader-policy.json
- AWS CLI コマンドでポリシーを作成して、ARNを取得しています。
~ $ S3_READER_POLICY_ARN=$(aws iam create-policy \ --policy-name "niikawa-S3ReaderPolicyForPodIdentity" \ --policy-document file://s3-reader-policy.json \ --query 'Policy.Arn' --output text)
- 次にロールに使用する信頼ポリシーを準備しています。Pod Identity用に、PrincipalとAction を正しく設定します。
- Action に、「sts:TagSession」を含めています。EKS Pod Identityエージェントがロールを引き受ける際にセッションタグを付与するために必要になります。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "pods.eks.amazonaws.com" }, "Action": [ "sts:AssumeRole", "sts:TagSession" ] } ] }
- 以下コマンド例では、cat コマンドで次に上記コードを貼り付け、続けて”EOF” を入力し、JSONファイルを作成します。
- 次に、AWS CLI コマンドでロールを作成して、ARNを取得しています。
- 最後に、作成したロールにポリシーをアタッチしています。
cat << EOF > s3-reader-trust-policy.json
~ $ S3_READER_ROLE_ARN=$(aws iam create-role \ --role-name "niikawa-S3ReaderRoleForPod" \ --assume-role-policy-document file://s3-reader-trust-policy.json \ --query 'Role.Arn' --output text) ~ $ ~ $ aws iam attach-role-policy \ --role-name "niikawa-S3ReaderRoleForPod" \ --policy-arn "${S3_READER_POLICY_ARN}" ~ $
- マネージメントコンソールから確認します。Pod用のIAMロールが作成されました。(画像のロール名が前述のコマンドと異なる点はご容赦ください)
- Pod Identity用の信頼ポリシーも設定できていますね。
手順2:Pod Identity の関連付けとKubernetesリソースの作成
- ここからは、EKS にKubernetesリソースを追加し、Pod Identity の関連付けを行います。
- まずマネージメントコンソールから確認します。EKS のクラスタを選択し、アクセスの項目からPod Identity の関連付けを確認します。現在は、オートスケーラーのKarpenter用に使用するPod Identity しか設定されていません。
- 次に、Kubernetes のNamespaceとServiceAccount およびPod を作成します。
- Namespaceは、1つのKubernetesクラスタを複数の仮想的な区画に分割してリソースを整理するための仕組みであり、ServiceAccountはその区画(Namespace)内で動作するPodに割り当てる、アプリケーション用のIDです。
- 続けて、Pod の定義を行います。
- Pod A、Pod B に使用するコンテナは「aws-cli」という名前を付け、イメージには「image: amazon/aws-cli:latest」を設定しています。
- コンテナが起動したときに、指定したコマンドをシェル(/bin/sh)経由で実行するように指定しており、args に続く記述が実際に実行するコマンドになります。aws s3 ls コマンドを行い、コマンドが成功するかどうかを調べています。
- 「restartPolicy: Never」はPod全体の設定で、コンテナが終了しても、Kubernetesが自動で再起動させないことを意味します。今回のテストでは一度だけ実行して結果を確認したいだけですので、このようなケースでは便利な設定です。
- Pod AはPod Identity の設定でS3 アクセスの権限を得ていますが、Pod Bはdefault サービスアカウントを使用するため、ノード側のIAMロールを使います。そのため、S3アクセスの権限はなく、コマンドは失敗する想定です。
--- apiVersion: v1 kind: Namespace metadata: name: pod-identity-test --- apiVersion: v1 kind: ServiceAccount metadata: name: s3-reader-sa namespace: pod-identity-test --- # Pod A: S3読み取り権限を持つべきPod apiVersion: v1 kind: Pod metadata: name: s3-reader-pod namespace: pod-identity-test spec: serviceAccountName: s3-reader-sa containers: - name: aws-cli image: amazon/aws-cli:latest command: ["/bin/sh", "-c"] args: - > echo "--- Attempting to access S3 with s3-reader-sa ---"; aws s3 ls s3://${S3_BUCKET_NAME}/; echo "--------------------------------------------------"; sleep 3600; restartPolicy: Never --- # Pod B: S3アクセス権を持たないPod apiVersion: v1 kind: Pod metadata: name: no-s3-access-pod namespace: pod-identity-test spec: # serviceAccountNameを省略(defaultサービスアカウントを使用) containers: - name: aws-cli image: amazon/aws-cli:latest command: ["/bin/sh", "-c"] args: - > echo "--- Attempting to access S3 with default SA ---"; aws s3 ls s3://${S3_BUCKET_NAME}/ --cli-read-timeout 60 || echo "Access Denied as expected."; echo "---------------------------------------------"; sleep 3600; restartPolicy: Never
- 以下コマンド例では、cat コマンドで次に上記コードを貼り付け、続けて”EOF” を入力し、YAMLファイルを作成します。
- 最後に、念のためhead コマンドでファイルが作られたことを確認しています。
cat << EOF > s3-access-test.yaml
~ $ head s3-access-test.yaml --- apiVersion: v1 kind: Namespace metadata: name: pod-identity-test --- apiVersion: v1 kind: ServiceAccount metadata: name: s3-reader-sa ~ $
- aws eks コマンドを使用し、Pod Identity の関連付けを行います。(先にPod を作成すると期待通りの結果になりません)
~ $ aws eks create-pod-identity-association \ --cluster-name ${CLUSTER_NAME} \ --namespace pod-identity-test \ --service-account s3-reader-sa \ --role-arn ${S3_READER_ROLE_ARN} { "association": { "clusterName": "niikawa-karpenter-demo", "namespace": "pod-identity-test", "serviceAccount": "s3-reader-sa", "roleArn": "arn:aws:iam::111111111111:role/niikawa-S3ReaderRoleForPod", "associationArn": "arn:aws:eks:us-west-2:111111111111:podidentityassociation/niikawa-karpenter-demo/a-uarmu78bubtqabgrq", "associationId": "a-uarmu78bubtqabgrq", "tags": {}, "createdAt": "2025-07-27T01:27:36.180000+00:00", "modifiedAt": "2025-07-27T01:27:36.180000+00:00", "disableSessionTags": false } }
- 最後に、NamespaceとServiceAccount およびPod の作成を行います。
~ $ kubectl apply -f s3-access-test.yaml namespace/pod-identity-test created serviceaccount/s3-reader-sa created pod/s3-reader-pod created pod/no-s3-access-pod created
- マネージメントコンソールから確認します。EKS のクラスタを選択し、アクセスの項目からPod Identity の関連付けを確認します。今回の検証用に作成したPod Identity が追加されたことが分かります。
- 続けて、コンソールからPod の状態を確認します。
- 以下は、Pod A(s3-reader-pod)の画面です。無事に起動ができ、実行中になりました。
- 以下は、Pod B(no-s3-access-pod)の画面です。無事に起動ができ、実行中になりました。
手順3:動作確認
- それでは動作確認を行います。各Pod に対して、kubectl logs コマンドでログを表示させます。
- 以下は、Pod A(s3-reader-pod)のログとなります。期待通りの結果であり、S3バケット内のオブジェクト一覧が取得できました。
~ $ kubectl logs s3-reader-pod -n pod-identity-test --- Attempting to access S3 with s3-reader-sa --- 2025-07-27 00:30:20 19 test.txt --------------------------------------------------
- 以下は、Pod B(no-s3-access-pod)のログとなります。期待通り、権限がないエラーとなりました。
~ $ kubectl logs no-s3-access-pod -n pod-identity-test --- Attempting to access S3 with default SA --- An error occurred (AccessDenied) when calling the ListObjectsV2 operation: User: arn:aws:sts::111111111111:assumed-role/eksctl-niikawa-karpenter-demo-node-NodeInstanceRole-0Zm5SRQnYRQW/i-085bfa5fb288773fc is not authorized to perform: s3:ListBucket on resource: "arn:aws:s3:::pod-identity-test-bucket-niikawa-karpenter-demo" because no identity-based policy allows the s3:ListBucket action Access Denied as expected. ---------------------------------------------
- ここからは、Pod の詳しい動作を理解します。まずkubectl get pod コマンドで、Pod が配置されているノードを確認します。「NODE」列に表示されるノード名を特定します。
- 次に、kubectl get pods -n kube-system -o wide コマンドで、特定したノード上にeks-pod-identity-agent のPod がRunning 状態で存在するか確認します。
~ $ kubectl get pod s3-reader-pod -n pod-identity-test -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES s3-reader-pod 1/1 Running 0 3m21s 10.192.20.83 ip-10-192-20-166.us-west-2.compute.internal ~ $ kubectl get pods -n kube-system -o wide | grep eks-pod-identity-agent eks-pod-identity-agent-dtfgh 1/1 Running 0 120m 10.192.21.249 ip-10-192-21-249.us-west-2.compute.internal eks-pod-identity-agent-nlntk 1/1 Running 0 120m 10.192.20.166 ip-10-192-20-166.us-west-2.compute.internal
- 次は、エージェントのログを確認します。eks-pod-identity-agent のPod名を指定して、kubectl logs コマンドを実行します。
- ログ内から、「fetched_role_arn”:”…/niikawa-S3ReaderRoleForPod/…”」とあり、Podに関連付けられた正しいS3 用のIAMロールの認証情報を取得し、「msg”:”Successfully fetched credentials…”」とあるため、Podに情報を渡すことに成功したと読み取れます。
~ $ POD_IDENTITY_AGENT_POD_NAME="eks-pod-identity-agent-nlntk" ~ $ kubectl logs ${POD_IDENTITY_AGENT_POD_NAME} -n kube-system Defaulted container "eks-pod-identity-agent" out of: eks-pod-identity-agent, eks-pod-identity-agent-init (init) 2025/07/26 23:46:55 Running command: Command env: (log-file=, also-stdout=false, redirect-stderr=true) Run from directory: Executable path: /eks-pod-identity-agent Args (comma-delimited): /eks-pod-identity-agent,server,--port,80,--cluster-name,niikawa-karpenter-demo,--probe-port,2703 2025/07/26 23:46:55 Now listening for interrupts 2025/07/26 23:46:55 Setting logging verbosity level to: info (4) {"bind-addr":"[fd00:ec2::23]:80","level":"info","msg":"Starting server...","time":"2025-07-26T23:46:55Z"} {"bind-addr":"169.254.170.23:80","level":"info","msg":"Starting server...","time":"2025-07-26T23:46:55Z"} {"bind-addr":"localhost:2703","level":"info","msg":"Starting server...","time":"2025-07-26T23:46:55Z"} {"bind-addr":"0.0.0.0:2705","level":"info","msg":"Starting server...","time":"2025-07-26T23:46:55Z"} {"client-addr":"10.192.20.230:59598","cluster-name":"niikawa-karpenter-demo","level":"info","msg":"handling new request request from 10.192.20.230:59598","time":"2025-07-27T00:18:18Z"} {"client-addr":"10.192.20.230:59598","cluster-name":"niikawa-karpenter-demo","level":"info","msg":"Calling EKS Auth to fetch credentials","time":"2025-07-27T00:18:18Z"} {"client-addr":"10.192.20.230:59598","cluster-name":"niikawa-karpenter-demo","fetched_role_arn":"arn:aws:sts::111111111111:assumed-role/niikawa-karpenter-demo-karpenter/eks-niikawa-ka-karpenter--e5770bb9-0785-4269-8fdb-7c4fd88d2e5c","fetched_role_id":"AROATRPRKFUWOTBWU5UJ7:eks-niikawa-ka-karpenter--e5770bb9-0785-4269-8fdb-7c4fd88d2e5c","level":"info","msg":"Successfully fetched credentials from EKS Auth","request_time_ms":128,"time":"2025-07-27T00:18:18Z"} {"client-addr":"10.192.20.230:59598","cluster-name":"niikawa-karpenter-demo","level":"info","msg":"Storing creds in cache","refreshTtl":10800000000000,"time":"2025-07-27T00:18:18Z"} {"client-addr":"10.192.20.83:48636","cluster-name":"niikawa-karpenter-demo","level":"info","msg":"handling new request request from 10.192.20.83:48636","time":"2025-07-27T01:43:42Z"} {"client-addr":"10.192.20.83:48636","cluster-name":"niikawa-karpenter-demo","level":"info","msg":"Calling EKS Auth to fetch credentials","time":"2025-07-27T01:43:42Z"} {"client-addr":"10.192.20.83:48636","cluster-name":"niikawa-karpenter-demo","fetched_role_arn":"arn:aws:sts::111111111111:assumed-role/niikawa-S3ReaderRoleForPod/eks-niikawa-ka-s3-reader--8199a236-949c-4711-bd88-b51bd3e00014","fetched_role_id":"AROATRPRKFUWO6WEMG2GA:eks-niikawa-ka-s3-reader--8199a236-949c-4711-bd88-b51bd3e00014","level":"info","msg":"Successfully fetched credentials from EKS Auth","request_time_ms":96,"time":"2025-07-27T01:43:42Z"} {"client-addr":"10.192.20.83:48636","cluster-name":"niikawa-karpenter-demo","level":"info","msg":"Storing creds in cache","refreshTtl":10800000000000,"time":"2025-07-27T01:43:42Z"} ~ $
まとめ
- 今回のハンズオンでは、EKS Pod Identity を使うことで、ノードのIAMロールに依存せず、Pod 単位でAWS リソースへのアクセス権限をきめ細かく制御できることを実証しました。特定のPod にだけS3 へのアクセスを許可し、他のPod からは拒否することで、セキュリティのベストプラクティスである「最小権限の原則」を実践できることが確認できました。
- もし期待通りに動作しない場合は、今回行ったようにeks-pod-identity-agent のログを確認することが、有効なトラブルシューティング手段となります。この方法はS3だけでなく、DynamoDB やSQS など他のAWSサービスにも応用可能です。