仮想化通信

日本仮想化技術株式会社の公式エンジニアブログ

Podmanのrootlessを試す

前回はDockerのrootlessを試しましたので、今回はPodmanで同じようなことを試してみようと思います。

tech.virtualtech.jp

Podmanとは

PodmanとはRed Hat社が開発したコンテナ管理ツールです。

Red Hat Enterprise Linux 8以降ではDockerの利用は非推奨となり、その代替としてPodmanが提供されています。PodmanはDockerと互換性があり、コマンドライン操作についてはほぼ互換になっています。

PodmanにはDockerのようにdockerデーモンに該当するデーモンプロセスがないため、デーモンがダウンして全コンテナに影響が出るといったことがないのも特徴の一つです。

最近のDockerであればrootlessについては同様に可能ですが、インストールするだけですぐroot権限が無くても一般ユーザで利用できる点がDockerよりも優れています。(Dockerのrootlessモードはちょっとだけ設定変更が必要です)。

Red Hat Enterprise Linux(以下RHEL)や様々なRHELクローン、CentOS StreamやFedoraではパッケージが標準で提供されており、簡単に導入可能です。そのほかのLinux ディストリビューションでもパッケージが提供されているようです。

Podmanの設定の話

PodmanはデフォルトでSELinuxサポートが適用されています。

Podmanなどのデフォルトの設定は /usr/share/containers/ に置かれていて、デフォルト設定でもある程度セキュアな状態を保っています。

$ ls /usr/share/containers/ 
containers.conf  mounts.conf  seccomp.json  selinux

PodmanとSELinux

このディレクトリーのselinuxディレクトリーにはSELinuxがホスト上でenforcingモードの場合、それぞれのプロセスに設定するコンテキストが設定されています。

# tree selinux/
selinux/
└── contexts
# cat selinux/contexts 
process = "system_u:system_r:container_t:s0"
file = "system_u:object_r:container_file_t:s0"
ro_file="system_u:object_r:container_ro_file_t:s0"
kvm_process = "system_u:system_r:container_kvm_t:s0"
init_process = "system_u:system_r:container_init_t:s0"
engine_process = "system_u:system_r:container_engine_t:s0"

Podmanとセキュアコンピューティングモード

seccomp.jsonはセキュアコンピューティングモード(secure computing mode; seccomp)の設定がされています。この設定によってコンテナー内で利用できる処理(システムコール)を制限できます。

適切な設定が行われていないと、コンテナランタイムの脆弱性や仕様を悪用するなどして不正利用や情報の漏洩につながる可能性があるので、コンテナーに対してシステムコールの許可を適切に設定することは重要です。ちなみにDockerでも標準で300 以上のシステムコールのうち、約 44 件のシステムコールを無効にしているようです。

標準のseccomp.jsonをコピーもしくは編集してカスタムポリシーの定義も可能ですが、設定ファイルはJSON形式で書かれていますし、かつ設定の記述方法もあまり情報がなく難解なので、設定するのは至難の業です。

Podmanの場合はcontainers.confにケーパビリティを設定するところがあります。ここを適切に設定するだけで必要もしくは不要なシステムコールの定義ができるので、こちらで設定するほうがわかりやすくておすすめです。

Linuxではプロセスは一般ユーザ権限か特権(root権限)で動きます。一般ユーザー権限では動かないプロセスの場合は特権を付与して実行するわけですが、すべての特権を渡してしまうとそのプロセスに脆弱性があった場合に、すべての特権を取られてしまう可能性があります。それを防ぐために、現在のLinuxでは特権を細分化して「ケーパビリティ」として定義されています。

適切なケーパビリティを付与することによって、すべての特権を付与するのと比べてプロセスの脆弱性を狙った攻撃を食らったとしても被害を最小化することができる可能性があります。

ケーパビリティとはつまり、必要最小限の権利を与えるために設定するもののことです。Linux kernel 2.4から実装されています。詳細については次をご覧ください。

Podmanには次のようなケーパビリティがデフォルトで設定されていました。

# List of default capabilities for containers. If it is empty or commented out,
# the default capabilities defined in the container engine will be added.
#
default_capabilities = [
  "CHOWN",
  "DAC_OVERRIDE",
  "FOWNER",
  "FSETID",
  "KILL",
  "NET_BIND_SERVICE",
  "SETFCAP",
  "SETGID",
  "SETPCAP",
  "SETUID",
  "SYS_CHROOT"

最近のLinuxディストリビューションではDockerとPodmanのパッケージが提供されており、あえてPodmanを使う理由がわからないと思われる方もいるかも知れませんが、こういった設定が簡単にできる点がPodmanのメリットであると私は考えます。

ちなみに /usr/share/containers/containers.conf の冒頭にも書かれていますが、/usr/share/containers/にデフォルトの設定が、/etc/containers/にrootユーザーでPodmanを使うときの設定があり、ユーザーごとの設定は$HOME/.config/containers/に置くと良いそうです。

Podmanの設定を変えてみる

先程のケーパビリティのデフォルト設定を、ユーザーでPodmanを使ってコンテナーを実行した場合にchrootができないように設定変更してみましょう。基本的にはデフォルトのcontainers.confを持ってきて、書き換えるだけで設定は完了します。

$ mkdir -p $HOME/.config/containers/
$ cp /usr/share/containers/containers.conf  $HOME/.config/containers/
$ cp /usr/share/containers/storage.conf  /etc/containers/
$ vi $HOME/.config/containers/containers.conf
...
default_capabilities = [
  "CHOWN",
  "DAC_OVERRIDE",
  "FOWNER",
  "FSETID",
  "KILL",
  "NET_BIND_SERVICE",
  "SETFCAP",
  "SETGID",
  "SETPCAP",
  "SETUID",
  "SYS_CHROOT"
]

「SYS_CHROOT」の行を取り除いてみてください。忘れずに最後の項目のカンマも取り除きます。

storage.confの方もrootless_storage_pathのコメント部分を外します。

...
# Storage path for rootless users
#
# rootless_storage_path = "$HOME/.local/share/containers/storage"
rootless_storage_path = "$HOME/.local/share/containers/storage"

設定したディレクトリーを作成しておきましょう。

$ mkdir -p $HOME/.local/share/containers/storage

Podmanで前回Dockerでやったことを試す

では、前回試したことをPodmanで実行してみましょう。 Podmanの場合、dockerのコマンドをpodmanに置き換えるだけで、後はサブコマンド、オプションを指定するだけで同じように使えます。

まずはコンテナーを起動し、

$ podman container run -it -v /:/rootfs fedora:36 bash
[root@49400a4458e6 /]# cat /etc/fedora-release 
Fedora release 36 (Thirty Six)

chrootを実行すると、次のようにパーミッションがないと言われて失敗します。

[root@49400a4458e6 /]# chroot /rootfs
chroot: cannot change root directory to '/rootfs': Operation not permitted

もとの設定にあった「SYS_CHROOT」を追加してみましょう。

$ vi $HOME/.config/containers/containers.conf
...
default_capabilities = [
  "CHOWN",
  "DAC_OVERRIDE",
  "FOWNER",
  "FSETID",
  "KILL",
  "NET_BIND_SERVICE",
  "SETFCAP",
  "SETGID",
  "SETPCAP",
  "SETUID",
  "SYS_CHROOT"
]

もう一度同じように実行してみると、コンテナーのシェルから抜けてしまうことが可能でした。

$ podman container run -it -v /:/rootfs fedora:36 bash
[root@499e0eae894f /]# chroot /rootfs
sh-5.1# 

とはいえ、システムでSELinuxが有効になってさえいれば、抜けたシェルの先で任意のコマンドを実行するのはなかなか至難の業です。前の方法のようにchrootは実行できないようにしておいたほうが、多くのLinuxディストリビューションで有効な設定ですし、安全です。

sh-5.1# podman ps
Error: error creating runtime static files directory: mkdir /var/lib/containers/storage/libpod: permission denied
sh-5.1# dnf update
Config error: [Errno 13] Permission denied: None

というわけで、Podmanはデフォルトの設定でSELinuxが有効であり、デフォルトの設定のままでも一定の保護は有効であることがわかりました。また、ケーパビリティを適切に設定することで最小特権をコンテナーに適用することができることも確認できました。