仮想化通信

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

Dockerのrootlessを試す

Dockerのドキュメントを見ていたら「rootレスモード」というものが気になったので、試してみることにしました。 以前Podmanが先に「rootレスモード」を実装していてDockerでも利用できるようになったくらいのときに、この機能を一度試したことがあります。

あの頃はシェルスクリプトを実行してセットアップする方法で導入した気がしますが、現在も同じ方法で導入できるだけでなく、 パッケージでDockerをインストールしたときに、/usr/binディレクトリーにシェルスクリプトdockerd-rootless-setuptool.shが展開されるので、これを利用したセットアップもできるようです。この方法では動かしたことがなかったので、早速試すことにしました。

rootレスモードDockerのセットアップ

セットアップの方法は、普通にDocker Engineをインストールしたあと、rootレスで実行するためのセットアップを行うだけです。

DebianやUbuntuの場合はパッケージインストールの際にDockerのサービスが起動してしまうので、rootレス設定前にそれらを停止する必要があります。その他、Linuxディストリビューションごとに必要なパッケージを導入します。具体的に何を必要とするかはrootlessモードのドキュメントを参照。

$ sudo systemctl disable --now docker.service docker.socket

なお、今回もDockerでSELinuxサポート機能を使いたいがために、Dockerを実行するLinuxホストのOSとしては「CentOS Stream 8」(一部については「CentOS Stream 9」でも確認)を使っています。

rootフル/rootレス環境で色々やってみた

rootフル(通常のようにDockerを導入した構成)とrootレス(ユーザーモードでDockerを動かす構成)で色々やってみました。 行った内容については、以前書いたブログ記事を元としています。

ytooyama.hatenadiary.jp

rootフルかつSELinuxで守られていない場合

この場合は以前と同じように迂回して新しいコンテナーを実行できてしまいます。よく言う「ホストにアクセスされ、任意のコマンドを実行できてしまう」状態ですね。

[root@docker-nomal ~]# docker container run -it -v /:/rootfs fedora:36 bash
[root@c288d15671f4 /]# cat /etc/redhat-release 
Fedora release 36 (Thirty Six)

コンテナーの中でFedora 36を動かしています。 chrootを実行してみます。

[root@c288d15671f4 /]# chroot /rootfs/

ホストのシェルに抜けることができました。

sh-4.4# cat /etc/redhat-release 
CentOS Stream release 8

ホストのシェルでdockerコマンドが利用できます。新しいコンテナーの実行も余裕で動作しました。

sh-4.4# docker container ps
CONTAINER ID   IMAGE       COMMAND   CREATED          STATUS          PORTS     NAMES
c288d15671f4   fedora:36   "bash"    58 seconds ago   Up 56 seconds             goofy_goldwasser
sh-4.4# docker container run -it ubuntu:20.04 bash
root@075a802ba5ad:/# cat /etc/os-release |grep PRETTY_NAME
PRETTY_NAME="Ubuntu 20.04.4 LTS"

何もセキュリティ対策していないと、簡単に攻略可能でした。 次はSELinuxサポートを有効にしてみます。

rootフルだがSELinuxを有効(かつDockerのSELinuxサポートがオン)の場合

これについては、迂回した後のシェルからdockerコマンドがSELinuxサポートがOFF時と異なり実行できないため、問題はなさそうです。

[root@docker-nomal ~]# cat /etc/docker/daemon.json 
{
  "bip": "192.168.20.1/24",
  "mtu": 1412,
  "selinux-enabled": true
}
[root@docker-nomal ~]# systemctl restart docker

[root@docker-nomal ~]# docker container run -it -v /:/rootfs fedora:36 bash
[root@c288d15671f4 /]# cat /etc/redhat-release 
Fedora release 36 (Thirty Six)
[root@c288d15671f4 /]# chroot /rootfs/
sh-4.4# cat /etc/redhat-release 
CentOS Stream release 8

ホストのシェルに抜けられてしまうがDocker daemon socketは守られているので、想定した作業は実行はできない状況。

sh-4.4# docker container ps
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json": dial unix /var/run/docker.sock: connect: permission denied
  1. root lessモードで動かしたDockerの場合

unix:///var/run/docker.sockに接続できないと言われるが、rootlessで動いていることがわかっている場合は引き続き悪用可能。

[centos@docker-rootless ~]$ cat ~/.config/docker/daemon.json
{
  "selinux-enabled": false,
  "bip": "192.168.10.1/24",
  "mtu": 1412
}
[centos@docker-rootless ~]$ systemctl --user restart docker

[centos@docker-rootless ~]# docker container run -it -v /:/rootfs fedora:36 bash
[root@c288d15671f4 /]# cat /etc/redhat-release 
Fedora release 36 (Thirty Six)
[root@c288d15671f4 /]# chroot /rootfs/
sh-4.4# cat /etc/redhat-release 
CentOS Stream release 8

ホストシェルへ抜けられたが、docker.sockへアクセスできないというメッセージになりました。 「DOCKER_HOSTはrootlessモードのデフォルトパスにソケットがある」と指定してあげると、悪用できました。

Dockerのプロセスはrootで動いていないものの、コンテナーへのアクセスがrootユーザー前提になっていると悪用できるという例です。

sh-4.4# docker container ps
WARNING: Error loading config file: /root/.docker/config.json: open /root/.docker/config.json: permission denied
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

sh-4.4# export DOCKER_HOST=unix:///run/user/1000/docker.sock
sh-4.4# docker container ps
WARNING: Error loading config file: /root/.docker/config.json: open /root/.docker/config.json: permission denied
CONTAINER ID   IMAGE       COMMAND   CREATED              STATUS              PORTS     NAMES
4fc413c3c0f9   fedora:36   "bash"    About a minute ago   Up About a minute             friendly_euclid

sh-4.4# docker container run -it ubuntu:20.04 bash
root@075a802ba5ad:/# cat /etc/os-release |grep PRETTY_NAME
PRETTY_NAME="Ubuntu 20.04.4 LTS"

rootレスモードで動かしたDockerかつSELinuxを有効(かつDockerのSELinuxサポートがオン)の場合

「ホストのシェルに抜けられてしまうがDocker daemon socketは守られている」という同じ動きをしてほしかったのですが、残念ながら迂回できてしまいました。 Dockerのroot lessモードについてはSELinuxはあまり有用ではないのかも?

ちなみに念のため同じことを「CentOS Stream 9」ベースでも試してみましたが、動きとしては同様でした。

[centos@docker-rootless ~]$ cat ~/.config/docker/daemon.json
{
  "selinux-enabled": true,
  "bip": "192.168.10.1/24",
  "mtu": 1412
}
[centos@docker-rootless ~]$ systemctl --user restart docker

[centos@docker-rootless ~]$ docker container run -it -v /:/rootfs fedora:36 bash
[root@c288d15671f4 /]# cat /etc/redhat-release 
Fedora release 36 (Thirty Six)
[root@c288d15671f4 /]# chroot /rootfs/
sh-4.4# cat /etc/redhat-release 
CentOS Stream release 8
sh-4.4# docker ps
WARNING: Error loading config file: /root/.docker/config.json: open /root/.docker/config.json: permission denied
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

良かった、接続できない...じゃないですね。

sh-4.4# export DOCKER_HOST=unix:///run/user/1000/docker.sock
sh-4.4# docker container ps
WARNING: Error loading config file: /root/.docker/config.json: open /root/.docker/config.json: permission denied
CONTAINER ID   IMAGE       COMMAND   CREATED              STATUS              PORTS     NAMES
4fc413c3c0f9   fedora:36   "bash"    About a minute ago   Up About a minute             friendly_euclid

sh-4.4# docker container run -it ubuntu:20.04 bash
WARNING: Error loading config file: /root/.docker/config.json: open /root/.docker/config.json: permission denied
root@7c8c0ffe1236:/# cat /etc/os-release |grep PRETTY_NAME
PRETTY_NAME="Ubuntu 20.04.4 LTS"

Dockerはrootlessで動いているが現在rootユーザーでシェルにアクセスしているため、rootユーザーのDockerの設定がないという警告は出るものの、 コンテナの作成やアクセスはできてしまうようです。これは事前の予想ではできないと思っていました。

rootレスで安全にコンテナを動かすには?

一般ユーザーをつくって、アプリケーションコンテナーを一般ユーザーで動かすのを試してみます。 今回はDockerfileを作ってユーザーを作ってコンテナーにアクセスすると、任意のユーザーでプロセスを実行するようなイメージを作ってみます。

$ tree test/
test/
└── Dockerfile
$ cat test/Dockerfile 
FROM docker.io/fedora:36

RUN useradd --create-home appuser
WORKDIR /home/appuser
USER appuser

$ docker image build --compress -t myfedora:latest -f Dockerfile .

作成したイメージを使ってコンテナーを実行してみます。

[centos@docker-rootless test]$ docker container run -it myfedora:latest bash
[appuser@857252a67de7 ~]$ id
uid=1000(appuser) gid=1000(appuser) groups=1000(appuser)
[appuser@857252a67de7 ~]$ sudo dnf update

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

[sudo] password for appuser: 

プロセスはrootで動いていないので、root権限が必要になるとパスワードが要求されます(CTRL+Cで抜けられます)。 当然ながら、root権限が必要な作業は実行できません。アプリケーションの実装によると思いますが、root権限がなくても動くアプリケーションなら一般ユーザーで動かすだけでも防げる事案もあります。

いつものマウントシェル迂回については、chrootはパーミッションがないから実行できませんでした。 DockerfileへのUSER指定は有効であることが証明されました。

[centos@docker-rootless test]$ docker container run -it -v /:/rootfs myfedora:latest bash
[appuser@46df23e384ea ~]$ chroot /rootfs
chroot: cannot change root directory to '/rootfs': Operation not permitted

まとめ

  • rootレスモードはrootフルモード同様に利用できるが制限もあるので、十分検証が必要
  • 現状は従来のrootフルモードで、セキュリティガチガチのほうが安全のような気がする
    • SELinux or AppArmorの活用
    • セキュリティコンピューティングモード(secure computing mode; seccomp)
    • Dockerfileを作ってrootユーザー以外でアプリケーションを実行する
      • USERの指定
  • ベースとして利用するコンテナイメージはどういった構成になっているかよく確認する
    • 公式のイメージだけを使う(出どころの明確ではないイメージは実行しない)
    • イメージの脆弱性スキャンを行い、対策をしたイメージを使ってアプリケーションを実行
    • 特にsshサーバーとか入っているイメージは要注意。謎の公開鍵が登録されていたりしないか?

Dockerセキュリティには色々な情報がまとまっているので、見ておくと良いと思います(定期的に更新されているため)。