メインコンテンツへスキップ
  1. ノート/

TIL: rootless Podmanを支えるUIDマッピングの仕組み

·4 分
Ixonae
著者
Ixonae

注意: この記事はLLMによって英語から翻訳されたものです。正確性については保証いたしかねますので、あらかじめご了承ください。 英語の原文はこちら

かなり長い間、rootless Podmanを使ってきました。最初に驚いたことの一つは、自分のファイルシステムにマウントされた一部のボリュームが、Linuxシステム上には存在しないような妙なUIDに所有されていたことです。

これはLinuxのuser namespace機能によるものだとわかりました。より具体的には、subuidとsubgidです。

では、これは一体何でしょうか。基本的に、rootlessコンテナを実行する際、コンテナ内で動作するものには自分がrootであると思わせたい一方で、コンテナエスケープ時の追加の安全策として、UID 0、つまりシステムのrootのUIDは使いたくありません。

また、コンテナ内で複数のユーザーを表現できるようにもしたいはずです。コンテナには独自のroot、サービスアカウント、アプリケーションユーザー、グループが存在し得ます。従属UID・GID範囲を使うことで、これらの内部的なIDを、コンテナを実行しているユーザーにすべて集約するのではなく、ホスト上の個別の非特権IDにマッピングできます。

これを実現するため、システムは/etc/subuid/etc/subgidにマッピングを作成します。これらのファイルは次のような内容になります。

$ cat /etc/subuid
ixonae:100000:65536
user2:165536:65536
$ cat /etc/subgid
ixonae:100000:65536
user2:165536:65536

最初の数字はUIDマッピングの開始位置、2つ目は作成可能なサブIDの数です。連続した単純な従属範囲の場合、マッピングはhost_uid = subuid_start + container_uidとなります。例えば、rootはホストシステム上で100000になり、Podman内でID 1000のユーザーが作成された場合、ホストシステム上では101000にマッピングされます。

これにより、ホストシステム上ではPodmanから実行されるものは依然として非特権のままですが、コンテナ内ではあたかも特権があるかのように動作できます。

では、65536はどこから来るのでしょうか。多くのシステムでは、このデフォルト値は/etc/login.defs内で定義されています。具体的にはSUB_UID_MINSUB_UID_MAXSUB_UID_COUNTです。このファイルには、useradd使用時に使われるUID_MINUID_MAXも定義されています。デフォルトの範囲は通常、通常のユーザーIDとの重複を避けるように設定されています。

rootless Podmanのコマンドやコンテナを実行する際、newuidmapnewgidmapのユーティリティが呼び出され、user namespaceの初期化と/proc/<pid>/uid_mapファイルへの書き込みを行います。このファイルはその後、カーネルがパーミッション管理やユーザーIDに関わる処理を行う際に使用されます。

これらのパーミッションは次のように確認できます。

# 以下のコマンドで表示される3つの列は以下の通りです。
#  - user namespace内でのID
#  - namespace外でのID
#  - マッピングの長さ
[user2@myhost ~]$ podman unshare cat /proc/self/uid_map
         0       1001          1
         1     165536      65536
$ id user2
uid=1001(user2) gid=1001(user2) groups=1001(user2)
$ cat /etc/subuid | grep user2
user2:165536:65536

この場合、コンテナ内のrootは、ホストOS上でPodmanを実行しているユーザーにマッピングされていることに注意してください。それ以外のすべてのユーザーはsubuidに紐づけられます。これはPodmanのデフォルトの動作ですが、変更することも可能です。

例えば、Podman Composeを使用する場合は以下のようになります。

services:
  app:
    userns_mode: auto # デフォルトは "" (未設定)

なお、この動作を変更するには、パーミッションの再マッピングが必要になります。Podman Composeファイル内でマウントするパスに:Uを使用すると、それを自動的に処理できます。

関連する便利なコマンドをいくつか紹介します。

  • getsubids user2 はそのユーザーのsubuidファイルの内容を返します
  • podman system migrate は、ユーザーのsubuidファイルが変更された場合に、関連するコンテナのマッピングを更新します
  • podman unshare [コマンド] を使うと、Podmanと同じuser namespace内でコマンドを実行でき、ホストユーザーが所有していないファイルへのアクセスが可能になります

追加リソース