連載の第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をマッピングさせる機能です。
図だと、ホスト上の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 6月 14 22:00 bin drwxr-xr-x 3 root root 4096 6月 16 22:54 dev drwxr-xr-x 24 root root 4096 6月 16 22:54 etc drwxr-xr-x 2 root root 4096 1月 27 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 6月 14日 22:00 bin drwxr-xr-x 3 nobody nogroup 4,096 6月 16日 22:54 dev drwxr-xr-x 24 nobody nogroup 4,096 6月 16日 22:54 etc drwxr-xr-x 2 nobody nogroup 4,096 1月 27日 02:53 home :(略)
ここでは、現在のユーザーをコンテナ(User Namespace)内のrootにマッピングして、ls -l
コマンドを実行しています。しかし、ファイルの所有権は何も触っていないので、所有者がnobody:nogroup
所有になってしまっています。これは、コンテナホスト上のUID/GID:0が、新たに作ったUser Namespace内ではマッピングされていないためです。
この変換をID mappedマウント以前は、chownなどで実行していたのですが、これは非常に効率が悪いために、コンテナからマウントする際にファイルの所有者についても、マッピングを使ってずらそうという考えで実装されたのがID mappedマウントです。
図のように、User Namespaceで使うマッピングをそのまま使い、ファイルの所有者についても変換を行ってしまおうという機能です。
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/GIDをu1002
ユーザの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やケーパビリティの動きを見ていますが、マウントされてしまったあとの動きは同じですので(カーネルの同じ機能を使ってるので当たり前ですね)、そのあたりは記事を参照いただければと思います。