LXC を AppArmor で守るメモ。
LXC のようなコンテナ技術は環境を隔離するので、コンテナの中からホストに悪さできないようになってて欲しいですが、回避できない様々な攻撃方法があります。そこで他の技術と組み合わせてセキュリティを確保します。Linux Containers - LXC - Security によるとそもそも LXC は一般ユーザで動かすべきで AppArmor に頼ってはならぬとの事ですが、どうしても AppArmor が必要になった場合のメモです。
私がテストに使っている Ubuntu-18 では最初から LXC の AppArmor サポートが有効になっていました。もしも /etc/apparmor.d/lxc
が無ければ --enable-apparmor
付きで LXC をビルドし直します。(例: Yocto だと PACKAGECONFIG_append_pn-lxc = "apparmor"
を local.conf に追加)
それではこんな例題をやっていきましょう。
- ルート権限で LXC の busybox コンテナ bbox を作る。
- ホストの
/tmp/
をコンテナ bbox の/host/
にマウントする。 - コンテナからは
/host/black.txt
を読む事はできない。
AppArmor は本来ホワイトリスト形式なので、デフォルト禁止で読めるファイルだけ記述するのが正しいやり方です。しかし LXC + AppArmor の組み合わせでは禁止するファイルを記述するブラックリスト形式しか使えません。理由は後述します。
ルート権限で LXC の busybox コンテナ bbox を作る。
まず以下のコマンドでさくっと busybox コンテナを作り、設定ファイル /var/lib/lxc/bbox/config
を編集します。
sudo lxc-create --template busybox --name bbox
sudo chmod a+rx /var/lib/lxc/bbox
sudo chmod a+rw /var/lib/lxc/bbox/config
vi /var/lib/lxc/bbox/config
追加する内容は以下です。
lxc.mount.entry = /tmp host none rw,bind,create=dir 0 0
これでホストの /tmp/
をコンテナの /host/
にマウントします。内容は fstab のフォーマットですが以下のような意味です。
- 1枡目:
/tmp
: コンテナに公開したいホストのディレクトリ位置。 - 2枡目:
host
: コンテナ側の位置です。相対パスを書くと、この場合/var/lib/lxc/bbox/rootfs
からの位置。 - 3枡目:
none
: bind mount を使うので none にします。 - 4枡目:
rw,bind,create=dir
: 追加設定。-
rw
: 読み書き可能。 -
bind
: ディレクトリをそのままマウント。 -
create=dir
: マウント位置が無かったら作る。
-
- 5桁目:
0
: dump が使うらしい。デフォルトのままにします。 - 6桁目:
0
: fsck が使うらしい。デフォルトのままにします。
さていよいよ実験します。lxc-execute
を使うとコンテナを開始してコマンドを実行して即コンテナを終了します。別に lxc-start
を使うとバックグラウンドで動かす事もできますが、テストには lxc-execute
の方が便利です。
$ sudo lxc-execute -n bbox -- sh -c 'echo Hello white > /host/white.txt'
$ sudo lxc-execute -n bbox -- sh -c 'echo Hello black > /host/black.txt'
$ cat /tmp/*.txt
Hello black
Hello white
このようにコンテナの /host/
に書き込んだものがホストの /tmp/
に現れたら OK です。
AppArmor で守る
それでは、いよいよ AppArmor でコンテナに制限をかけてみます。実は Ubuntu ではデフォルトで apparmor が有効になっていて、最初からある程度の制限がかかっています。例えば /sys/kernel/debug/
にアクセスできません。
$ sudo lxc-execute -n bbox -- ls /sys/kernel/debug/
ls: can't open '/sys/kernel/debug/': Permission denied
このデフォルトの profile としては lxc-container-default-cgns が使われていて、特に container-base に主要なルールが記述されています。これで余計なリソースにアクセスできないようになっています。
自分で新たにルールを作るにはこの既存の profile をコピーして作ります。
cd /etc/apparmor.d/lxc/
sudo cp lxc-default-cgns bbox
sudo vi bbox
例えばこんな感じ。ここで重要な注意ですが、ルールに使うパスはコンテナから見たパス (例: /host
) にします。ホストから見たパス /tmp
ではありません。
# profile 名を bbox に変更する
profile bbox flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/lxc/container-base>
deny mount fstype=devpts,
mount fstype=cgroup -> /sys/fs/cgroup/**,
mount fstype=cgroup2 -> /sys/fs/cgroup/**,
# 以下追加部分 /host/white.txt を許可して /host/black.txt を禁止します。
audit deny /host/black.txt rw,
audit allow /host/white.txt rw,
}
ややこしいことに更新をカーネルに通知するにはするにはこのファイルではなく ../lxc-containers
の更新を apparmor_parser -r
コマンドで伝えます。この /etc/apparmor.d/lxc-containers
の中で lxc/
内の各種 profile を include する仕組みになっています。
sudo apparmor_parser -r ../lxc-containers
仕上げに /var/lib/lxc/bbox/config
の lxc.apparmor.profile
に作った profile 名を設定すると、指定した profile が有効になります。
lxc.apparmor.profile = bbox
試してみます。
$ sudo lxc-execute -n bbox -- sh -c 'echo Hello white > /host/white.txt'
$ sudo lxc-execute -n bbox -- sh -c 'echo Hello black > /host/black.txt'
sh: can't create /host/black.txt: Permission denied
うまくいきました!実は、上で
audit allow /host/white.txt rw,
のように許可するファイルを書きましたが実はこの行には効果がありません。container-base の中にすべてのファイルアクセスを許可する file
というルールがあるために、すべてのファイルアクセスが予め許可されてしまっています。これが LXC + AppArmor の組み合わせではブラックリスト方式しか動かない理由です。一方で container-base の中を見ると涙ぐましい努力のグロブパターンでホワイトリストを実現しているので興味のある方は参考にしてください。
頑張れば container-base を使わず自分で profile ホワイトリスト方式で書いて更にセキュアな環境を作れると思いますが、私は大変過ぎて諦めました。
以上分かってしまえば簡単ですが、マニュアルを読むだけでは絶対に分からないノウハウでした。大枠 ChatGPT に教えてもらいました。
参考
-
Linux Containers - LXC - Security
- LXC を使う際のセキュリティの考え方。
-
Linux Containers - LXC - Manpages - lxc.container.conf.5
-
lxc.apparmor.profile
が profile 名を表すという事が明記されておらず不明瞭なマニュアル。
-
-
LXC - Qiita
- LXC の基本。前に書いた記事です。
-
LXC - Debian Wiki
- なんとなく
lxc.apparmor.profile
が profile 名を表す事が分かる。
- なんとなく