Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[Alpha] PodでUserNamespaceを使おう

Junya Taniai
November 15, 2022
430

[Alpha] PodでUserNamespaceを使おう

Kubernetes Novice Tokyo #22 ( https://k8s-novice-jp.connpass.com/event/262918/?utm_campaign=event_reminder&utm_source=notifications&utm_medium=email&utm_content=detail_btn ) のセッション資料です。
Kubernetes 1.25でAlphaで導入されたUser NamespaceをPodで使う方法やメリットについて紹介しています。

セッション動画はこちらです。
https://www.youtube.com/watch?v=_XwvFrMykWo

Junya Taniai

November 15, 2022
Tweet

Transcript

  1. 2022/11/15 6 1. KubernetesでのUser Namespaceの扱い ノードのUID 0 262143 196608 0

    65535 PodのUID KubernetesではノードのUIDおよびGID範囲内の⼀部(range 65536)をPodのUID/GID 0〜65535にマッピングする。 つまり、ノード上では⾮rootで動いているが、Pod内ではrootで動いているように⾒せかけることが可能。 UID範囲外の操作をしようとすると、権限不⾜が発⽣する。
  2. 8 2. 実装を覗いてみよう ノードのUID/GIDをPodのUser Namespceにマッピングする流れは以下の通りです。 1. NewMainKubelet関数で、Kubelet構造体をruntimeHelperに指定してruntimeを⽣成。 この時Kubelet構造体はポインタを渡す。 2. Kubelet構造体にusernsManagerフィールドを設定。

    usernsManagerはUIDとGIDを⽣成する際に必要なbitArrayなどが含まれる。 3. Podがデプロイされたら、 determineEffectiveSecurityContext関数が呼ばれ、1で⽣成したruntime. runtimeHelperを 引数にruntimeutil.NamespacesForPod関数を呼び出す。 4. NamespacesForPod関数からruntimeHelper.GetOrCreateUserNamespaceMappings関数を呼び出す。 5. GetOrCreateUserNamespaceMappings関数からcreateUserNs関数→ allocateOne関数→ findAvailable関数の順で呼び 出す。 6. findAvailable関数でUIDとGID計算⽤の値を算出 7. $(UIDとGID計算⽤の値 * 65536)の式にて、 UIDとGID算出 8. ノードの/var/lib/kubelet/pods/$(PodUUID)/usernsファイルにマッピング情報を書き出す 9. マッピング情報をNamespaceOption構造体に設定して、Podを起動する。 kubelet 起動時 Pod デプロイ時
  3. Kubelet 2022/11/15 2. 実装を覗いてみよう NewMainKubelet() runtime usernsManager Kubelet. determineEffective SecurityContext()

    usernsManager. allocateOne() usernsManager. bitArray. findAvailable() UIDとGIDの計算に 使⽤する値を⽣成 UIDとGIDの計算に 使⽤する値のreturn kl.usernsManager. GetOrCreateUserName spaceMappings() UIDとGIDを 計算してreturn /var/lib/kubelet/pods/ $(PodUUID)/userns UIDとGIDのマッピング 状況を書き込み usernsManager. createUserNs() UIDとGIDのマッピング 状況のreturn NamespaceOption のreturn Podの起動 上記処理の流れはいくつか関数を省略しています。 詳細なコードリーディング結果は、以下のZennスクラップを ご確認ください。 - [kubelet] User Namespaceのコードリーディング https://zenn.dev/junya0530/scraps/c75784ea5b8a5b 9
  4. 2022/11/15 11 3. 制限事項 User Namespaceは便利︕ただし現時点では制限があります。 1. Alphaで使⽤可能なVolume • Configmap

    • Secret • downwardAPI • emptyDir • Projected 2. ノード上でUser Namespaceを利⽤可能なPodはMax1024個 // Limit the total number of pods using userns in this node to this value. // This is an alpha limitation that will probably be lifted later. const maxPods = 1024 https://github.com/kubernetes/kubernetes/blob/641197dc9562d8affd428d05046a8921b3a367e0/pkg/kubelet/userns_manager.go#L43-L45
  5. 2022/11/15 14 $ cat << EOT | kubectl apply -f

    - apiVersion: v1 kind: Pod metadata: name: escapepod-priv spec: hostPID: true containers: - image: ubuntu name: escapepod-priv command: ["sleep", "infinity"] securityContext: privileged: true EOT pod/escapepod-priv created ü パターン1 hostPID = trueかつ、privileged = true $ kubectl exec escapepod-priv -it -- bash root@escapepod-priv:/# hostname escapepod-priv root@escapepod-priv:/# nsenter -t 1 -a bash root@v1252-worker:/# hostname v1252-worker 4. 何が嬉しいの? ~その1
  6. 2022/11/15 15 $ cat << EOT | kubectl apply -f

    - apiVersion: v1 kind: Pod metadata: name: escapepod-hostpath spec: hostNetwork: true hostPID: true hostIPC: true containers: - name: escapepod-hostpath image: busybox command: [ "tail", "-f", "/dev/null" ] securityContext: privileged: true volumeMounts: - mountPath: /host name: noderoot volumes: - name: noderoot hostPath: path: / EOT pod/escapepod-hostpath created ü パターン2 hostNetwork = true、 hostPID = true、 hostIPC = true、privileged: true、かつノードの / ディレクトリをhostpathでmount $ kubectl exec escapepod-hostpath –it -- sh / # cd /host /host # chroot . root@v1252-worker:/# hostname v1252-worker 4. 何が嬉しいの? ~その1
  7. 2022/11/15 17 if spec.SecurityContext.HostNetwork { allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostNetwork"), "when

    `pod.Spec.HostUsers` is false")) } if spec.SecurityContext.HostPID { allErrs = append(allErrs, field.Forbidden(fldPath.Child("HostPID"), "when `pod.Spec.HostUsers` is false")) } if spec.SecurityContext.HostIPC { allErrs = append(allErrs, field.Forbidden(fldPath.Child("HostIPC"), "when `pod.Spec.HostUsers` is false")) } validation.goのvalidateHostUsers関数にて、以下のようにhost*フィールドとhostPathが禁⽌されており、乗っ取りに必要な権限がすべて剥奪され ている。ちなみprivilegedはUserNamespace内での権限となるため、特に規制はかかっていない。 for i, vol := range spec.Volumes { switch { case vol.EmptyDir != nil: case vol.Secret != nil: case vol.DownwardAPI != nil: case vol.ConfigMap != nil: case vol.Projected != nil: default: allErrs = append(allErrs, field.Forbidden(fldPath.Child("volumes").Index(i), "volume type not supported when `pod.Spec.HostUsers` is false")) } } https://github.com/kubernetes/kubernetes/blob/641197dc9562d8affd428d05046a8921b3a367e0/pkg/apis/core/validation/validation.go#L3138-L3147 https://github.com/kubernetes/kubernetes/blob/641197dc9562d8affd428d05046a8921b3a367e0/pkg/apis/core/validation/validation.go#L3121-L3131 4. 何が嬉しいの? ~その1
  8. 2022/11/15 18 コンテナの乗っ取りを防ぐことで、以下のノードのroot権限奪取による脆弱性について緩和が可能。 • CVE-2019-5736 • CVE-2021-25741 • CVE-2017-1002101 •

    CVE-2021-30465 • CVE-2016-8867 • CVE-2018-15664 今年2022年の以下の脆弱性もUser Namespaceを使⽤することで緩和可能である可能性がある。 • CVE-2022-0492 • CVE-2022-23648 4. 何が嬉しいの? ~その1
  9. 2022/11/15 19 User Namespaceを使⽤することで、強い権限をPod内のUser Namespace内に限定または、無効とすることが できる。 例えば、以下のcapabilityはPod外では無効になります。 • CAP_SYS_MODULE ポッドに付与されても無視され、

    Podはカーネルモジュールをロードすることができません。 • CAP_SYS_ADMIN PodのUser Namespaceに限定され、外では無効となります。 4. 何が嬉しいの? ~その2
  10. 2022/11/15 20 $ cat << EOT | kubectl apply -f

    - apiVersion: v1 kind: Pod : spec: hostUsers: false containers: - image: nginx name: nginx EOT pod/nginx created $ kubectl exec nginx -it – bash root@nginx:/# cat /proc/self/uid_map 0 196608 65536 root@nginx:/# cat /proc/self/gid_map 0 196608 65536 ノード上は⾮rootでもPod内ではrootなので、rootで起動が必要なcontainerも起動可能 ※パラメータの意味は後述します。 4. 何が嬉しいの? ~その3
  11. 2022/11/15 22 5. 有効化⽅法 前提条件: • CRIがCRI-O v1.25以上であること (containerdはv1.7からuser namespaceをサポート予定)

    ※ 1 containerd現バージョンは1.6.9 有効化 - kube-apiserverと、ワーカーノードのkubeletでFeatureGate UserNamespacesStatelessPodsSupportを 有効化します。 spec: containers: - command: - kube-apiserver - --feature-gates=UserNamespacesStatelessPodsSupport=true /etc/kubernetes/manifests/kube-apiserver.yaml KUBELET_KUBEADM_ARGS=“--container-runtime=remote --container-runtime-endpoint=unix:///var/run/crio/crio.sock --pod-infra-container-image=registry.k8s.io/pause:3.8 --feature-gates=UserNamespacesStatelessPodsSupport=true" /var/lib/kubelet/kubeadm-flags.env
  12. 2022/11/15 23 Podの.spec.hostUsersをfalseにすることでPod内でUser Namespaceを使⽤することが可能となる。 ※1 $ cat << EOT |

    kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: userns spec: hostUsers: false containers: - name: userns image: debian command: ["sleep", "infinity"] EOT pod/userns created 5. 有効化⽅法
  13. 2022/11/15 24 ※1 $ sudo cat /var/lib/kubelet/pods/$(kubectl get po userns

    -o jsonpath='{.metadata.uid}')/userns {"uidMappings":[{"hostId":131072,"containerId":0,"length":65536}], "gidMappings":[{"hostId":131072,"containerId":0,"length":65536}]} 以下の場合、UIDの範囲が65536なので、WorkerNode上のUID 131072〜196607が、PodのUID 0〜65535に マッピングされていることが分かります。 Podで確認しても同様の結果が得られる。 $ kubectl exec userns -it – bash root@userns:/# id uid=0(root) gid=0(root) groups=0(root) root@userns:/# cat /proc/self/uid_map 0 131072 65536 root@userns:/# cat /proc/self/gid_map 0 131072 65536 5. 有効化⽅法
  14. 2022/11/15 26 Demo 6. Demo 以下をDemoします。 • PodでのUser Namespaceの利⽤ •

    DeploymentでのUser Namespaceの利⽤およびPod再起動時の挙動 • 乗っ取りの回避
  15. 2022/11/15 28 1. Volume 今現状、以下のVolumeしかサポートされていない。 • Configmap • Secret •

    downwardAPI • emptyDir • Projected 他のVlumeはBeta, GAとなるにつれて他のVolumeも使⽤可能となる予定です。 2. ノードのUID枯渇対応 現状では、User Namespaceを使⽤可能なPod数をノード毎に1024に制限し、UID枯渇に対応するようにしている。 ただし、この対応が正しいのかはまだ結論が出ていない。 以下の対応が検討されている。 • PodのUser NamespaceがノードのUID全体を使⽤しないように制限を設ける • kubeletではなく、CRIにUIDマッピング⽅法を委ね、UID範囲を選択可能とする 7. 今後の展望