LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術

第47回 非特権コンテナの可能性を広げるseccomp notify機能

この記事を読むのに必要な時間:およそ 6.5 分

LXDでのseccomp notify機能の実装

それではseccomp notify機能の動きを実際に見ていきましょう。先に書いた通り,今回はLXDを使ってコンテナを作成し,seccomp notify機能を設定して機能を試していきます。LXDでは,特に指定しなければ一般ユーザー権限でコンテナが起動します。

LXCプロジェクトの開発者はカーネルにも積極的にコンテナ関連機能の実装を進めており,seccomp notify機能の実装も,LXCプロジェクトの開発者を中心に開発されています。

このため,カーネル側での実装とともにLXDでもseccomp notify機能の実装が進めることができ,カーネルで実装されてからかなり短い期間でLXDにもこの機能が実装されています。

デバイスファイルの作成

最初にseccomp notify機能がサポートされたのは,LXDでは5.0カーネルリリースから2ヶ月後のLXD 3.13です。LXD 3.13では,まずはmknodmknodatシステムコールが使えるようになり,デバイスファイルが作成できるようになりました。

LXDが依存しているLXCでは3.2.1から,libseccompは2.5.0からseccomp notify機能が使えます。LXDはLXC,libseccompを必要としていますので,これらの新しいライブラリが必要です。

LXDでmknodmknodatが使えるようになったと言っても任意のデバイスファイルが作成できるわけではありません。許可されているデバイスは先に紹介した/dev/null/dev/zeroなどの一部のデバイスだけです。詳細は公式ドキュメント日本語訳に記載があります。

それでは,デバイスファイルを作成してseccomp notify機能を試してみましょう。今回の実行例はUbuntu 20.04.1にsnapでインストールしたLXD 4.8という環境で実行しています。snapであれば前述のようなライブラリの依存関係を考えることなく,seccomp notify機能が使える形で作成されていますので安心です。なお,LXDに関してはstableリリースの4.0シリーズでもseccomp notify機能が使えます。

$ snap list lxd 
Name  Version  Rev    Tracking       Publisher   Notes
lxd   4.8      18520  latest/stable  canonical✓  -
$ lxc version
Client version: 4.8
Server version: 4.8

まずはコンテナを作成します。そしてこのコンテナが一般ユーザで起動していることを確認します。

$ lxc launch ubuntu:20.04 c1 (Ubuntu 20.04コンテナの作成・起動)
Creating c1
Starting c1
$ lxc info c1 | grep Pid (コンテナのPIDを確認)
Pid: 41463
$ ps aux | grep 41463
1000000    41463  0.0  0.4 104104  8032 ?        Ss   15:05   0:00 /sbin/init
(UID:1000000の一般ユーザで起動している)

それではコンテナ内に入ってデバイスファイルが作成できるか試してみましょう。

$ lxc shell c1 (コンテナ内に入りシェルを実行)
root@c1:~# mknod my-dev c 1 5 (デバイスファイルを作成)
mknod: my-dev: Operation not permitted (許可されていない)

失敗しました。デフォルトではデバイスファイルの作成は許可されていませんのでこれは当然の動作です。

それでは,LXDで設定してデバイスファイルの作成を許可して試してみましょう。許可するには,LXDの設定でコンテナに対してsecurity.syscalls.intercept.mknodtrueに設定します。

$ lxc config set c1 security.syscalls.intercept.mknod=true (seccomp notifyでmknodを許可する)
$ lxc config show c1 | grep intercept
  security.syscalls.intercept.mknod: "true" (trueに設定された)

設定されました。設定を反映させるためにコンテナを再起動し,再度コンテナ内でシェルを実行します。

$ lxc restart c1 (コンテナ再起動)
$ lxc shell c1
root@c1:~# mknod my-dev c 1 5 (実行成功)
root@c1:~# ls -l my-dev
total 1
crw-r--r-- 1 root root 1, 5 Dec  7 06:05 my-dev
(デバイスファイルが作成されている)

無事,メジャー番号1,マイナー番号5のデバイスファイル/dev/zeroが作成されています。さらにもうひとつテストで作ってみましょう/dev/random⁠。

root@c1:~# mknod my-dev2 c 1 8 (メジャー番号1,マイナー番号8で作成)
root@c1:~# ls -l my-dev2
crw-r--r-- 1 root root 1, 8 Dec  7 06:08 my-dev2
(デバイスファイルが作成されている)

問題なく作成できました。

shiftfs

次はファイルシステムのマウントを試してみましょう。その前に,この後のマウント操作で使用する機能であるshiftfsについて少しだけ紹介しておきます。

この機能は正式にカーネルにはマージされておらず,おそらくカーネルにマージされる際は別の名前で,今回紹介するshiftfsとは異なる実装となっているかもしれません※4⁠。Ubuntu 20.04のカーネルではshiftfsのパッチがマージされていて使えるようになっています。

※4)
カーネル開発方面では"idmapped mounts"などと名付けてパッチが投稿されています。

LXDに限らず,ネットワーク越しに取得するコンテナイメージや,自分で作成したコンテナイメージ内のファイルの所有権は,イメージ作成時に設定された所有権が設定されていることが普通でしょう。通常はほとんどのファイルがroot:rootとなっているのではないでしょうか。

しかしユーザ名前空間を使った非特権コンテナの場合,ホストから見たコンテナイメージ内の所有権は,非特権コンテナを起動するユーザ・グループの所有になっていないと,コンテナ内ではnobody:nogroupとなってしまうなど,本来期待する所有権とは異なる設定がされていて使えない状態になります。

そこで,非特権コンテナの場合,コンテナ起動時に再帰的にchownして所有権を期待する設定にする必要があります。

ここで,overlayfsのように重ね合わせのファイルシステムとして,オリジナルのファイルシステムを下層として,コンテナ用のファイルシステムを上層として重ね合わせた上で所有権を調整して,コンテナに提供できるようにしているのがshiftfsです。Ubuntuでは19.04でこのパッチを適用したカーネルが提供されました。

非特権コンテナからファイルシステムをマウントする場合は,この機能を使って所有権を調整する必要があるので,今回LXDでseccomp notifyを使う場合も,snapパッケージで動作するLXDがこの機能を使えるようにする必要があります。デフォルトでは次のように有効化されていません。

$ lxc info | grep shiftfs
    shiftfs: "false"

そこで,まずはLXDでshiftfsが有効になるように設定しましょう。snapパッケージでインストールしたLXDでshiftfsが使えるようにするには次のようにします※5⁠。

$ sudo snap set lxd shiftfs.enable=true (snapのlxdでshiftfsを有効化)
$ sudo systemctl reload snap.lxd.daemon (設定を反映させるために再起動)
$ lxc info | grep shiftfs
    shiftfs: "true"
※5)
詳しくはTrying out shiftfsをご覧ください。

これで準備OKです。shiftfsを有効にする前にコンテナを作成していた場合は,ここで一度コンテナを再作成します※6⁠。

$ lxc delete --force c1
$ lxc launch ubuntu:20.04 c1
Creating c1
Starting c1
※6)
すでに作成済のコンテナをそのまま使ってshiftfsを有効にする方法もありますが,わかりやすさのために再作成します。

ファイルシステムのマウント

準備ができましたので,seccomp notify機能を使ってファイルシステムをマウントしてみましょう。LXDで非特権コンテナ内でmountシステムコールを実行し,マウントができるようになったのは3.19からです。

ここの例では,ホストシステム上にマウントされていないパーティション/dev/sdb1が存在しています。このパーティションはext4でmkfsしています。

$ sudo fdisk -l /dev/sdb | grep sdb1
/dev/sdb1        2048 10485759 10483712   5G 83 Linux

このsdb1はコンテナ内ではデバイスファイルが存在しないので,デフォルトのままではマウントできません。また/dev/sdb1はコンテナ内でデバイスファイルが作成できませんので,LXDで設定してホスト側のデバイスファイルをバインドマウントしておきましょう。

$ lxc config device add c1 sdb1 unix-block path=/dev/sdb1 (コンテナ内に/dev/sdb1を出現させる)
Device sdb1 added to c1
$ lxc restart c1 (設定を反映させるためにコンテナ再起動)

コンテナ内に/dev/sdb1が出現しており,マウントする準備が済みました。

root@c1:~# ls -l /dev/sdb1
brw-rw---- 1 root root 8, 17 Dec  7 14:54 /dev/sdb1

seccomp notify機能を使ってマウントする前に,コンテナ内でマウント操作ができないことを確認しておきます。

$ lxc shell c1
root@c1:~# mount /dev/sdb1 /mnt
mount: /mnt: permission denied.
(コンテナに対してマウントを許可していないので失敗する)

マウント操作は失敗します。

ここで,コンテナに対して次の設定を行い,設定を反映させるためにコンテナを再起動します。

  • マウントができる設定security.syscalls.intercept.mount
  • マウントしたファイルシステムに対してshiftfsを有効にする設定security.syscalls.intercept.mount.shift
  • マウントできるファイルシステムとしてext4を許可する設定security.syscalls.intercept.mount.allowed
$ lxc config set c1 security.syscalls.intercept.mount true
$ lxc config set c1 security.syscalls.intercept.mount.shift true
$ lxc config set c1 security.syscalls.intercept.mount.allowed ext4
$ lxc restart c1

コンテナ内のシェルからマウントしてみましょう。

$ lxc shell c1
root@c1:~# mount /dev/sdb1 /mnt (マウント操作が成功する)
root@c1:~# df -h | grep /mnt
/mnt                                                                 4.9G   20M  4.6G   1% /mnt

マウント操作が成功し,ファイルシステムがマウントできています。

ここでは直接ext4をマウントしていますが,安全のためにコンテナ内にfuse2fsパッケージをインストールし,FUSE(fuse2fs)を使ってマウントすることもできますsecurity.syscalls.intercept.mount.fuse⁠。

まとめ

今回は,非特権コンテナ内でのデバイスファイルの作成とマウント操作を通してseccomp notify機能について紹介しました。

LXDでは,今回紹介したmknodmknodatmountシステムコール以外にもいくつかのシステムコールを許可する設定が追加されています。

seccomp notify機能を使うことで,これまで非特権コンテナ内で実行できなかった操作ができるようになり,非特権コンテナ活用の幅が広がりました。

今回の記事を書くに当たって,udzuraさんにレビューをしていただき,特に実装に近い部分について色々と教えていただきました。ありがとうございました。

参考文献

著者プロフィール

加藤泰文(かとうやすふみ)

2009年頃にLinuxカーネルのcgroup機能に興味を持って以来,Linuxのコンテナ関連の最新情報を追っかけたり,コンテナの勉強会を開いたりして勉強しています。英語力のない自分用にLXCのmanページを日本語訳していたところ,あっさり本家にマージされてしまい,それ以来日本語訳のパッチを送り続けています。

Plamo Linuxメンテナ

Twitter:@ten_forward
技術系のブログ:http://tenforward.hatenablog.com/