Podmanとユーザ名前空間: 最高の組みあわせ

この記事は Podman and user namespaces: A marriage made in heaven | Opensource.com を翻訳したものです。RHEL 7.8以降およびRHEL 8.1以降ではこの記事で紹介されているユーザ名前空間を活用した rootless container がフルサポートとなっていて、利用できます。

Podmanを使用して別のユーザ名前空間でコンテナを実行する方法を学びます

:::2018年12月13日 Daniel J Walsh (Red Hat):::

f:id:mrwk:20200403144300p:plain

画像: opensource.com

 

libpodライブラリの一部であるPodmanを使うことで、ユーザはポッドやコンテナ、コンテナイメージを管理することができます。前回の記事では、コンテナをより安全に実行する方法としてPodmanについて書きました。ここでは、Podmanを使ってコンテナを別々のユーザ名前空間で実行する方法を説明します。

私は、主にRed HatのEric Biederman氏が開発したユーザ名前空間を、コンテナを分離するための素晴らしい機能だと常々考えていました。ユーザ名前空間では、ユーザ識別子(UID)とグループ識別子(GID)のマッピングを指定してコンテナを実行することができます。つまり、コンテナ内ではUID 0、コンテナ外ではUID 100000として実行できます。コンテナプロセスがコンテナを脱出した場合、カーネルはUID 100000として扱います。それだけでなく、ユーザ名前空間にマッピングされていないUIDが所有するファイルオブジェクトは「nobody」(65534、kernel.overflowuid)が所有するものとして扱われ、そのオブジェクトが「other」(world readable/writable)でアクセス可能でない限り、コンテナプロセスはアクセスを許可されません。

"本物の" root が所有するファイルをパーミッション 660 で持っていて、ユーザ名前空間のコンテナプロセスがそれを読もうとした場合、そのファイルへのアクセスは阻止され、そのファイルは nobody が所有するものとして表示されます。

これがどのように機能するかを示します。まず、rootが所有するシステムにファイルを作成します。

$ sudo bash -c "echo Test > /tmp/test"
$ sudo chmod 600 /tmp/test
$ sudo ls -l /tmp/test
-rw-------. 1 root root 5 Dec 17 16:40 /tmp/test

次に、ユーザ名前空間マップ0:100000:5000で実行されているコンテナにファイルをボリュームマウントします。

$ sudo podman run -ti -v /tmp/test:/tmp/test:Z --uidmap 0:100000:5000 fedora sh
# id
uid=0(root) gid=0(root) groups=0(root)
# ls -l /tmp/test
-rw-rw----. 1 nobody nobody 8 Nov 30 12:40 /tmp/test
# cat /tmp/test
cat: /tmp/test: Permission denied

上の --uidmap 設定は、コンテナの外側のUID 100000から始まるコンテナ内の5000のUIDの範囲(つまり範囲は100000-104999)からコンテナ内のUID 0から始まる範囲(つまり範囲は0-4999)までをマップするようにPodmanに指示します。コンテナ内では、私のプロセスがUID 1として実行されている場合、ホスト上では100001です。

本物の UID=0 がコンテナ内にマッピングされていないため、root が所有するファイルは誰も所有していないものとして扱われます。コンテナ内のプロセスが CAP_DAC_OVERRIDE を持っていても、この保護を上書きすることはできません。DAC_OVERRIDE を使用すると、ルートプロセスが所有していなくても、システム上の任意のファイルを読み書きできるようになりますし、そのプロセスがルートが所有していなくても、ワールドが読み書きできるようになっていなくても、読み書きできるようになります。

ユーザ名前空間でのケーパビリティは、ホスト上でのケーパビリティとは異なります。それらは名前空間内でのケーパビリティです。つまり、私のコンテナのルートは、コンテナ内でのみ、つまりユーザ名前空間にマップされた UID の範囲内でのみ能力を持っています。コンテナプロセスがコンテナから脱出した場合、UID=0 を含め、ユーザ名前空間にマップされていない UID に対しては何の能力も持ちません。 プロセスが何らかの方法で別のコンテナに入ることができたとしても、コンテナが異なる範囲の UID を使用している場合は、それらの能力を持つことはできません。

注意: SELinux や他の技術でも、コンテナプロセスがコンテナから抜け出した場合にできることを制限しています。

`podman top`を使用してユーザ名前空間を表示

コンテナ内で実行しているプロセスのユーザ名を調べて、ホスト上の本当の UID を識別できるように、 podman top に機能を追加しました。

まず、UIDマッピングを使ってスリープコンテナを実行してみましょう。

$ sudo podman run --uidmap 0:100000:5000 -d fedora sleep 1000

次に podman top を実行します。

$ sudo podman top --latest user huser
USER HUSER
root 100000
$ ps -ef | grep sleep
100000   21821 21809  0 08:04 ?     00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1000

podman top のレポートによると、ユーザプロセスはコンテナ内では root として実行されているが、ホスト (HUSER) では UID 100000 として実行されていることに注意してください。また、psコマンドでスリーププロセスがUID 100000で実行されていることを確認しています。

ここで、2つ目のコンテナを実行してみますが、今回は200000から始まる個別のUIDマップを選択します。

$ sudo podman run --uidmap 0:200000:5000 -d fedora sleep 1000
$ sudo podman top --latest user huser
USER   HUSER
root   200000
$ ps -ef | grep sleep
100000   21821 21809  0 08:04 ?     00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1000
200000   23644 23632  1 08:08 ?     00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1000

podman top のレポートでは、2 つ目のコンテナはコンテナ内では root として実行されていますが、ホスト上では UID=200000 として実行されていることに注意してください。

また、ps コマンドを見てみると、それぞれのsleepプロセスについてひとつは 100000で、もう一つは 200000 で動作していることがわかります。

これは、別々のユーザ名前空間内でコンテナを実行することで、伝統的なプロセス間の UID 分離が可能になることを意味しています。

ユーザ名前空間の問題

私は数年前から、誰もが欲しがるセキュリティツールとしてユーザ名前空間を提唱してきましたが、ほとんど誰も使っていませんでした。理由は、ファイルシステムのサポートやシフトファイルシステムがなかったからです。

コンテナでは、多くのコンテナ間でベースイメージを共有したいです。上の例では、それぞれの例でFedoraのベースイメージを使用しています。Fedora イメージ内のほとんどのファイルは実在の UID=0 で所有されています。 このイメージ上でユーザ名前空間 0:100000:5000 でコンテナを実行した場合、デフォルトではこれらのファイルはすべて誰も所有していないとみなされるので、ユーザ名前空間に合わせてこれらの UID をすべてシフトする必要があります。私は何年も前から、カーネルにこれらのファイル UID をユーザ名前空間に一致するようにリマップするように指示するマウントオプションが欲しいと思っていました。アップストリームカーネルストレージの開発者は、この機能について調査を続け、進歩を遂げていますが、難しい問題です。

Podman は、Nalin Dahyabhai が率いるチームによってcontainers/storageに組み込まれた自動 chowning により、同じイメージ上で異なるユーザ名前空間を使用することができます。Podmanはcontainers/storageを使用しますが、Podmanが初めてコンテナイメージを新しいユーザ名前空間で使用すると、containers/storageはイメージ内のすべてのファイルをユーザ名前空間にマッピングされたUIDに「chowning」(つまり所有権を変更)し、新しいイメージを作成します。これを fedora:0:100000:5000 イメージと考えてください。

Podmanが同じUIDマッピングを持つイメージ上で別のコンテナを実行すると、"pre-chowned "イメージを使用します。2つ目のコンテナを0:200000:5000で実行すると、containers/storageは2つ目のイメージを作成します。

podman buildpodman commitを行っていて、新しく作成されたイメージをコンテナレジストリにプッシュすると、Podman は containers/storage を使ってシフトを逆にして、すべてのファイルが chowned されたイメージを実際の UID=0 に戻してプッシュすることに注意してください。

これは、イメージ内のファイル数に応じて chown が遅くなる可能性があるため、新しい UID マッピングでのコンテナ作成が本当に遅くなる可能性があります。また、通常の OverlayFS では、イメージ内の全てのファイルがコピーされます。通常の Fedora イメージでは、chown を終了してコンテナを起動するのに 30 秒かかることがあります。

幸いなことに、Vivek Goyal と Miklos Szeredi を中心とした Red Hat カーネルストレージチームは、カーネル 4.19 で OverlayFS に新機能を追加しました。この機能は、metadata only copy-up と呼ばれるものです。マウントオプションとして metacopy=on を指定してオーバーレイファイルシステムをマウントした場合、ファイル属性を変更しても下位レイヤのコンテンツはコピーアップされません; カーネルは下位レイヤのデータを指す参照を含む属性を含む新しい inode を作成します。カーネルは下位レベルのデータを指す参照を含む属性を含む新しい inode を作成します。コンテンツが変更されれば、コンテンツはコピーアップされます。この機能は Red Hat Enterprise Linux 8 Beta で利用可能です。

これは、コンテナのchowningが数秒でできることを意味し、各コンテナのストレージ容量が2倍になることはありません。

これにより、Podman のようなツールでコンテナを別のユーザ名前空間で実行することが可能になり、システムのセキュリティが大幅に向上します。

今後

Podmanに --userns=auto のような新しいフラグを追加して、実行するコンテナごとにユニークなユーザ名前空間を自動的に選択するように指示したいのですが、これはSELinuxがマルチカテゴリセキュリティ(MCS)ラベルを分離して動作する方法に似ています。これは、SELinuxがマルチカテゴリセキュリティ(MCS)ラベルを個別に設定して動作する方法に似ています。環境変数 PODMAN_USERNS=auto を設定すれば、フラグを設定する必要すらありません。

Podmanはついに、ユーザが別々のユーザ名前空間でコンテナを実行できるようになりました。BuildahCRI-Oのようなツールも、ユーザ名前空間を活用できるようになるでしょう。ただし、CRI-Oについては、Kubernetesがどのユーザ名前空間でコンテナエンジンを実行するのかを理解する必要があり、上流ではそれに取り組んでいます。

次回の記事では、ユーザ名前空間で非rootとしてPodmanを実行する方法を説明します。

 

クリエイティブ・コモンズ・ライセンス
この 記事 は クリエイティブ・コモンズ 表示 - 継承 4.0 国際 ライセンスの下に提供されています。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。