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を動かす構成)で色々やってみました。 行った内容については、以前書いたブログ記事を元としています。
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
- 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セキュリティには色々な情報がまとまっているので、見ておくと良いと思います(定期的に更新されているため)。