Kubernetes 1.31: Fine-grained SupplementalGroups control

この記事ではKubernetes 1.31の新機能である、Pod内のコンテナにおける補助グループ制御の改善機能について説明します。

動機: コンテナイメージ内の/etc/groupに定義される暗黙的なグループ情報

この挙動は多くのKubernetesクラスターのユーザー、管理者にとってあまり知られていないかもしれませんが、Kubernetesは、デフォルトでは、Podで定義された情報に加えて、コンテナイメージ内の/etc/groupのグループ情報を マージ します。

例を見てみましょう。このPodはsecurityContextでrunAsUser=1000runAsGroup=3000supplementalGroups=4000を指定しています。

apiVersion: v1
kind: Pod
metadata:
  name: implicit-groups
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    supplementalGroups: [4000]
  containers:
  - name: ctr
    image: registry.k8s.io/e2e-test-images/agnhost:2.45
    command: [ "sh", "-c", "sleep 1h" ]
    securityContext:
      allowPrivilegeEscalation: false

ctrコンテナでidコマンドを実行すると何が出力されるでしょうか?

# Podを作成してみましょう。
$ kubectl apply -f https://k8s.io/blog/2024-08-22-Fine-grained-SupplementalGroups-control/implicit-groups.yaml

# Podのコンテナが実行されていることを確認します。
$ kubectl get pod implicit-groups

# idコマンドを確認します。
$ kubectl exec implicit-groups -- id

出力は次のようになるでしょう。

uid=1000 gid=3000 groups=3000,4000,50000

Podマニフェストには50000は一切定義されていないにもかかわらず、補助グループ(groupsフィールド)に含まれているグループID50000は一体どこから来るのでしょうか? 答えはコンテナイメージの/etc/groupファイルです。

コンテナイメージの/etc/groupの内容が下記のようになっていることが確認できるでしょう。

$ kubectl exec implicit-groups -- cat /etc/group
...
user-defined-in-image:x:1000:
group-defined-in-image:x:50000:user-defined-in-image

なるほど!コンテナのプライマリユーザーであるユーザー(1000)がグループ(50000)に属していることが最後のエントリから確認出来ました。

このように、コンテナイメージ上の/etc/groupで定義される、コンテナのプライマリユーザーのグループ情報は、Podからの情報に加えて 暗黙的にマージ されます。ただし、この挙動は、現在のCRI実装がDockerから引き継いだ設計上の決定であり、コミュニティはこれまでこの挙動について再検討することはほとんどありませんでした。

何が悪いのか?

コンテナイメージの/etc/groupから 暗黙的にマージ されるグループ情報は、特にボリュームアクセスを行う際に、セキュリティ上の懸念を引き起こすことがあります(詳細はkubernetes/kubernetes#112879を参照してください)。なぜなら、Linuxにおいて、ファイルパーミッションはuid/gidで制御されているからです。更に悪いことに、/etc/groupに由来する暗黙的なgidは、マニフェストにグループ情報の手がかりが無いため、ポリシーエンジン等でチェック・検知をすることが出来ません。これはKubernetesセキュリティの観点からも懸念となります。

PodにおけるFine-grained(きめ細かい) SupplementalGroups control: SupplementaryGroupsPolicy

この課題を解決するために、Kubernetes 1.31はPodの.spec.securityContextに、新しくsupplementalGroupsPolicyフィールドを追加します。

このフィールドは、Pod内のコンテナプロセスに付与される補助グループを決定するを方法を制御できるようにします。有効なポリシーは次の2つです。

  • Merge: /etc/groupで定義されている、コンテナのプライマリユーザーが所属するグループ情報をマージします。指定されていない場合、このポリシーがデフォルトです(後方互換性を考慮して既存の挙動と同様)。

  • Strict: fsGroupsupplementalGroupsrunAsGroupフィールドで指定されたグループIDのみ補助グループに指定されます。つまり、/etc/groupで定義された、コンテナのプライマリユーザーのグループ情報はマージされません。

では、どのようにStrictポリシーが動作するか見てみましょう。

apiVersion: v1
kind: Pod
metadata:
  name: strict-supplementalgroups-policy
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    supplementalGroups: [4000]
    supplementalGroupsPolicy: Strict
  containers:
  - name: ctr
    image: registry.k8s.io/e2e-test-images/agnhost:2.45
    command: [ "sh", "-c", "sleep 1h" ]
    securityContext:
      allowPrivilegeEscalation: false
# Podを作成してみましょう。
$ kubectl apply -f https://k8s.io/blog/2024-08-22-Fine-grained-SupplementalGroups-control/strict-supplementalgroups-policy.yaml

# Podのコンテナが実行されていることを確認します。
$ kubectl get pod strict-supplementalgroups-policy

# プロセスのユーザー、グループ情報を確認します。
kubectl exec -it strict-supplementalgroups-policy -- id

出力はこのようになります。

uid=1000 gid=3000 groups=3000,4000

Strictポリシーによってグループ50000groupsから除外されているのが確認できました!

このように、確実にsupplementalGroupsPolicy: Strictを設定する(ポリシーエンジン等によって強制する)ことで、暗黙的な補助グループを回避することが可能になります。

Podステータスにおける付与されたユーザー、グループ情報の確認

この機能は、Podのstatus.containerStatuses[].user.linuxフィールドでコンテナの最初のプロセスに付与されたユーザー、グループ情報を公開しています。暗黙的なグループIDが付与されているかどうかを確認するのに便利でしょう。

...
status:
  containerStatuses:
  - name: ctr
    user:
      linux:
        gid: 3000
        supplementalGroups:
        - 3000
        - 4000
        uid: 1000
...

この機能を利用するには

supplementalGroupsPolicyフィールドを有効化するには、下記のコンポーネントを利用する必要があります。

  • Kubernetes: v1.31以降、かつ、SupplementalGroupsPolicyフィーチャーゲートが有効化されていること。v1.31現在、このフィーチャーゲートはアルファです。
  • CRI実装:
    • containerd: v2.0以降
    • CRI-O: v1.31以降

ノードの.status.features.supplementalGroupsPolicyフィールドでこの機能が利用可能かどうか確認出来ます。

apiVersion: v1
kind: Node
...
status:
  features:
    supplementalGroupsPolicy: true

将来の展望

Kubernetes SIG Nodeは、この機能が将来的なKubernetesのリリースでベータ版に昇格し、最終的には一般提供(GA)されることを望んでおり、期待しています。そうなれば、ユーザーはもはや機能ゲートを手動で有効にする必要がなくなります。

supplementalGroupsPolicyが指定されていない場合は、後方互換性のためにMergeポリシーが適用されます。

より学ぶには?

参加するには?

この機能はSIG Nodeコミュニティによって推進されています。コミュニティに参加して、上記の機能やそれ以外のアイデアやフィードバックを共有してください。皆さんからのご意見をお待ちしています!