TenForward

技術ブログ。はてなダイアリーから移転しました

util-linux での ID mapped マウント

連載の第50回第51回で、ID mappedマウントについて書きました。

これらの記事を書いた時点(2022年末)では、mount コマンドでは ID mapped マウントができなかったので、記事では、ID mapped マウントの主要開発者であるChristian Brauner氏の mount-idmapped を使って説明しました。

その後、util-linux 2.39-rc1 の libmount で ID mapped マウントがサポートされ、2.40 で mount コマンドで --map-users と --map-group オプションが追加されました

ID mappedマウントの必要性

User Namespaceは、図のようにホストOS環境のUID/GIDと、コンテナ環境のUID/GIDマッピングさせる機能です。

User Namespace

図だと、ホスト上のUID:100000がコンテナ内の0に、UID:100001がコンテナ内の1に、UID:101000がコンテナ内の1000にマッピングされます。つまり、コンテナ内でUID:0のrootユーザーは、実はコンテナの外から見るとUID:100000の一般ユーザーの権限で動いており、仮にコンテナからホストに対して悪意ある操作を行おうと思っても、UID:100000の一般ユーザー権限でできることしかできません。

さて、コンテナを起動する際は、コンテナホスト上にコンテナイメージを展開し、それをマウントしてコンテナのルートファイルシステムとします。コンテナがUID:100000の一般ユーザー権限で動いているとすると、コンテナホスト上に展開されるコンテナイメージも、UID:100000から始まるマッピング先のUIDやGIDから操作できる権限がなければいけません。

しかし、一般的には、コンテナイメージはコンテナホスト上のrootから読める前提、つまりUID/GIDが0から始まるIDで作成されているでしょう。元から一般ユーザー権限で起動する前提でイメージを作れないことはないですが、そもそも各ユーザーでどのようなマッピングを行うのかは、イメージ作成時にはわかりません。

例えば、次のようにLXCコンテナをroot権限で作成したとします。

$ sudo lxc-create -t download c1 -- -d alpine -r 3.19 -a amd64
$ sudo ls -l /var/lib/lxc/c1/rootfs
合計 68
drwxr-xr-x  2 root root 4096  614 22:00 bin
drwxr-xr-x  3 root root 4096  616 22:54 dev
drwxr-xr-x 24 root root 4096  616 22:54 etc
drwxr-xr-x  2 root root 4096  127 02:53 home
  :(略)
$ sudo chmod 775 /var/lib/lxc/c1 (これはこのあとの操作のために権限を緩めています)

このように、ほとんどのディレクトリーはホストのroot所有になっています。

ここで、unshareコマンドを使い、User Namespaceを使ってコンテナを作ります。

$ unshare --user --map-root-user ls -l /var/lib/lxc/c1/rootfs/
合計 69,632
drwxr-xr-x  2 nobody nogroup 4,096  614日  22:00 bin
drwxr-xr-x  3 nobody nogroup 4,096  616日  22:54 dev
drwxr-xr-x 24 nobody nogroup 4,096  616日  22:54 etc
drwxr-xr-x  2 nobody nogroup 4,096  127日  02:53 home
  :(略)

ここでは、現在のユーザーをコンテナ(User Namespace)内のrootにマッピングして、ls -lコマンドを実行しています。しかし、ファイルの所有権は何も触っていないので、所有者がnobody:nogroup所有になってしまっています。これは、コンテナホスト上のUID/GID:0が、新たに作ったUser Namespace内ではマッピングされていないためです。

この変換をID mappedマウント以前は、chownなどで実行していたのですが、これは非常に効率が悪いために、コンテナからマウントする際にファイルの所有者についても、マッピングを使ってずらそうという考えで実装されたのがID mappedマウントです。

図のように、User Namespaceで使うマッピングをそのまま使い、ファイルの所有者についても変換を行ってしまおうという機能です。

ID mappedマウントの動き

mountコマンドからID mappedマウントを利用する

さて、2.40からmountコマンドに追加されたID mappedマウントを行うためのオプションを使って、ID mappedマウントを試していきましょう。

追加されたオプションは次の2つです。2つとも使い方は同じです。いずれのオプションも複数回指定できます。このオプションとバインドマウントを組み合わせます。

オプション 説明
--map-users <id-from>:<id-to>:<id-range> UIDのマッピング指定
--map-groups <id-from>:<id-to>:<id-range> GIDマッピング指定

このオプションで指定するコロンで接続された値 <id-from>:<id-to>:<id-range> のそれぞれで指定する値は次の通りです。

説明
id-from マッピング元、つまりmountコマンドを実行する元のUser NamespaceでのID
id-to マッピング先、つまりマウント先のUser NamespaceでのID

例えば、次のようにマッピングとマウント元、マウント先のディレクトリーを指定します。

$ sudo mount --bind --map-users 1001:1002:1 --map-group 1001:1002:1 /path/to/src /path/to/dest

この例では、mountを実行する元のNamespaceのUID/GID:1001を、マウント先ではUID/GID:1002にマッピングして、バインドマウントしています。

それでは、実際にID mappedマウントの動きを見ていきます。実行はUID/GIDがいずれも1002のu1002ユーザで実行しています。

u1002@host:~$ id
uid=1002(u1002) gid=1002(u1002) groups=1002(u1002),10(wheel)

そして、UID/GIDが1001のu1001ユーザのホームディレクトリーをu1002ユーザでID mappedマウントしてみます。

u1002@host:~$ id u1001
uid=1001(u1001) gid=1001(u1001) groups=1001(u1001),10(wheel)
u1002@host:~$ sudo ls -l /home/u1001
合計 8
drwxr-xr-x 2 u1001 u1001 4096  5月 13 18:10 Mail
drwxr-xr-x 2 u1001 u1001 4096  5月 13 18:10 Sample
-rw-r--r-- 1 u1001 u1001    0  6月 16 21:41 u1001-file

上のように、u1001ユーザのホームディレクトリには、u1001-fileというファイルをu1001ユーザ権限で作成してあります。

それでは、このu1001ユーザのホームディレクトリである/home/u1001を、UID/GIDu1002ユーザのIDである1002にマッピングして、/mntにマウントしてみます。

u1002@host:~$ sudo mount --bind --map-users 1001:1002:1 --map-group 1001:1002:1 /home/u1001 /mnt

これで/home/u1001がIDマッピングされて、/mntにマウントされているはずです。/mntディレクトリの所有権を確認してみましょう。

u1002@host:~$ ls -l /mnt
合計 0
-rw-r--r-- 1 u1002 u1002 0  6月 16日  21:41 u1001-file

元の/home/u1001では、u1001ユーザ所有だったファイルがマッピングされ、u1002ユーザ所有に見えています。

次に、/mnt配下でファイルをu1002ユーザ(UID/GID=1002)権限で作成してみましょう。

u1002@host:~$ touch /mnt/u1002-file
u1002@host:~$ ls -l /mnt
合計 0
-rw-r--r-- 1 u1002 u1002 0  6月 16日  21:41 u1001-file
-rw-r--r-- 1 u1002 u1002 0  7月  9日  01:34 u1002-file

ファイルを問題なく作成でき、新たに作成されたファイルの所有権はu1002ユーザになっています。

ここで、新たにID mappedマウントされたディレクトリである/mntで作成したu1002-fileを、マウント元の/home/u1001で確認しておきましょう。

u1001@host:~$ ls -l /home/u1001
合計 0
-rw-r--r-- 1 u1001 u1001 0  6月 16 21:41 u1001-file
-rw-r--r-- 1 u1001 u1001 0  7月  9 01:34 u1002-file

/mnt上でu1002ユーザの権限で作成したu1002-fileは、元の/home/u1001で確認すると、きちんとUID/GIDが1001のu1001ユーザ所有になっています。


さて、ここまでで util-linux に含まれている mount コマンドで ID mappedマウントの動きを見てきました。記事では、このあと、ACLやケーパビリティの動きを見ていますが、マウントされてしまったあとの動きは同じですので(カーネルの同じ機能を使ってるので当たり前ですね)、そのあたりは記事を参照いただければと思います。