ユーザー名前空間

FEATURE STATE: Kubernetes v1.30 [beta]

このページでは、KubernetesのPodにおけるユーザー名前空間について説明します。 ユーザー名前空間はホストのユーザーとコンテナ内プロセスが利用するユーザーを隔離するものです。

ユーザー名前空間を使うと、コンテナ内でrootとして稼働するプロセスを、ホスト側の異なる(root以外の)ユーザーとして実行することができます。 言い換えれば、ユーザー名前空間内部のリソースの操作に特権をもつプロセスは、名前空間の外側では非特権のプロセスとなっています。

ホストや他のPodに危害を及ぼす、侵害されたコンテナによる被害を軽減するために、この機能を用いることができます。 HIGH ないしは CRITICAL にレートされたいくつかの脆弱性は、ユーザー名前空間が有効な場合には悪用できないものでした。 ユーザー名前空間は、将来の脆弱性を緩和することも期待できます。

始める前に

この機能はLinux固有であり、Linuxのファイルシステムでidmapマウントがサポートされている必要があります。

  • ノード上で/var/lib/kubelet/pods/(ないしはそのカスタムディレクトリとして設定した場所)でidmapマウントがサポートされている必要があります。
  • Podのボリュームとして使われる全てのファイルシステムがidmapマウントをサポートしている必要があります。

これは、最低でもLinux 6.3以降を利用していて、かつidmapマウントをサポートするtmpfsが必要であることを意味します。 一般的に、いくつかのKubernetesの機能はtmpfsを利用しています。 (デフォルトでは、PodがサービスアカウントトークンやSecretをマウントする時にtmpfsを使っていたりします)。

Linux 6.3でidmapマウントをサポートするポピュラーなファイルシステムはbtrfs、ext4、xfs、fat、tmpfs、overlayfsです。

さらに、コンテナランタイムとその基盤であるOCIランタイムもユーザー名前空間をサポートしている必要があります。 次のOCIランタイムではサポートが提供されています。

  • crun バージョン1.9以上 (推奨バージョンは1.13以上).
  • runc バージョン1.2以上

Kubernetesでユーザー名前空間を利用する際、Podでこの機能を使うためにはCRIコンテナランタイム も必要です。

  • containerd: バージョン2.0以上ではコンテナのユーザー名前空間をサポート。
  • CRI-O: バージョン1.25以上でコンテナのユーザー名前空間をサポート。

ユーザー名前空間のサポート状況については、GitHubのissueで確認できます。

導入

Linuxの機能であるユーザー名前空間を用いると、コンテナのユーザーをホスト側の異なるユーザーにマップすることができます。 さらに言えば、ユーザー名前空間においてPodに付与されたケーパビリティ(capability)は、ユーザー名前空間内においてのみ有効で、外側では無効です。

Podはpod.spec.hostUsersフィールドをfalseに設定することで、ユーザー名前空間を使えるようになります。

kubeletはPodに対応する(ホストの)UID/GIDを選択した上で、同一ノードの2つ以上のPodが同じ対応関係にならないよう保証するようにしつつ、ユーザーをマップします。

pod.specにおけるrunAsUserrunAsGroupfsGroupなどのフィールドはコンテナ内のユーザーを指すものです。

この機能が有効化された場合、UID/GIDとして正しい値の範囲は0-65535です。 これはファイルとプロセスに対して適用されます。 (runAsUserrunAsGroupなど)。

この範囲を超えるUID/GIDを利用するファイルはオーバーフローしたID(一般的には65534)に所属するように見えるでしょう。 (/proc/sys/kernel/overflowuid/proc/sys/kernel/overflowgidで設定されます)。 ただし、これらのファイルは65534のユーザー/グループで稼働するプロセスであっても、編集することはできません。

rootとして動かす必要があるアプリケーションであっても、ホストにおける他のユーザー名前空間や他のリソースに対してアクセスしないものの多くは、ユーザー名前空間を有効化しても動かせますし、アプリケーションを修正しなくても問題なく動作するでしょう。

Podにおけるユーザー名前空間を理解する

いくつかのコンテナランタイム(Docker Engineやcontainer、CRI-Oなど)は、デフォルトでユーザー名前空間を利用するように設定されています。

これらのランタイムとその他の既存技術を組み合わせて使うことも可能です。 (例えば、Kata ContainerはLinux名前空間の代わりにVMを利用します)。 このページの内容はLinux名前空間を隔離に使うコンテナランタイムに適用できるものです。

Podを作成する時、デフォルトでは、Podの隔離にいくつかの新しい名前空間が利用されます。 (コンテナネットワークの隔離のためのネットワーク名前空間、プロセスの見える範囲を隔離するためのPID名前空間など)。 ユーザー名前空間を利用する場合には、コンテナ内のユーザーをノードのユーザーと隔離します。

これは、コンテナがrootとしてプロセスを動かせる一方で、ホスト上では非rootユーザーとしてマップされていることを意味します。 コンテナ内部のプロセスはrootとして動作しているものと思っていることでしょう。 (したがって、aptyumなどは問題なく動作します)。 しかし、実際には、このプロセスにホスト上での特権はありません。 これを検証するには、例えばホスト上でps auxを実行することで、コンテナのプロセスがどのユーザーを使用しているかを確認するとよいでしょう。 psが示すユーザーは、コンテナ内でidを実行した場合に示されるユーザーとは異なっているはずです。

この分離によって、ホスト上で「起こせること」を制限できます。例えばコンテナ内プロセスのホストへのエスケープを処理する場合にこの制限が有効に働きます。 コンテナはホスト上で非特権のユーザーとして動作しているため、ホスト上でできることが制限されているのです。

さらに言えば、それぞれのPodのユーザーは、ホスト上においては異なるユーザーにマップされており、UID/GIDは重複しません。 他のPodに対してできることさえも制限されているのです。

Podに付与されたケーパビリティについても、Podのユーザー名前空間に制限されており、名前空間の外部においてはほとんどが効力を持たず、いくつかのケーパビリティは外部では完全に無効です。 2つの例を挙げます:

  • CAP_SYS_MODULE がユーザー名前空間を使うPodに付与されている場合、Podはカーネルモジュールをロードできません。
  • CAP_SYS_ADMIN はPodのユーザー名前空間の内部のみに制限され、名前空間の外部では無効です。

コンテナブレークアウトのケースについて考えてみます。 この場合、ユーザー名前空間を使用せずにコンテナをrootで稼働させていると、ノードのroot権限が取得されます。 さらに、ケーパビリティがコンテナに付与されていた場合には、そのケーパビリティはホスト上でも有効となっています。 ユーザー名前空間を利用していれば、これらはいずれも成立しません。

ユーザー名前空間を使う場合に何が変わるのかについて詳しく知りたい場合には、man 7 user_namespacesを参照してください。

ユーザー名前空間をサポートするノードを設定する

ほとんどのLinuxディストリビューションで標準的なUIDである0-65535の範囲については、kubeletはホストのファイルやプロセスがこの範囲のUIDを利用しているものとみなし、デフォルトではこれよりも上のUID/GIDの値をPodに紐付けます。 言い換えれば、0-65535の範囲のIDをPodで使うことはできません。 このアプローチにより、PodとホストのUID/GIDが重複することを防ぎます。

Podによる潜在的な任意のファイル読み出しの脆弱性であるCVE-2021-25741のような脆弱性の影響を緩和する上で、UID/GIDの重複を防ぐことは重要です。 PodとホストのUID/GIDが重複しなければ、Podができることは限定されます。 (PodのUID/GIDはホスト上のファイル所有者やグループと一致することがないのです)。

kubeletはPodに割り当てるユーザーIDとグループIDの範囲を変更することが可能です。カスタムの範囲を設定するには、ノードが次の条件を満たす必要があります。

  • ユーザーkubeletがシステム上に存在していること(他のユーザー名を使うことはできません)
  • getsubidsバイナリ(shadow-utilsの一部)がインストールされており、kubeletバイナリが参照するPATHに入っていること
  • kubeletユーザーのsubordinate UID/GID (man 5 subuid およびman 5 subgidを参照)

これはsubordinate UID/GIDの範囲に関する設定のみを示しており、kubeletを実行するユーザーは変更しません。

ユーザーkubeletに割り当てるsubordinate IDの範囲に関しては、いくつかの制約に従う必要があります。

  • subordinate UIDの起点(つまりPodのUID範囲の開始位置)が65536の倍数に設定されていて、かつ65536以上であること( 必須 )。 言い換えると、0-65535の範囲をPodのUIDとして使うことはできません。 偶発的にインセキュアな設定がなされることを防ぐために、kubeletはこの制約を強制します。

  • subordinate UID/GIDの個数が65536の倍数であること(必須)。

  • subordinate UID/GIDの個数は最低でも65536 x <最大Pod数>であること(必須)。 <最大Pod数> はノードで稼働できるPodの数の最大値を表します。

  • UIDとGIDとして同じ個数を割り当てること(必須)。 他のユーザーに対して、GIDの範囲と合致しないUID範囲を指定することは問題ありません。

  • 割り当てられたUID/GID範囲は他の割当と重複しないこと(推奨)。

  • subordinate UID/GIDの設定は単一行でなされること(必須)。 同一のユーザーに対して複数のUID/GID範囲を定義することはできません。

例えば、ユーザーkubeletのエントリについて、/etc/subuid/etc/subgidに次のように定義することができます。

# フォーマットは次の通り
#   name:firstID:count
# この例における意味
# - firstIDは65536 (とりうる最小の値)
# - countは110 (デフォルトの制限値) * 65536
kubelet:65536:7208960

Podセキュリティのアドミッション検証への統合

FEATURE STATE: Kubernetes v1.29 [alpha]

ユーザー名前空間を有効化したLinuxのPodでは、KubernetesはPod Security Standardsで制御されるアプリケーションの制限を緩和します。 この挙動はエンドユーザーの早期オプトインを可能にするためのUserNamespacesPodSecurityStandardsフィーチャーゲートで制御することが可能です。 このフィーチャーゲートを使う場合、クラスタ管理者はユーザー名前空間が全てのノードで有効化されていることを確実にする必要があります。

フィーチャーゲートを有効化した上でユーザー名前空間を使うPodを作成する場合、_Baseline_ないしは_Restricted_Podセキュリティ基準のセキュリティコンテキストが強制されていても、以下のフィールドによる制約がなされません。

  • spec.securityContext.runAsNonRoot
  • spec.containers[*].securityContext.runAsNonRoot
  • spec.initContainers[*].securityContext.runAsNonRoot
  • spec.ephemeralContainers[*].securityContext.runAsNonRoot
  • spec.securityContext.runAsUser
  • spec.containers[*].securityContext.runAsUser
  • spec.initContainers[*].securityContext.runAsUser
  • spec.ephemeralContainers[*].securityContext.runAsUser

制限

Podでユーザー名前空間を利用する際には、他のホスト名前空間を利用することはできません。 特にhostUsers: falseを設定している場合、次の値を設定することはできません。

  • hostNetwork: true
  • hostIPC: true
  • hostPID: true

次の項目

このページの項目は、Kubernetesが必要とする機能を提供するサードパーティー製品またはプロジェクトです。Kubernetesプロジェクトの作者は、それらのサードパーティー製品またはプロジェクトに責任を負いません。詳しくは、CNCFウェブサイトのガイドラインをご覧ください。第三者のリンクを追加するような変更を提案する前に、コンテンツガイドを読むべきです。