ちょうど 2 年ほど前、Open Source Summit で話を聞いて ShiftFS について試したことがありました。
その後はとりあえずカーネルにパッチを当てたりはしてましたが、あまり使わないままでした。少し前に gihyo.jp の Ubuntu の記事を読んでいると、
discoのカーネルフリーズが4月4日に迫る中,興味深いパッチが投稿されています。パッチの中身は「ShiftFS」と呼ばれる,コンテナ環境でのセキュリティ機能を提供するためのラッパーファイルシステムです
https://gihyo.jp/admin/clip/01/ubuntu-topics/201903/29
なんて書かれています。その後の記事で
ShiftFSは, 前回取り上げた後,4月4日に無事にマージに辿り着いています。
https://gihyo.jp/admin/clip/01/ubuntu-topics/201904/19
と書かれていたので、また変わった所がないか調べて試してみたいなと思っていました。
そこでようやく重い腰を上げて、Ubuntu 19.04 環境を用意して試してみました。
ShiftFS について
詳しくは先に紹介した 2 年前の記事を参照してください。
簡単に言うと、非特権コンテナを起動する場合、どの uid/gid で起動するかは一般的に決まっているわけではないですが、世間で流通しているコンテナイメージ内のファイルの所有権は作成時点で一意に決まりますし、自作のコンテナイメージでも同様でしょう。
そこで非特権コンテナを起動するときには、それを起動するユーザの uid/gid に合わせて chown してあげる必要があります。
この問題を解消するために考えられたのが ShiftFS です。
ShiftFS は OverlayFS のように重ね合わせのファイルシステムとして実装されているようです。元のファイルシステムを「下層」(lower filesystem)とし、その上に「上層」(upper filesystem)のファイルシステムを重ねて所有権を調整しているのでしょう(たぶん)。
この ShiftFS は Ubuntu 19.04 の 5.0 カーネルにパッチを適用して使えるようにしており、upstream のカーネルには導入されていない機能です(されそうなのかしら?)。
Ubuntu は 12.04 だったか 14.04 の頃も、まだ upstream な Linux カーネルに導入されていない OverlayFS を先取りして入れていました(カーネルに入ったときには仕様が変わっていて LXC にパッチ送ったりしたのはいい思い出)。また 16.04 なんかでも、かなり後のカーネルで導入される機能がバックポートされていたりして、かなり意欲的に自分たちの求める機能を入れてきます。
ShiftFS によるマウント
マウント方法は2 年前の記事と大きく変わっていません。マウントオプションが増えた程度でしょうか。
マウントは 2 段階で行います。
ShiftFS がないとき
これも2 年前の記事と同じなのですが、簡単に紹介しておきましょう。
ここではファイルシステムとして LXC コンテナのイメージを展開して使用しています(alpineイメージ)。検証のために一部ディレクトリの所有権などをいじっています。root
権限で作成されたコンテナです。
コンテナファイルシステムの root(/)は /var/lib/lxc/ct01/rootfs
です。
$ sudo ls -l /var/lib/lxc/ct01/rootfs total 68 drwxr-xr-x 2 root root 4096 Jul 1 13:00 bin drwxr-xr-x 3 root root 4096 Jul 2 07:17 dev drwxr-xr-x 19 root root 4096 Jul 2 07:17 etc drwxr-xr-x 2 root root 4096 Jun 19 17:14 home drwxr-xr-x 6 root root 4096 Jul 1 13:00 lib drwxr-xr-x 5 root root 4096 Jun 19 17:14 media : (snip)
上の例のように rootfs
以下は root
所有です。
現在のユーザーをコンテナ内の root
にマッピングして User Namespace を作成します。
$ id -u && id -g 1000 1000 $ unshare --pid --user --map-root-user --mount --mount-proc --fork -- /bin/bash # id uid=0(root) gid=0(root) groups=0(root),65534(nogroup) # ls -l /var/lib/lxc/ct01/rootfs/ total 68 drwxr-xr-x 2 nobody nogroup 4096 Jul 1 13:00 bin drwxr-xr-x 3 nobody nogroup 4096 Jul 2 07:17 dev drwxr-xr-x 19 nobody nogroup 4096 Jul 2 07:17 etc drwxr-xr-x 2 nobody nogroup 4096 Jun 19 17:14 home drwxr-xr-x 6 nobody nogroup 4096 Jul 1 13:00 lib drwxr-xr-x 5 nobody nogroup 4096 Jun 19 17:14 media : (snip)
上のように uid/gid=1000/1000 のユーザーをコンテナ内の uid/gid=0/0 にマッピングしただけなので、マッピングされていない uid/gid については nobody/nogroup の 65534/65534 となってしまっています。
ShiftFS があるとき
まずはコンテナの root(/)である /var/lib/lxc/ct01/rootfs
を ShiftFS の下層(lower filesystem)としてマークします。この場合、マウントオプションに mark
を指定します。
$ sudo mount -t shiftfs -o mark /var/lib/lxc/ct01/rootfs /var/lib/lxc/ct01/rootfs [sudo] password for ubuntu: $ grep shiftfs /proc/self/mountstats device /var/lib/lxc/ct01/rootfs mounted on /var/lib/lxc/ct01/rootfs with fstype shiftfs
ShiftFS としてマウントされています(このイメージは root
所有なので、ここでは root
ユーザーで mark しています)。
ここで、さきほどと同じように User Namespace を作成します。
$ unshare --pid --user --map-root-user --mount --mount-proc --fork -- /bin/bash # id uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
そしてさきほど mark した ShiftFS マウントを使って、権限のあるディレクトリに ShiftFS マウントしてみます。
# mount -t shiftfs /var/lib/lxc/ct01/rootfs/ /home/ubuntu/mnt # grep shiftfs /proc/self/mountstats device /var/lib/lxc/ct01/rootfs mounted on /var/lib/lxc/ct01/rootfs with fstype shiftfs device /var/lib/lxc/ct01/rootfs mounted on /home/ubuntu/mnt with fstype shiftfs
マウントされています。マウント先の /home/ubuntu/mnt
を確認してみると、
# ls -l /home/ubuntu/mnt total 68 drwxr-xr-x 2 root root 4096 Jul 1 13:00 bin drwxr-xr-x 3 root root 4096 Jul 2 07:17 dev drwxr-xr-x 19 root root 4096 Jul 2 07:17 etc drwxr-xr-x 2 root root 4096 Jun 19 17:14 home drwxr-xr-x 6 root root 4096 Jul 1 13:00 lib drwxr-xr-x 5 root root 4096 Jun 19 17:14 media : (snip)
ちゃんと root
所有になっており、ShiftFS が効いていることがわかります。
ちなみにこの「下層」(mark)のマウントはホストのNamespaceで行っているので、ホストから見えますが、ホストでは「上層」は見えません。
$ grep shiftfs /proc/self/mountstats (ホスト上で実行)
device /var/lib/lxc/ct01/rootfs mounted on /var/lib/lxc/ct01/rootfs with fstype shiftfs
$
コンテナ起動後は、余計な情報を見せないためにホスト上ではこの mark 用 ShiftFS は umount
できます。
動きは 2 年前の変わっていないようです。
passthrough オプション
Ubuntuのパッチでは ShiftFS のオプションとして passthrough
オプションが追加されているようです。2 年前の記事の時点ではおそらくなかったオプションだと思います。
さきほどの例では何も指定していませんので、passthrough=0
が指定されているのと同じ意味になるようです。
- 1 を指定すると、マウントされたファイルシステムタイプとして「下層」側のファイルシステム名が返るようです
- 2 を指定すると、
ioctl()
でホワイトリストとして指定されたリクエストは「下層」側ファイルシステムに渡されるようです - 3 を指定すると、
1+2
を指定したのと同じになるようです
さきほどの例でマウントした ShiftFS で 1 が指定されていない効果を確認してみます。
# stat -f /home/ubuntu/mnt/root File: "/home/ubuntu/mnt/root" ID: ad6dc6d23bfb15c1 Namelen: 255 Type: UNKNOWN (0x6a656a62) Block size: 4096 Fundamental block size: 4096 Blocks: Total: 4111327 Free: 3014986 Available: 2801214 Inodes: Total: 1048576 Free: 972147
ファイルシステムタイプとして Type: UNKNOWN
と返っています。
(2 の指定は気軽に試すには私にはハードルが高そうなので、)とりあえず 3
を指定してみます。
$ sudo mount -t shiftfs -o mark,passthrough=3 /var/lib/lxc/ct01/rootfs /var/lib/lxc/ct01/rootfs $ grep shiftfs /proc/self/mountinfo 540 28 0:53 / /var/lib/lxc/ct01/rootfs rw,relatime shared:294 - shiftfs /var/lib/lxc/ct01/rootfs rw,mark,passthrough=3
passthrough=3
付きでマウントされています。
では User namespace を作成しましょう。
$ unshare --pid --user --map-root-user --mount --mount-proc --fork -- /bin/bash # mount -t shiftfs -o passthrough=3 /var/lib/lxc/ct01/rootfs /home/ubuntu/mnt # stat -f /home/ubuntu/mnt/root File: "/home/ubuntu/mnt/root" ID: ad6dc6d23bfb15c1 Namelen: 255 Type: ext2/ext3 Block size: 4096 Fundamental block size: 4096 Blocks: Total: 4111327 Free: 3019139 Available: 2805367 Inodes: Total: 1048576 Free: 972149
stat
コマンドで確認すると Type: ext2/ext3
と返ってきています。
2 の ioctl()
周りですが、ShiftFS のパッチ(の一部) を見る限りでは、
static long shiftfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { case FS_IOC_GETVERSION: /* fall through */ case FS_IOC_GETFLAGS: /* fall through */ case FS_IOC_SETFLAGS: break; default: return -ENOTTY; } return shiftfs_real_ioctl(file, cmd, arg); } static long shiftfs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { case FS_IOC32_GETVERSION: /* fall through */ case FS_IOC32_GETFLAGS: /* fall through */ case FS_IOC32_SETFLAGS: break; default: return -ENOIOCTLCMD; } return shiftfs_real_ioctl(file, cmd, arg); }
のような実装になってるので、ここに挙がってるリクエストは許可されているのでしょう(知らんけど)。
LXD
ちなみに LXD ではすでに ShiftFS が使えるようになっています(3.12以降)。
使い方は次で説明されています。