この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
みなさん、こんにちは!
AWS事業本部の青柳@福岡オフィスです。
AWSのマネージドKubernetesサービスである「EKS」(Amazon Elastic Kubernetes Service) に、多くの人が待望していた (かもしれない) 機能がついに追加されました!
Amazon EKS Adds Support to Assign IAM Permissions to Kubernetes Service Accounts
タイトルを直訳すると「Amazon EKSがIAMアクセス許可をKubernetesサービスアカウントへ割り当てるサポートを追加」です。
今回のアップデートがどういうものであるのかの概要と、同時に公開されたチュートリアルを実際に行ってみた際の流れをご紹介します。
今回のアップデートで何が変わったのか
サービスアカウントとはKubernetesに標準で備わっている認証認可関連の要素であり、Pod内のコンテナから各リソースへのアクセス制御の際に用いられます。
しかし、サービスアカウントはKubernetesの世界に閉じた概念であり、これまではAWSのIAMと連係することができませんでした。
そのため、EKS環境(あるいはKops等で構築したAWS上のKubernetes環境)においてPod内のコンテナからS3等のAWSリソースに対するアクセス制御を行う場合、大きな制約となっていました。
従来の方法
Podに対してAWSリソースに対するアクセス権限を与える場合の従来の方法は「IAMインスタンスプロファイルを使用してワーカーノードのEC2インスタンスにIAMロールを割り当てる」という方法でした。
EC2インスタンスに割り当てられたIAMロールに定義されたアクセス権限は、ワーカーノード上で動作するPodにも適用されます。
これによって、特定のアクセス権限をPod内コンテナに対して与えるという目的が達成されます。
しかし、この方法ですと、あるEC2インスタンス(ワーカーノード)上で動作する全てのPodに対して同一のアクセス権限を与えることになります。
もし異なるアクセス権限を与えたい場合には、別のEC2インスタンスを用意してIAMロールを割り当てる必要があります。
これだとPodの配置が柔軟に行えませんし、アクセス権限のパターンの数だけEC2インスタンスを用意することになるためリソース効率も悪くなります。
もしくは、EC2インスタンスに配置される可能性のある各Podに必要なアクセス権限を全て併せ持ったIAMロールを定義して、EC2インスタンスに割り当てるという方法もあります。
(上の図で言うと、Bucket-A
と Bucket-B
の両方にアクセス権限を持つIAMロールを1つだけ定義して Host-1
Host-2
へ一律に割り当てる)
しかし、この方法ではPodに本来必要のない過剰なアクセス権限を与えてしまうことになり、セキュリティ観点から推奨されないものになります。
今回のアップデートでできるようになったこと
今回のアップデートでは、下図のようにPod単位でIAMロールを割り当てることが可能になりました。
これにより、EC2インスタンス上で動作させる各Podのアクセス権限がどのようになっているかということに影響を受けずに、Podの配置が可能になりました。
また、IAMロールをPodに対して直接割り当てるため、アクセス制御の管理が直感的になり見通しが良くなります。
実は、今回のアップデート以前にも、同様のことを実現する kube2iam や kiam などのOSSツールが公開されていました。
しかし、これらのツールはAWSクレデンシャルをKubernetesのメタデータを経由して渡すという手法を採用していたため、パフォーマンスの問題やセキュリティの懸念が指摘されていました。
今回のアップデートでは、標準化されたID連係の規格であるOpenID Connect (OIDC)を使った実装となっており、これらの懸念が解消されることが期待されます。
チュートリアルを行ってみる
前置きが長くなりましたが、それではチュートリアルを行ってみましょう。
AWS Blogにチュートリアルを含むブログ記事が公開されています。
Introducing Fine-Grained IAM Roles for Service Accounts | AWS Open Source Blog
(ページ中盤の「Example usage walkthrough」以降がチュートリアルとなっています)
今回は、このブログ記事に沿ってチュートリアルを行います。
0. 事前準備
チュートリアルを始める前に、AWS CLIとeksctlを最新化しておきましょう。
今回は、以下のバージョンで行いました。(9月5日時点の最新バージョン)
- AWS CLI: 1.16.232
- eksctl: 0.5.0
また、AWS CLIコマンドやeksctlコマンドが正しく実行されるように、プロファイルとリージョンを設定しておきます。
(環境変数 AWS_PROFILE
、AWS_REGION
を設定するか、設定ファイル ~/.aws/config
に記述します)
最後に、チュートリアルで使用する各種資産をGitHubリポジトリから入手しておきます。
$ git clone https://github.com/mhausenblas/s3-echoer.git
1. EKSクラスターとワーカーノードを作成する
eksctlコマンドを使用して、EKSクラスターとワーカーノードを作成します。
$ eksctl create cluster s3echotest
今回はチュートリアルということでクラスター名を除く全てのオプションを省略しましたので、作成されるクラスター・ワーカーノードはeksctlのデフォルト構成となっています。
(3つのAZにPublic/Privateの計6サブネット、ノードタイプ=m5.large、ノード数=2、etc.)
また、IAM Roles for Service Accounts機能を利用するにはKubernetesのバージョンが1.13以上である必要があります。
バージョン指定(--version
)を省略した際のデフォルトは「バージョン1.13」ですので、こちらも問題ありません。
(※ チュートリアルではコマンドラインが eksctl create cluster --approve
と記載されていますが、誤記だと思われます。クラスター名を省略するとランダムな名前になるため、後のコマンドと整合性が取れなくなります。また、--approve
オプションは create cluster
コマンドにはありません)
実行結果:
$ eksctl create cluster s3echotest
[ℹ] using region ap-northeast-1
[ℹ] setting availability zones to [ap-northeast-1c ap-northeast-1a ap-northeast-1d]
[ℹ] subnets for ap-northeast-1c - public:192.168.0.0/19 private:192.168.96.0/19
[ℹ] subnets for ap-northeast-1a - public:192.168.32.0/19 private:192.168.128.0/19
[ℹ] subnets for ap-northeast-1d - public:192.168.64.0/19 private:192.168.160.0/19
[ℹ] nodegroup "ng-0e53ed42" will use "ami-0a67c71d2ab43d36f" [AmazonLinux2/1.13]
[ℹ] using Kubernetes version 1.13
[ℹ] creating EKS cluster "s3echotest" in "ap-northeast-1" region
[ℹ] will create 2 separate CloudFormation stacks for cluster itself and the initial nodegroup
[ℹ] if you encounter any issues, check CloudFormation console or try 'eksctl utils describe-stacks --region=ap-northeast-1 --name=s3echotest'
[ℹ] CloudWatch logging will not be enabled for cluster "s3echotest" in "ap-northeast-1"
[ℹ] you can enable it with 'eksctl utils update-cluster-logging --region=ap-northeast-1 --name=s3echotest'
[ℹ] 2 sequential tasks: { create cluster control plane "s3echotest", create nodegroup "ng-0e53ed42" }
[ℹ] building cluster stack "eksctl-s3echotest-cluster"
[ℹ] deploying stack "eksctl-s3echotest-cluster"
[ℹ] building nodegroup stack "eksctl-s3echotest-nodegroup-ng-0e53ed42"
[ℹ] --nodes-min=2 was set automatically for nodegroup ng-0e53ed42
[ℹ] --nodes-max=2 was set automatically for nodegroup ng-0e53ed42
[ℹ] deploying stack "eksctl-s3echotest-nodegroup-ng-0e53ed42"
[✔] all EKS cluster resource for "s3echotest" had been created
[✔] saved kubeconfig as "/home/ubuntu/.kube/config"
[ℹ] adding role "arn:aws:iam::XXXXXXXXXXXX:role/eksctl-s3echotest-nodegroup-ng-0e-NodeInstanceRole-LCRT367XZSI6" to auth ConfigMap
[ℹ] nodegroup "ng-0e53ed42" has 0 node(s)
[ℹ] waiting for at least 2 node(s) to become ready in "ng-0e53ed42"
[ℹ] nodegroup "ng-0e53ed42" has 2 node(s)
[ℹ] node "ip-192-168-55-117.ap-northeast-1.compute.internal" is ready
[ℹ] node "ip-192-168-73-18.ap-northeast-1.compute.internal" is ready
[ℹ] kubectl command should work with "/home/ubuntu/.kube/config", try 'kubectl get nodes'
[✔] EKS cluster "s3echotest" in "ap-northeast-1" region is ready
EKSクラスターが作成されたので、マネジメントコンソール上でも確認してみましょう。
以前は無かった「OpenID Connect provider URL」という項目が表示されていることが確認できます。
(もし表示されていない場合は、AWS CLIやeksctlのバージョンを確認してください)
ついでに、このタイミングで kubectl cluster-info
、kubectl get nodes
等を実行して、EKSクラスターへの接続を確認しておくとよいでしょう。
2. IAMのIDプロバイダー(OIDCプロバイダー)を作成する
次に、AWSのIAMロールとKubernetesのサービスアカウントの橋渡しをしてくれる「OIDC」のIDプロバイダーを作成します。
これには、今回eksctlコマンドに新たに追加された eksctl utils associate-iam-oidc-provider
コマンドを使用します。
$ eksctl utils associate-iam-oidc-provider --name s3echotest --approve
実行結果:
$ eksctl utils associate-iam-oidc-provider --name s3echotest --approve
[ℹ] using region ap-northeast-1
[ℹ] will create IAM Open ID Connect provider for cluster "s3echotest" in "ap-northeast-1"
[✔] created IAM Open ID Connect provider for cluster "s3echotest" in "ap-northeast-1"
マネジメントコンソールで「IAM」→「IDプロバイダー」を開くと、OIDCのIDプロバイダーが作成されていることが確認できます。
「プロバイダーのURL」欄に、さきほどEKSクラスターで確認した「OpenID Connect provider URL」の値が設定されていることが分かります。
eksctlコマンドの実行時にプロバイダーURLの指定はありませんでしたが、EKSクラスター名から自動的に情報を引っ張ってきてくれるようです。
3. IAMロールとKubernetesサービスアカウントを作成する
次に、IAMロールとKubernetesサービスアカウントを作成します。
これらは eksctl create iamserviceaccount
コマンドを使用して一度に作成することができます。
$ eksctl create iamserviceaccount \
--name s3-echoer \
--cluster s3echotest \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess \
--approve
--name
で指定した名前は、サービスアカウント名、および、IAMロール名の一部に採用されます。
--attach-policy-arn
にはIAMロールへアタッチするポリシーを指定します。
ここではAWS管理ポリシー AmazonS3FullAccess
を指定しています。(S3へのフルアクセス権を与える)
(※ チュートリアルではコマンドラインの4行目の終わりに必要な \
が漏れています。そのまま実行するとエラーになりますので注意してください)
実行結果:
$ eksctl create iamserviceaccount \
> --name s3-echoer \
> --cluster s3echotest \
> --attach-policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess \
> --approve
[ℹ] using region ap-northeast-1
[ℹ] 1 iamserviceaccount (default/s3-echoer) was included
[!] serviceaccounts that exists in Kubernetes will be excluded, use --include-existing-serviceaccounts to override
[ℹ] 1 task: { 2 sequential sub-tasks: { create IAM role for serviceaccount "default/s3-echoer", create serviceaccount "default/s3-echoer" } }
[ℹ] building iamserviceaccount stack "eksctl-s3echotest-addon-iamserviceaccount-default-s3-echoer"
[ℹ] deploying stack "eksctl-s3echotest-addon-iamserviceaccount-default-s3-echoer"
[ℹ] created serviceaccount "default/s3-echoer"
作成されたIAMロールを見てみましょう。
eksctl-(--nameで指定した名前)-addon-iamserviceaccount-de-Role1-(ランダム文字列)
という命名規則でIAMロールが作成されています。
指定した AmazonS3FullAccess
ポリシーがアタッチされていることも確認できます。
IAMロールの信頼関係は以下のようになっています。
信頼されたエントリーとしてOIDCプロバイダーのARNがセットされています。
また、AssumeRoleの条件としてKubernetesサービスアカウント名が指定されていることも確認できます。
今度は、Kubernetesサービスアカウントを見てみましょう。
$ kubectl get serviceaccounts
NAME SECRETS AGE
default 1 18m
s3-echoer 1 2m15s
default
は最初からあるKubernetes標準のサービスアカウントで、新たに s3-echoer
サービスアカウントが追加されています。
サービスアカウントの詳細は以下のようになっています。
$ kubectl get serviceaccounts s3-echoer --output yaml
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::XXXXXXXXXXXX:role/eksctl-s3echotest-addon-iamserviceaccount-de-Role1-9VW8WH4S9BT
creationTimestamp: "2019-09-05T11:49:14Z"
name: s3-echoer
namespace: default
resourceVersion: "1734"
selfLink: /api/v1/namespaces/default/serviceaccounts/s3-echoer
uid: 2f18e2f2-cfd3-11e9-8817-06179e478c90
secrets:
- name: s3-echoer-token-2lz29
こちらには、アノテーションにIAMロールのARNがセットされていることが確認できます。
これで、IAMロールとKubernetesサービスアカウントが作成され、それぞれ相互に情報がセットされていることが分かりました。
4. S3バケットを作成する
テストに用いるS3バケットをAWS CLIで作成します。
バケット名は任意のものに変えてください。
$ TARGET_BUCKET=s3-echoer-bucket
$ aws s3api create-bucket \
--bucket $TARGET_BUCKET \
--create-bucket-configuration LocationConstraint=$(aws configure get region) \
--region $(aws configure get region)
5. Kubernetesのリソース定義YAMLファイルを編集する
最初に入手したGitHubリポジトリに、Kubernetesのリソース定義YAMLファイルのテンプレートが用意されています。
こちらをコピーして編集しましょう。
$ cd s3-echoer
$ cp s3-echoer-job.yaml.template s3-echoer-job.yaml
$ vi s3-echoer-job.yaml
YAMLファイルの内容は以下のようになっています。(下記は編集後の内容になっています)
s3-echoer-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: s3-echoer
spec:
template:
spec:
serviceAccountName: s3-echoer
containers:
- name: main
image: amazonlinux:2018.03
command:
- "sh"
- "-c"
- "curl -sL -o /s3-echoer https://github.com/mhausenblas/s3-echoer/releases/latest/download/s3-echoer-linux && chmod +x /s3-echoer && echo This is an in-cluster test | /s3-echoer s3-echoer-bucket"
env:
- name: AWS_DEFAULT_REGION
value: "ap-northeast-1"
- name: ENABLE_IRP
value: "true"
restartPolicy: Never
変更するポイントは2箇所です。
- 15行目の最後にある
TARGET_BUCKET
の文字列部分を、作成したS3のバケット名に書き換えます。 - 18行目の
us-west-2
をS3バケットのリージョンに書き換えます。
ここまでできたら、全ての準備は完了です。
(ちょっと寄り道) リソース定義YAMLファイルで、何を定義しているの?
チュートリアルでは、S3へアクセスするテストアプリをKubernetesの「Jobリソース」として用意しています。
通常、KubernetesのPodは常駐型の動作をしますが、Jobリソースを使うとバッチジョブ型のPodを起動することができます。
※ Jobリソースについては、こちらのリンク先の説明が分かり易いと思います。
KubernetesのWorkloadsリソース(その2) | Think IT(シンクイット)
Jobリソースから起動されるPodが、どんな処理をするものなのか、ざっくり見ていきます。
- コンテナイメージ
amazonlinux
を使ってコンテナを起動 - GitHubから
s3-echoer-linux
という実行ファイルをダウンロードしてs3-echoer
にリネームして保存 s3-echoer
に実行権限を付与して、ルートディレクトリに配置s3-echoer
を実行 (パラメータとしてS3バケット名を指定、また、This is an in-cluster test
という文字列を標準入力から与える)
s3-echoer
という実行ファイルについてですが、これは、このチュートリアルブログの執筆者の一人であるMichael Hausenblas氏が公開しているテスト用ツールです。
https://github.com/mhausenblas/s3-echoer
s3-echoer
の動作内容は「パラメータ(第1引数)で指定されたS3バケットに空のオブジェクトを書き込む」というだけのシンプルな動作です。
(標準入力から文字列を与えると、オブジェクトの内容が与えられた文字列テキストになります)
s3-echoer
は単体でもKubernetes上でも動作するツールとなっています。
実行時に環境変数 ENABLE_IRP
を true
にセットすることで、IAM Roles for Service Accountsに対応した動作をします。
(※ 正確にはIRSAに対応した処理をしている訳ではなくて、AWS SDK for Goのバージョン関連の問題を吸収するための対応であるようです。興味のある方はソースコードを見てみてください)
今回のチュートリアルではこれを使ってIAM Roles for Service Accountsの動作をテストするという訳です。
6. Podを起動してS3へのアクセスを確認する
では、実際にPodがIAMロールで指定したアクセス権限をもってS3へアクセスできることをテストしましょう。
さきほどコピー・編集したリソース定義YAMLファイルを kubectl apply
コマンドで適用します。
$ kubectl apply -f s3-echoer-job.yaml
投入されたJobリソースを確認します。
$ kubectl get jobs
NAME COMPLETIONS DURATION AGE
s3-echoer 1/1 8s 11s
Jobリソースが起動したPodリソースを確認します。
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
s3-echoer-mplhl 0/1 Completed 0 11s
Podの処理は数秒で終わるので、ステータスは Completed
になっています。
(ステータスが Running
のまま終わらないとか Error
になる場合は、ここまでの手順でどこかが間違っている可能性があります)
S3バケットにオブジェクトが書き込まれたことを確認しましょう。
ちゃんとオブジェクトが作成されていました。(ファイル名の後ろの数字はUNIXタイムです)
つまり、Podに対して、S3バケットへの書き込みアクセス権限が正しく適用されているということです。
ファイルをダウンロードして中身も確認してみます。
$ aws s3 cp s3://s3-echoer-bucket/s3echoer-1567685163 .
download: s3://s3-echoer-bucket/s3echoer-1567685163 to ./s3echoer-1567685163
$ cat s3echoer-1567685163
This is an in-cluster test
ちゃんと書き込まれていますね。
これで、無事にチュートリアルを完走しました。
どんな仕組みなのか・・・少しだけ見てみましょう
さて、動作が確認できたところで、仕組みについても少しだけ追いかけてみましょう。
とは言っても、EKSがOIDCを使って内部的にどのような処理を行っているのか・・・については解説する紙面が足りない (私の理解も足りない!) ので、今回は外部から見える部分のみを確認してみることにします。
JobがPodを起動する際、どのようなリソース定義を行っているのか、内容をダンプしてみます。
(かなり分量がありますがご容赦ください)
$ kubectl get pods s3-echoer-mplhl --output yaml
apiVersion: v1
kind: Pod
metadata:
annotations:
kubernetes.io/psp: eks.privileged
creationTimestamp: "2019-09-05T12:05:58Z"
generateName: s3-echoer-
labels:
controller-uid: 85781e04-cfd5-11e9-8817-06179e478c90
job-name: s3-echoer
name: s3-echoer-mplhl
namespace: default
ownerReferences:
- apiVersion: batch/v1
blockOwnerDeletion: true
controller: true
kind: Job
name: s3-echoer
uid: 85781e04-cfd5-11e9-8817-06179e478c90
resourceVersion: "3227"
selfLink: /api/v1/namespaces/default/pods/s3-echoer-mplhl
uid: 857d935e-cfd5-11e9-8817-06179e478c90
spec:
containers:
- command:
- sh
- -c
- curl -sL -o /s3-echoer https://github.com/mhausenblas/s3-echoer/releases/latest/download/s3-echoer-linux
&& chmod +x /s3-echoer && echo This is an in-cluster test | /s3-echoer s3-echoer-bucket
env:
- name: AWS_DEFAULT_REGION
value: ap-northeast-1
- name: ENABLE_IRP
value: "true"
- name: AWS_ROLE_ARN
value: arn:aws:iam::XXXXXXXXXXXX:role/eksctl-s3echotest-addon-iamserviceaccount-de-Role1-9VW8WH4S9BT
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
image: amazonlinux:2018.03
imagePullPolicy: IfNotPresent
name: main
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: s3-echoer-token-2lz29
readOnly: true
- mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount
name: aws-iam-token
readOnly: true
dnsPolicy: ClusterFirst
enableServiceLinks: true
nodeName: ip-192-168-73-18.ap-northeast-1.compute.internal
priority: 0
restartPolicy: Never
schedulerName: default-scheduler
securityContext: {}
serviceAccount: s3-echoer
serviceAccountName: s3-echoer
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
volumes:
- name: aws-iam-token
projected:
defaultMode: 420
sources:
- serviceAccountToken:
audience: sts.amazonaws.com
expirationSeconds: 86400
path: token
- name: s3-echoer-token-2lz29
secret:
defaultMode: 420
secretName: s3-echoer-token-2lz29
status:
conditions:
- lastProbeTime: null
lastTransitionTime: "2019-09-05T12:05:58Z"
reason: PodCompleted
status: "True"
type: Initialized
- lastProbeTime: null
lastTransitionTime: "2019-09-05T12:06:06Z"
reason: PodCompleted
status: "False"
type: Ready
- lastProbeTime: null
lastTransitionTime: "2019-09-05T12:06:06Z"
reason: PodCompleted
status: "False"
type: ContainersReady
- lastProbeTime: null
lastTransitionTime: "2019-09-05T12:05:58Z"
status: "True"
type: PodScheduled
containerStatuses:
- containerID: docker://0897c48da182645826f3793e1a56fa9f244e7cfd7117f94f6099f9c01577ee85
image: amazonlinux:2018.03
imageID: docker-pullable://amazonlinux@sha256:9a712720ba49101d90a71d319b35d2bf578657cc114e48f13e15441bf3da679b
lastState: {}
name: main
ready: false
restartCount: 0
state:
terminated:
containerID: docker://0897c48da182645826f3793e1a56fa9f244e7cfd7117f94f6099f9c01577ee85
exitCode: 0
finishedAt: "2019-09-05T12:06:05Z"
reason: Completed
startedAt: "2019-09-05T12:05:59Z"
hostIP: 192.168.73.18
phase: Succeeded
podIP: 192.168.93.24
qosClass: BestEffort
startTime: "2019-09-05T12:05:58Z"
まず、46~52行目と72~84行目の volumes
、volumeMounts
の記述内容に着目します。
spec:
containers:
・・・
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: s3-echoer-token-2lz29
readOnly: true
- mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount
name: aws-iam-token
readOnly: true
・・・
volumes:
- name: aws-iam-token
projected:
defaultMode: 420
sources:
- serviceAccountToken:
audience: sts.amazonaws.com
expirationSeconds: 86400
path: token
- name: s3-echoer-token-2lz29
secret:
defaultMode: 420
secretName: s3-echoer-token-2lz29
この箇所は、IRSAを使用しない(ServiceAccountを指定しない)場合は以下のようになります。
spec:
containers:
・・・
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: default-token-XXXXX
readOnly: true
・・・
volumes:
- name: default-token-XXXXX
secret:
defaultMode: 420
secretName: default-token-XXXXX
secret
経由で受け渡しているサービスアカウントのトークン名が両者で異なることが分かります。
(ServiceAccountを指定しない場合は default
というサービスアカウントが使用されます)
また、IRSA使用時には aws-iam-token
というトークンが projected
経由で受け渡されています。
どうやら Service Account Token Volume Projection という仕組みが使われているようです。
次に、36~39行目の env
の部分です。
spec:
containers:
・・・
env:
- name: AWS_DEFAULT_REGION
value: ap-northeast-1
- name: ENABLE_IRP
value: "true"
- name: AWS_ROLE_ARN
value: arn:aws:iam::XXXXXXXXXXXX:role/eksctl-s3echotest-addon-iamserviceaccount-de-Role1-9VW8WH4S9BT
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
この箇所は、IRSAを使用しない(ServiceAccountを指定しない)場合は以下のようになります。
spec:
containers:
・・・
env:
- name: AWS_DEFAULT_REGION
value: ap-northeast-1
- name: ENABLE_IRP
value: "true"
IRSA使用時には、Jobの定義YAMLには記述されていなかった AWS_ROLE_ARN
、AWS_WEB_IDENTITY_TOKEN_FILE
という2つの環境変数が追加指定されています。
指定されたIAMロールのARNと、OIDCを使って取得されたSTSトークンの場所が、環境変数経由でPod内コンテナに情報伝達されているようです。
このように、IRSAが使用される場合は「Pod定義に対して必要な設定が自動的に追加(注入/injection)される」ということを覚えておくとよいかと思います。
追加検証:複数のIAMロールと複数のPodの組み合わせでIRSAの動作を確認する
長丁場になりつつありますが、最後に追加で検証を行ってみたいと思います。
チュートリアルでは1つのIAMロールを1つのPodに割り当てるという内容でしたが、これだと「Pod単位のアクセス制御」が本当に行えるのかイマイチ分からないのではないかと思います。
そこで、最初に示した図のように、2つのS3バケットに対するアクセス権限を持つ2種類のIAMロールを用意した上で、複数のPodに別々のアクセス権限を適用できることを確認します。
EKSクラスター、ワーカーノード、OIDCプロバイダー
チュートリアルで作成したものをそのまま利用します。
S3バケットの作成
S3バケットを2つ作成します。
$ aws s3api create-bucket \
--bucket s3-echoer-bucket-a \
--create-bucket-configuration LocationConstraint=$(aws configure get region) \
--region $(aws configure get region)
$ aws s3api create-bucket \
--bucket s3-echoer-bucket-b \
--create-bucket-configuration LocationConstraint=$(aws configure get region) \
--region $(aws configure get region)
IAMポリシーの作成
作成した2つの各S3バケットに対するフルアクセス権限を定義したIAMポリシーを作成します。
s3-echoer-a-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::s3-echoer-bucket-a",
"arn:aws:s3:::s3-echoer-bucket-a/*"
]
}
]
}
s3-echoer-b-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::s3-echoer-bucket-b",
"arn:aws:s3:::s3-echoer-bucket-b/*"
]
}
]
}
$ aws iam create-policy --policy-name s3-echoer-a-policy --policy-document file://s3-echoer-a-policy.json
$ aws iam create-policy --policy-name s3-echoer-b-policy --policy-document file://s3-echoer-b-policy.json
IAMロール・Kubernetesサービスアカウントの作成
作成した2つの各IAMポリシーをアタッチしたIAMロール、および、IAMロールに対応するKubernetesサービスアカウントを作成します。
$ eksctl create iamserviceaccount \
--name s3-echoer-a \
--cluster s3echotest \
--attach-policy-arn arn:aws:iam::XXXXXXXXXXXX:policy/s3-echoer-a-policy \
--approve
$ eksctl create iamserviceaccount \
--name s3-echoer-b \
--cluster s3echotest \
--attach-policy-arn arn:aws:iam::XXXXXXXXXXXX:policy/s3-echoer-b-policy \
--approve
Jobリソースの作成
以下の2つのJobリソースを作成します。
- s3-echoer-a-to-a-job
- サービスアカウント: バケットAへのアクセス権限を持つIAMロールに対応するサービスアカウント
- 処理内容: バケットAに対してオブジェクトの作成を試みる
- s3-echoer-a-to-b-job
- サービスアカウント: バケットAへのアクセス権限を持つIAMロールに対応するサービスアカウント
- 処理内容: バケットBに対してオブジェクトの作成を試みる
s3-echoer-a-to-a-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: s3-echoer-a-to-a
spec:
template:
spec:
serviceAccountName: s3-echoer-a
containers:
- name: main
image: amazonlinux:2018.03
command:
- "sh"
- "-c"
- "curl -sL -o /s3-echoer https://github.com/mhausenblas/s3-echoer/releases/latest/download/s3-echoer-linux && chmod +x /s3-echoer && echo My name is s3-echoer-a-to-a | /s3-echoer s3-echoer-bucket-a"
env:
- name: AWS_DEFAULT_REGION
value: "ap-northeast-1"
- name: ENABLE_IRP
value: "true"
restartPolicy: Never
s3-echoer-a-to-b-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: s3-echoer-a-to-b
spec:
template:
spec:
serviceAccountName: s3-echoer-a
containers:
- name: main
image: amazonlinux:2018.03
command:
- "sh"
- "-c"
- "curl -sL -o /s3-echoer https://github.com/mhausenblas/s3-echoer/releases/latest/download/s3-echoer-linux && chmod +x /s3-echoer && echo My name is s3-echoer-a-to-b | /s3-echoer s3-echoer-bucket-b"
env:
- name: AWS_DEFAULT_REGION
value: "ap-northeast-1"
- name: ENABLE_IRP
value: "true"
restartPolicy: Never
動作の確認
まず、s3-echoer-a-to-a-job
の方から実行してみます。
$ kubectl apply -f s3-echoer-a-to-a-job.yaml
job.batch/s3-echoer-a-to-a created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
s3-echoer-a-to-a-ddglm 0/1 Completed 0 13s
Podは正常に処理完了したようです。
S3バケットに作成されたオブジェクトも確認してみます。
$ aws s3 ls s3://s3-echoer-bucket-a
2019-09-08 16:14:03 23 s3echoer-1567926840
$ aws s3 cp s3://s3-echoer-bucket-a/s3echoer-1567926840 .
download: s3://s3-echoer-bucket-a/s3echoer-1567926840 to ./s3echoer-1567926840
$ cat s3echoer-1567926840
My name is s3-echoer-a-to-a
オブジェクトが正常に作成されたことが確認できました。
次に、s3-echoer-a-to-b-job
の方を実行してみます。
$ kubectl apply -f s3-echoer-a-to-b-job.yaml
job.batch/s3-echoer-a-to-b created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
s3-echoer-a-to-a-ddglm 0/1 Completed 0 4m6s
s3-echoer-a-to-b-6vnbh 0/1 Error 0 10s
Podがエラーとなっています。
Podのログを確認してみます。
$ kubectl logs s3-echoer-a-to-b-6vnbh
Uploading user input to S3 using s3-echoer-bucket-b/s3echoer-1567927074
2019/09/08 07:17:57 Can't upload to S3: AccessDenied: Access Denied
status code: 403, request id: 50424DF5B99506BE, host id: DqSeg26SkZqQ6NZOj+czciEfEWvBToUayPc0jaMID5v94yLmAl0/gNP3Idr97foLhqGeo5dZDrE=
アクセス権限が無いためオブジェクトを作成できなかったことが分かります。
同様にして、Job s3-echoer-b-to-b-job
、s3-echoer-b-to-a-job
を作成して実行すると、前者はS3バケットへアクセスすることができ、後者はアクセス不可になることが確認できるかと思います。(Job定義および実行結果は割愛します)
- s3-echoer-b-to-b-job
- サービスアカウント: バケットBへのアクセス権限を持つIAMロールに対応するサービスアカウント
- 処理内容: バケットBに対してオブジェクトの作成を試みる
- s3-echoer-b-to-a-job
- サービスアカウント: バケットBへのアクセス権限を持つIAMロールに対応するサービスアカウント
- 処理内容: バケットAに対してオブジェクトの作成を試みる
このように、IAM Role for Service Accountsを使用することにより、Pod単位でIAMロールを割り当ててアクセス制御ができることが確認できました。
おわりに
以上、EKSの「IAM Roles for Service Accounts」アップデートの概要と使用方法についてご紹介しました。
AWS側のIAMロールとKubernetes側のサービスアカウントが1対1で対応しているため、アクセス権限の管理が分かり易く行えるのではないかと思います。
eksctlコマンドを使うことでAWSとKubernetesの設定が簡略化できる点や、裏で動いているSTSトークンなどの設定まわりを自動追加してくれる点など、設定まわりも分かり易くなっているのではないでしょうか。
EKSを使う上で非常に有用なアップデートだと思いますので、みなさんも是非、本ブログを参考に試してみてください。