はじめに
「Kubernetes on AWS」な環境においてIAMと連係したアクセス制御を行う仕組みとして、「kube2iam」や「kiam」などのOSSが開発されています。
それらのうちkube2iamについて、Kops等を使用して構築したKubernetesクラスタに対するkube2iamの導入手順は、既にそれなりの数の情報を見ることができます。
しかし、Amazon EKS環境については具体的な情報を見かけませんでしたので、試してみた過程をまとめました。
(既に情報が出ていればすみません)
参考にしたドキュメント・記事
kube2iam 公式ドキュメント
https://github.com/jtblin/kube2iam
AWS Workshop for Kubernetes / 402 - Authentication and Authorization with Kubernetes
https://github.com/aws-samples/aws-workshop-for-kubernetes/tree/master/04-path-security-and-networking/402-authentication-and-authorization
AWS ECSのタスク同様に、KubernetesでもPod毎にIAMロールを設定する仕組みと方法
https://qiita.com/mumoshu/items/3dd5179efce8f626a99c
kube2iam で Kubernetes の Pod に IAM role を割り当てる
https://blog.shiftky.net/integrate-aws-iam-with-kubernetes-using-kube2iam/
kube2iamとは?
KubernetesのPodからAWSリソース(S3等)へアクセスする方法は、主に以下の2通りがあります。
- AWSリソースへのアクセス権限を持つIAMユーザーを作成し、Pod内でIAMユーザーのアクセスキー/シークレットアクセスキーを指定する。
- Podが配置されているホスト(EC2)に対してIAMロールをアタッチし、STSにより一時的なアクセスキーを取得する。
いずれも、EC2上のプログラムからAWSリソースへアクセスする際に用いる仕組みを踏襲しています。
ただし、EC2上での仕組みがKubernetesにそのまま適用できる訳ではなく、Kubernetes特有の仕組みと組み合わせて行います。
1.においては、Pod内のプログラムへアクセスキー/シークレットアクセスキーを安全に渡すために、Kubernetesの「Secret」リソースを使います。
2.においては、Pod内のプログラムへ一時的なアクセスキーを適用するスタンダードな仕組みはまだありませんが、現在は「kube2iam」や「kiam」といったOSSが公開されています。
何故「kube2iam」や「kiam」のような仕組みが必要か?
Podが稼働するホスト(EC2)に対してIAMロールを介したアクセス権限を与えた場合、ホスト上で動作する全Podに一律のアクセス権を与えることになります。
それではセキュリティ的によろしくないので、Pod単位でIAMアクセスポリシーを割り当てる仕組みが考えられました。
Kubernetes(のアクセス管理機能)とIAMの間に入ってアクセス権限の管理を行ってくれるのが「kube2iam」や「kiam」です。
kube2iamの利用手順
前提
ここでは、Amazon EKSユーザーガイドの「ご利用開始にあたって(Getting Started)」の手順に従って構築したEKSクラスタ環境を前提としています。
https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/getting-started.html
1. IAMロールの準備
まず、kube2iamで必要となる2つのIAMロールを準備します。
(1) Worker Node用のIAMロール
Getting Startedの手順でクラスタを構築した場合、Worker Node用のIAMロールとして「XXXXXXXX-NodeInstanceRole-YYYYYYYYYYYY」という名前のロールが既に作成されているはずです。
(XXXXXXXX:作成時に付けた任意の名前、YYYYYYYYYYYY:ランダムな英数字)
このロールに対して「任意のロールに対してAssumeRoleを行うことができる権限」を与えます。
付与するポリシーをJSONで記述:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "*"
}
]
}
記述したJSONを指定してポリシーを作成:
aws iam create-policy \
--policy-name sts-assumerole-policy \
--policy-document file://node-role-permission-policy.json
ロールにポリシーをアタッチ:
aws iam attach-role-policy \
--role-name XXXXXXXX-NodeInstanceRole-YYYYYYYYYYYY \
--policy-arn arn:aws:iam::123456789012:policy/sts-assumerole-policy
(2) Pod用のIAMロール
Podに対してアクセス権限を設定するためのIAMロールを作成します。
IAMロールの信頼関係ポリシードキュメントとして、「EC2」および「Worker Node用のIAMロール」からのAssumeRoleを許可する設定を記述します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
},
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/XXXXXXXX-NodeInstanceRole-YYYYYYYYYYYY"
},
"Action": "sts:AssumeRole"
}
]
}
記述した信頼関係ポリシードキュメントのJSONファイルを指定して、IAMロールを作成します。
aws iam create-role \
--role-name MyPodRole \
--assume-role-policy-document file://pod-role-trust-policy.json
作成したロールに対して、具体的に付与したい「AWSリソースへのアクセス権限」を設定します。
例えば、AWS管理ポリシーの「S3読み取り専用」権限であれば、以下のようにします。
aws iam attach-role-policy \
--role-name MyPodRole \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
2. kube2iamのデプロイ準備
2つのマニフェスト(yaml)を記述します。
(わかりやすいように2つに分けただけなので、1つにまとめても問題ありません)
kube2iam-sa.yaml
- kube2iamがKubernetes上で動作するために必要な権限を設定します。
作成するリソース: ServiceAccount、ClusterRole、ClusterRoleBinding
kube2iam-ds.yaml
- kube2iamの処理を行う実体となるコンテナを各ホスト上に配置します。
作成するリソース: DaemonSet
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: kube2iam
namespace: kube-system
---
apiVersion: v1
items:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kube2iam
rules:
- apiGroups: [""]
resources: ["namespaces","pods"]
verbs: ["get","watch","list"]
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kube2iam
subjects:
- kind: ServiceAccount
name: kube2iam
namespace: kube-system
roleRef:
kind: ClusterRole
name: kube2iam
apiGroup: rbac.authorization.k8s.io
kind: List
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube2iam
namespace: kube-system
labels:
app: kube2iam
spec:
selector:
matchLabels:
app: kube2iam
template:
metadata:
labels:
app: kube2iam
spec:
serviceAccountName: kube2iam
hostNetwork: true
containers:
- name: kube2iam
image: jtblin/kube2iam:latest
imagePullPolicy: Always
args:
- "--auto-discover-base-arn"
- "--iptables=true"
- "--host-ip=$(HOST_IP)"
- "--host-interface=eni+"
- "--verbose"
env:
- name: HOST_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
ports:
- containerPort: 8181
hostPort: 8181
name: http
securityContext:
privileged: true
コンテナ「kube2iam」のオプションパラメーター(args:
)の意味は以下の通りです。
--auto-discover-base-arn
- 文字通り、ロールのARNのベース部分(arn:aws:iam::123456789012:role/)を自動的に検出してくれる設定です。
うまく検出してくれない場合は、明示的に--base-role-arn=arn:aws:iam::123456789012:role/
と指定する必要があるかもしれません。
--iptables=true
- このオプションで「true」を指定した場合、ホスト(EC2)のiptablesに必要な設定を自動的に登録します。
この指定を行わないと、手動でiptablesの設定を行う必要があります。
--host-ip=$(HOST_IP)
- 「--iptables」を指定する場合は「--host-ip」も必ず指定する必要があります。
--host-interface=eni+
- 「Getting Started」の手順に従ってEKS環境を構築した場合はPod Networkingとして「amazon-vpc-cni-k8s」が採用されるため、このオプションは「eni+」を指定する必要があります。
--verbose
- 詳細なログを出力します。トラブルシューティング時に役立つでしょう。(役立ちました)
3. kube2iamのデプロイ
準備したマニフェストを使ってkube2iamをデプロイします。
$ kubectl apply -f kube2iam-sa.yaml
serviceaccount "kube2iam" created
clusterrole.rbac.authorization.k8s.io "kube2iam" created
clusterrolebinding.rbac.authorization.k8s.io "kube2iam" created
$ kubectl apply -f kube2iam-ds.yaml
daemonset.apps "kube2iam" created
DaemonSetが登録され、Podが正常に動作していることを確認します。
$ kubectl get daemonsets -n kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
aws-node 3 3 3 3 3 <none> 5d
kube-proxy 3 3 3 3 3 <none> 5d
kube2iam 3 3 3 3 3 <none> 5m
$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
aws-node-bmv7x 1/1 Running 0 5d
aws-node-lnrkb 1/1 Running 1 5d
aws-node-nkx8j 1/1 Running 0 5d
kube-dns-7cc87d595-7f4gk 3/3 Running 0 5d
kube-proxy-ffshl 1/1 Running 0 5d
kube-proxy-nbsww 1/1 Running 0 5d
kube-proxy-xq47h 1/1 Running 0 5d
kube2iam-2ffpd 1/1 Running 0 5m
kube2iam-2h5fd 1/1 Running 0 5m
kube2iam-wtlsz 1/1 Running 0 5m
Podのログを見てみます。
$ kubectl logs -n kube-system kube2iam-2ffpd
time="2018-08-08T09:28:40Z" level=info msg="base ARN autodetected, arn:aws:iam::123456789012:role/"
time="2018-08-08T09:28:40Z" level=debug msg="Namespace OnAdd" ns.name=default
time="2018-08-08T09:28:40Z" level=debug msg="Namespace OnAdd" ns.name=kube-public
time="2018-08-08T09:28:40Z" level=debug msg="Namespace OnAdd" ns.name=kube-system
time="2018-08-08T09:28:40Z" level=debug msg="Pod OnAdd" pod.iam.role= pod.name=aws-node-nkx8j pod.namespace=kube-system pod.status.ip=192.168.215.249 pod.status.phase=Running
time="2018-08-08T09:28:40Z" level=debug msg="Pod OnAdd" pod.iam.role= pod.name=kube-proxy-ffshl pod.namespace=kube-system pod.status.ip=192.168.92.232 pod.status.phase=Running
time="2018-08-08T09:28:40Z" level=debug msg="Pod OnAdd" pod.iam.role= pod.name=kube-proxy-nbsww pod.namespace=kube-system pod.status.ip=192.168.140.55 pod.status.phase=Running
time="2018-08-08T09:28:40Z" level=debug msg="Pod OnAdd" pod.iam.role= pod.name=kube2iam-2ffpd pod.namespace=kube-system pod.status.ip=192.168.140.55 pod.status.phase=Pending
time="2018-08-08T09:28:40Z" level=debug msg="Pod OnAdd" pod.iam.role= pod.name=kube-dns-7cc87d595-7f4gk pod.namespace=kube-system pod.status.ip=192.168.95.202 pod.status.phase=Running
time="2018-08-08T09:28:40Z" level=debug msg="Pod OnAdd" pod.iam.role= pod.name=kube2iam-wtlsz pod.namespace=kube-system pod.status.ip=192.168.215.249 pod.status.phase=Pending
time="2018-08-08T09:28:40Z" level=debug msg="Pod OnAdd" pod.iam.role= pod.name=aws-node-bmv7x pod.namespace=kube-system pod.status.ip=192.168.92.232 pod.status.phase=Running
time="2018-08-08T09:28:40Z" level=debug msg="Pod OnAdd" pod.iam.role= pod.name=aws-node-lnrkb pod.namespace=kube-system pod.status.ip=192.168.140.55 pod.status.phase=Running
time="2018-08-08T09:28:40Z" level=debug msg="Pod OnAdd" pod.iam.role= pod.name=kube2iam-2h5fd pod.namespace=kube-system pod.status.ip=192.168.92.232 pod.status.phase=Pending
time="2018-08-08T09:28:40Z" level=debug msg="Pod OnAdd" pod.iam.role= pod.name=kube-proxy-xq47h pod.namespace=kube-system pod.status.ip=192.168.215.249 pod.status.phase=Running
time="2018-08-08T09:28:40Z" level=debug msg="Caches have been synced. Proceeding with server."
time="2018-08-08T09:28:40Z" level=info msg="Listening on port 8181"
time="2018-08-08T09:28:40Z" level=debug msg="Pod OnUpdate" pod.iam.role= pod.name=kube2iam-wtlsz pod.namespace=kube-system pod.status.ip=192.168.215.249 pod.status.phase=Running
time="2018-08-08T09:28:40Z" level=debug msg="Pod OnUpdate" pod.iam.role= pod.name=kube2iam-2ffpd pod.namespace=kube-system pod.status.ip=192.168.140.55 pod.status.phase=Running
time="2018-08-08T09:28:40Z" level=debug msg="Pod OnUpdate" pod.iam.role= pod.name=kube2iam-2h5fd pod.namespace=kube-system pod.status.ip=192.168.92.232 pod.status.phase=Running
デフォルトのkube2iamポート「8181」で待ち受けを開始しているので、とりあえずOKのようです。
動作テスト
kube2iamを使用する設定を記述したPod(ReplicaSet)をデプロイして、AWSリソースへ実際にアクセスできることを確認します。
アノテーションiam.amazonaws.com/role:
に、アクセス権限を設定したロールの名前(ここではMyPodRole
)を設定します。
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: mypod-awscli
spec:
replicas: 1
selector:
matchLabels:
app: mypod-awscli
template:
metadata:
annotations:
iam.amazonaws.com/role: MyPodRole
labels:
app: mypod-awscli
spec:
containers:
- name: aws-cli
image: fstab/aws-cli
command: [ "/bin/bash", "-c", "--" ]
args: [ "while true; do sleep 30; done;" ]
ReplicaSetをデプロイし、Podが起動したことを確認したら、Podに接続してbashを起動します。
$ kubectl apply -f mypod-awscli.yaml
replicaset.apps "mypod-awscli" created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
mypod-awscli-94hgz 1/1 Running 0 52s
$ kubectl exec mypod-awscli-94hgz -it /bin/bash
(env) aws@mypod-awscli-94hgz:~$
バケットリストの参照を試み、参照できることを確認します。
(env) aws@mypod-awscli-94hgz:~$ aws s3 ls
2018-08-08 09:55:48 mybucket20180808
削除を試みると、読み取り権限しか与えられていないため、失敗します。
(env) aws@mypod-awscli-94hgz:~$ aws s3 rb s3://mybucket20180808
remove_bucket failed: s3://mybucket20180808/ An error occurred (AccessDenied) when calling the DeleteBucket operation: Access Denied
検証中に確認したエラー (失敗の記録)
その1:「ServiceAccount」を設定しなかった場合
ドキュメントによっては、「ServiceAccount」を設定せずにDaemonSetのデプロイのみの手順で説明されているものがあります。
そのようにすると、kube2iamのPodログに以下のようなエラーが記録され続けて、正しく動作しません。
$ kubectl logs kube2iam-mqqv6 -n kube-system
time="2018-08-13T18:13:00Z" level=info msg="base ARN autodetected, arn:aws:iam::123456789012:role/"
E0813 18:13:00.423621 1 reflector.go:199] github.com/jtblin/kube2iam/vendor/k8s.io/client-go/tools/cache/reflector.go:94: Failed to list *v1.Namespace: namespaces is forbidden: User "system:serviceaccount:kube-system:default" cannot list namespaces at the cluster scope
E0813 18:13:00.424728 1 reflector.go:199] github.com/jtblin/kube2iam/vendor/k8s.io/client-go/tools/cache/reflector.go:94: Failed to list *v1.Pod: pods is forbidden: User "system:serviceaccount:kube-system:default" cannot list pods at the cluster scope
E0813 18:13:01.426627 1 reflector.go:199] github.com/jtblin/kube2iam/vendor/k8s.io/client-go/tools/cache/reflector.go:94: Failed to list *v1.Namespace: namespaces is forbidden: User "system:serviceaccount:kube-system:default" cannot list namespaces at the cluster scope
E0813 18:13:01.427648 1 reflector.go:199] github.com/jtblin/kube2iam/vendor/k8s.io/client-go/tools/cache/reflector.go:94: Failed to list *v1.Pod: pods is forbidden: User "system:serviceaccount:kube-system:default" cannot list pods at the cluster scope
E0813 18:13:02.428618 1 reflector.go:199] github.com/jtblin/kube2iam/vendor/k8s.io/client-go/tools/cache/reflector.go:94: Failed to list *v1.Namespace: namespaces is forbidden: User "system:serviceaccount:kube-system:default" cannot list namespaces at the cluster scope
E0813 18:13:02.429634 1 reflector.go:199] github.com/jtblin/kube2iam/vendor/k8s.io/client-go/tools/cache/reflector.go:94: Failed to list *v1.Pod: pods is forbidden: User "system:serviceaccount:kube-system:default" cannot list pods at the cluster scope
EKS環境ではRBACが使われているため、ServiceAccountの設定は必須であるようです。
その2:「--host-interface=eni+」を指定しなかった場合
kube2iamは、iptablesの設定によってPod・ホスト(EC2)間の通信をフックし、EC2インスタンスメタデータを書き換えることでアクセス制御を行います。
「--host-interface」オプションを省略した場合は「docker0」がデフォルト値で使われますが、その場合のiptablesの状態は以下のようになります。
[ec2-user@ip-192-168-64-19 ~]$ sudo iptables -L -t nat --verbose --line-numbers
Chain PREROUTING (policy ACCEPT 1 packets, 60 bytes)
num pkts bytes target prot opt in out source destination
1 3 180 KUBE-SERVICES all -- any any anywhere anywhere /* kubernetes service portals */
2 1 60 DOCKER all -- any any anywhere anywhere ADDRTYPE match dst-type LOCAL
3 0 0 DNAT tcp -- docker0 any anywhere instance-data.us-west-2.compute.internal tcp dpt:http to:192.168.64.19:8181
3番目の「doker0」に対する設定がありますが、「pkts」「bytes」の値がいずれも「0」であることに注目して下さい。(つまり、ここをパケットが全く通っていない=Pod・ホスト間の通信に使われていない)
一方、「-host-interface=eni+」を指定した場合のiptablesは以下のようになります。
[ec2-user@ip-192-168-92-232 ~]$ sudo iptables -L -t nat --verbose --line-numbers
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 262K 20M KUBE-SERVICES all -- any any anywhere anywhere /* kubernetes service portals */
2 34 2040 DOCKER all -- any any anywhere anywhere ADDRTYPE match dst-type LOCAL
3 1028 61680 DNAT tcp -- eni+ any anywhere instance-data.us-west-2.compute.internal tcp dpt:http to:192.168.92.232:8181
こちらは、ちゃんとパケットが通っていることが分かります。
おわりに (「kiam」について)
とりあえず「EKSでkube2iamが動かせた」というレベルなので、これからまだ確認していかなければならないことが多々あると思います。
一方「kiam」についてですが、EKSで動かしてみようとしましたが、上手く行っていません。
kiamはモジュールが「server」と「agent」に分かれており、「server」モジュールはKubernetesのMaster Nodeで動かす前提になっています。
ところが、EKSはMaster NodeがマネージドなのでPodを配置できません。
無理やりWorker Nodeに配置するようにマニフェストを書き換えてみましたが、いろんなエラーで動かない状況です。
そもそも、kiamのアーキテクチャ的に、マネージドなMaster Nodeを持つEKSに適合するものなのかどうか、そこすら良く分かっていません。
何か分かったら、また記事にしたいと思います。