本記事は『リスクから学ぶ Kubernetesコンテナセキュリティ コンテナ開発者がおさえておくべき基礎知識』(著:望月敬太)の「CASE8:PodからKubernetesクラスタを不正に操作されてしまった」から抜粋したものです。掲載にあたって編集しています。
対策の基本原則
ServiceAccountにはRBACの仕組みを使用することで、Kubernetesクラスタに関する様々な権限を付与できます。また、パブリッククラウドでは、クラウドサービスの権限をServiceAccountに紐付ける機能も提供されています(例えばAmazon EKSにはIAM roles for service accountsという機能があります)。
これらの仕組みを利用する際に意識すべきことは、Podに紐付けるServiceAccountにはPodが正常に動作するために必要な権限のみを付与し、過剰な権限を付与しないという最小権限の原則に準拠することです(図1)。
一般的なアプリケーションをPodとしてデプロイする場合、Kubernetesクラスタに対する権限を必要とすることはほとんどありません。そのため、そのようなPodには可能な限り権限を付与しないことが望ましいと言えます。
また、例えばOperatorなどKubernetesクラスタに対して何かしらの操作を行うコンポーネントをPodとしてデプロイする場合や、Podとしてデプロイしたアプリケーションがクラウドサービスにアクセスする必要がある場合などは、特定の権限をPodに付与する必要があります。その場合も、付与する権限を必要最小限に留めることが重要です。
対策1 default ServiceAccountを使用する
KubernetesではNamespaceごとに、defaultというServiceAccountが存在します。このServiceAccountは、Namespaceの作成にあわせて自動的に作成され、基本的に権限が何も付与されていません(厳密にはAPI discovery rolesというKubernetesのAPI情報を参照するURLへのアクセスを許可する権限が付与されますが、特にセキュリティ上問題になる性質のものではなく、Kubernetesクラスタの設定で無効化することもできます)。
Podをデプロイする際、serviceAccountNameフィールドでServiceAccountを指定しない場合は、このdefaultServiceAccountが自動的にPodに紐付けられるようになっています。
基本原則でも触れた通り、一般的にPodがKubernetesクラスタに対する権限を必要とする場面は限られており、特にアプリケーションをPodとしてデプロイする場合は、そのような権限が必要になることはほとんどありません。
そのため、特に理由がない限りは、Podをデプロイする際にServiceAccountを指定せず、Podにdefault ServiceAccountを紐付けることで、不要な権限が付与されないようにすると良いでしょう。
ただし、default ServiceAccountに対してRoleBindingやClusterRoleBindingを用いて権限の紐付けを行うと、ServiceAccountを指定せずにデプロイした全てのPodに対して権限が付与されてしまうため注意が必要です。
default ServiceAccountをPodに紐付けても、PodからKubernetesクラスタに対する操作を行えないことを確認します。まずは、Namespaceにdefault ServiceAccountが存在することを確認します。
$ kubectl get serviceaccount default NAME SECRETS AGE default 0 2d
リスト2のマニフェストを作成します。このマニフェストでは、serviceAccountNameフィールドでServiceAccountを指定していません。そのため、Podにはdefault ServiceAccountが自動的に紐付けられます。
apiVersion: v1 kind: Pod metadata: name: default-sa-pod labels: app: default-sa-pod spec: containers: - name: ubuntu image: ubuntu:22.04 command: ["/bin/sh", "-c", "while :; do sleep 10; done"]
リスト2のマニフェストを適用してPodをデプロイすると、リスト3のようにdefault ServiceAccountが自動的に紐付けられた状態でPodがデプロイされることを確認できます。
$ kubectl apply -f default-sa-pod.yaml $ kubectl get pod default-sa-pod -o yaml apiVersion: v1 kind: Pod metadata: ... name: default-sa-pod namespace: default . .. spec: ... serviceAccountName: default ...
default ServiceAccountには、権限が何も付与されていません。そのため、このPodに含まれるコンテナ内で先ほどのようにkubectlコマンドを実行すると、Kubernetesクラスタに対してアクセスを行い認証を行うことはできても認可エラー(Forbidden)となり、Kubernetesクラスタに対する操作を行うことはできません(kubectlコマンドのインストール手順はリスト4を参照してください)。
$ kubectl exec -it default-sa-pod -- /bin/bash < kubectl コマンドのインストール(略)> root@default-sa-pod:/# kubectl get nodes Error from server (Forbidden): nodes is forbidden: User "system:serviceaccount: default:default" cannot list resource "nodes" in API group "" at the cluster scope root@default-sa-pod:/# exit exit
対策2 ServiceAccount 情報の自動マウントを無効化する
Podに紐付けられたServiceAccountの情報は、Podに含まれるコンテナの/var/run/secrets/kubernetes.io/serviceaccountディレクトリにマウントされることを解説しました。Podをデプロイする際にautomountServiceAccountToken: falseというフィールドを指定することで、コンテナにServiceAccountの情報が自動的にマウントされるのを無効化できます※12。
特にtokenというファイルには、Podに紐付けられたServiceAccountがKubernetesクラスタにアクセスする際の認証情報が含まれているため、必要がなければこのマウントを無効化しておくことを推奨します。なお、automountServiceAccountToken: falseという設定は、ServiceAccountでも行うことができますが、ここではPodのフィールドで設定する例を解説します。まずは、リスト5のマニフェストを作成します。
apiVersion: v1 kind: Pod metadata: name: no-sa-token-pod labels: app: no-sa-token-pod spec: containers: - image: ubuntu:22.04 name: ubuntu command: ["/bin/sh", "-c", "while :; do sleep 10; done"] automountServiceAccountToken: false
リスト5のマニフェストを適用してPodをデプロイすると、/var/run/secrets/kubernetes.io/serviceaccountディレクトリにServiceAccountの情報がマウントされていないことを確認できます。
$ kubectl apply -f no-sa-token-pod.yaml $ kubectl get pod no-sa-token-pod -o yaml apiVersion: v1 kind: Pod metadata: annotations: ... name: no-sa-token-pod namespace: default ... spec: automountServiceAccountToken: false ... serviceAccountName: default ... $ kubectl exec -it no-sa-token-pod -- ls /var/run/secrets/kubernetes.io/serviceaccount ls: cannot access '/var/run/secrets/kubernetes.io/serviceaccount': No such file or directory command terminated with exit code 2
設定上はこのPodにdefault ServiceAccountが紐付けられていることになりますが、実際はこのPodにはdefault ServiceAccountの情報はマウントされていません。そのため、リスト7のようにPodはKubernetesクラスタに対する認証を行い、アクセスすることができない状態です
$ kubectl exec -it no-sa-token-pod -- /bin/bash < kubectl コマンドのインストール(略)> root@no-sa-token-pod:/# kubectl get nodes E0207 15:03:35.039747 2804 memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused ... root@no-sa-token-pod:/# exit exit
まとめ
ここまで、Podに付与された過剰な権限が要因となり発生し得るリスクの対策について解説しました。
Podに紐付けたServiceAccountに権限が付与されている場合、Podに侵入した攻撃者はServiceAccountに紐付けられた権限をそのまま利用できることになるため、特に必要がない場合は権限を付与するべきではありません。また、Podになんらかの権限が必要な場合は、付与する権限を必要最小限に留めることが重要です。
最後に、今回作成したリソースを削除します。
$ kubectl delete pod sample-pod \ malicious-pod \ default-sa-pod \ no-sa-token-pod $ kubectl delete clusterrolebinding sample-crb $ kubectl delete serviceaccount sample-sa