Docker 19.03でのルートレスコンテナ:コンテナのセキュリティと管理性の向上


このブログ記事では、Docker 19.03で導入されたルートレスコンテナについて、この機能がコンテナのセキュリティと管理性をどのようにさらに向上させているかを取り上げています。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

なぜルートレスコンテナなのか

DockerやKubernetesが一般的になってきたことで、コンテナのセキュリティも大きな関心事になってきています。Dockerはアプリケーションの仮想化機能を提供し、ネームスペースやcgroupsを通じてリソースの分離やクォータの制約を実装しています。Docker Engineは以下のようなクライアント・サーバー構造を持っています。

Dockerクライアント(TCP/Unix Socket)→Dockerデーモン(親子プロセス)→コンテナ

Linuxのネームスペースの作成やファイルシステムのマウントには特権的な機能が必要なため、Docker Daemonは常にrootユーザーが起動する必要がありました。これにより、DockerにアクセスできるユーザーはDocker Engineに接続することでroot権限を取得し、システムの監査機能を迂回して攻撃を仕掛けることができます。これにより、一部のシナリオではコンテナの適用ができなくなっています。例えば、ハイパフォーマンスコンピューティングの分野では、従来のリソース管理やスケジューリングシステムではコンテナを非特権ユーザーが実行する必要があるため、コミュニティは別のコンテナランタイム-Singularityを実装しなければなりません。

MobyとBuildKitメンテナの須田昭宏氏の素晴らしい仕事により、Docker EngineとBuildKitにルートレスサポートが追加されました。このサポートにより、Docker Engineを非特権ユーザとして実行することが可能になり、Linuxセキュリティシステムのより良い再利用が可能になりました。

注意事項
1、現在、ルートレスコンテナは実験モードで利用可能です。cgroupsリソース制御、apparmorセキュリティプロファイル、チェックポイント/リストアなど、一部の機能はまだサポートされていません。
2、現在、Ubuntuのみがルートレスモードでのオーバーレイファイルシステムをサポートしています。セキュリティ上の理由から、このソリューションはアップストリームのサポートを受けていません。他のオペレーティングシステムでは、ルートレスモードはvfsストレージドライバを使用するため、パフォーマンスに影響を与える可能性があります。そのため、ルートレスモードはI/Oを多用するアプリケーションには適していません。

関連するコア技術

最初の鍵は、ユーザ名前空間を活用することです。ユーザー名前空間はユーザーIDの範囲をマッピングするので、内側の名前空間のルートユーザーが親名前空間の非特権範囲にマッピングされます。Docker Engineはすでに対応する機能をサポートするために--userns-remapフラグを提供しており、より優れたコンテナセキュリティを提供しています。ルートレスモードも同様に動作しますが、Dockerデーモンもリマップされた名前空間で実行されます。

非特権ユーザーはユーザーネームスペースにネットワークネームスペースを作成し、iptablesのルール管理やtcpdumpなどの操作を行うことはできますが、ホストとコンテナをまたいでvethペアを作成することはできず、コンテナにインターネット接続ができませんでした。この問題を解決するために、アキヒロでは、特権を持たないユーザーネームスペースにTAPデバイスを接続することで、コンテナにインターネット接続を提供するために、ユーザーモードネットワーク(SLiRP)を利用しています。次の図はそのアーキテクチャを示しています。

詳細については、slirp4netns プロジェクトをチェックしてください。

ルートレスコンテナ用の環境を設定する方法

以下の手順で、ルートレスコンテナ用の環境を設定することができます。検証の練習はCentOS 7.6のVM上で行うことに注意してください。

次に、ユーザーを作成したいと思います。以下のコマンドを実行することで行うことができます。

$ useradd moby
$ passwd moby

そして、新しいユーザーをsudoersグループに追加します。

usermod -aG wheel moby

次に、非特権ユーザに切り替えます。

$ su - moby
$ id
uid=1000(moby) gid=1000(moby) groups=1000(moby),10(wheel)

uid/gidマッピングを設定します。

$ echo "moby:100000:65536" | sudo tee /etc/subuid
$ echo "moby:100000:65536" | sudo tee /etc/subgid

ルートレスDockerのインストール

$ curl -sSL https://get.docker.com/rootless | sh

次に、初めてインストールする場合は、必要なパッケージをインストールする必要があります。

$ curl -sSL https://get.docker.com/rootless | sh
# Missing system requirements. Please run following commands to
# install the requirements and run this installer again.
# Alternatively iptables checks can be disabled with SKIP_IPTABLES=1

cat <<EOF | sudo sh -x
curl -o /etc/yum.repos.d/vbatts-shadow-utils-newxidmap-epel-7.repo https://copr.fedorainfracloud.org/coprs/vbatts/shadow-utils-newxidmap/repo/epel-7/vbatts-shadow-utils-newxidmap-epel-7.repo
yum install -y shadow-utils46-newxidmap
cat <<EOT > /etc/sysctl.d/51-rootless.conf
user.max_user_namespaces = 28633
EOT
sysctl --system
EOF

次に、必要に応じて usermode ネットワークプロトコルスタック (slirp4netns) をインストールします。このためには、yum でインストールされた slirp4netns のバージョンが古すぎて実行できないので、ソースコードから slirp4netns をビルドする必要があります。

$ sudo yum install glib2-devel
$ sudo yum group install "Development Tools"
$ git clone https://github.com/rootless-containers/slirp4netns
$ cd slirp4netns
$ ./autogen.sh
$ ./configure --prefix=/usr
$ make
$ sudo make install

Rootless Dockerをインストールすると、以下のようなプロンプトが表示されます。

$ curl -sSL https://get.docker.com/rootless | sh
# systemd not detected, dockerd daemon needs to be started manually

/home/moby/bin/dockerd-rootless.sh --experimental --storage-driver vfs

# Docker binaries are installed in /home/moby/bin
# Make sure the following environment variables are set (or add them to ~/.bashrc):\n
export XDG_RUNTIME_DIR=/tmp/docker-1000
export DOCKER_HOST=unix:///tmp/docker-1000/docker.sock

ルートレスコンテナを検証する

ルートレスコンテナを確認するには、以下の手順に従います。

まず、以下のコマンドを実行します。

$ export XDG_RUNTIME_DIR=/tmp/docker-1000
$ export DOCKER_HOST=unix:///tmp/docker-1000/docker.sock
$ /home/moby/bin/dockerd-rootless.sh --experimental --storage-driver vfs

次に、別ウィンドウで以下を実行します。

$ export XDG_RUNTIME_DIR=/tmp/docker-1000
$ export DOCKER_HOST=unix:///tmp/docker-1000/docker.sock
$ docker version
Client:
 Version:           master-dockerproject-2019-04-29
 API version:       1.40
 Go version:        go1.12.4
 Git commit:        3273c2e2
 Built:             Mon Apr 29 23:39:39 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          master-dockerproject-2019-04-29
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.4
  Git commit:       9a2c263
  Built:            Mon Apr 29 23:46:23 2019
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          v1.2.6
  GitCommit:        894b81a4b802e4eb2a91d1ce216b8817763c29fb
 runc:
  Version:          1.0.0-rc7+dev
  GitCommit:        029124da7af7360afa781a0234d1b083550f797c
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
$ docker run -d -p 8080:80 nginx
$ curl localhost:8080

iperf3を使ってネットワークパフォーマンステストを行い、サーバーを起動します。

$ docker run  -it --rm --name=iperf3-server -p 5201:5201 networkstatic/iperf3 -s

コンテナ全体のネットワーク帯域幅をテストします。

$ SERVER_IP=$(docker inspect --format "{{ .NetworkSettings.IPAddress }}" iperf3-server)
$ echo $SERVER_IP
172.17.0.2
$ docker run -it --rm networkstatic/iperf3 -c $SERVER_IP
...    
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bandwidth       Retr
[  4]   0.00-10.03  sec  29.8 GBytes  25.5 Gbits/sec    0             sender
[  4]   0.00-10.03  sec  29.8 GBytes  25.5 Gbits/sec                  receiver

コンテナとホスト間のネットワーク帯域幅(インターネット接続)をテストします。

$ HOST_IP=$(hostname --ip-address)
$ echo $HOST_IP
192.168.1.162
$ docker run -it --rm networkstatic/iperf3 -c $HOST_IP
...
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bandwidth       Retr
[  4]   0.00-10.00  sec  1011 MBytes   848 Mbits/sec    0             sender
[  4]   0.00-10.00  sec  1008 MBytes   845 Mbits/sec                  receiver

コンテナ間の通信帯域幅が比較的高いことがわかります。しかし、コンテナとホスト間の異なるネットワークネームスペースの通信帯域幅は大幅に減少しました。

結論

dockerによるルートレスコンテナの導入は、Docker/Runcコンテナのセキュリティと管理性を向上させるための大きな一歩です。この全く新しい機能により、Linuxでのセキュリティシステムの完全な再利用が可能となり、seccompやSELinuxのようなセキュリティ構成を組み合わせることで攻撃面を減らすことができます。また、コミュニティでは、root権限を必要としないKubernetesの実験版も提供しています。この実験版は https://github.com/rootless-containers/usernetes から入手できます。

しかし、付与されたルートレスコンテナは、Linuxカーネルのリスクを防ぐことはできません。また、ルートレスコンテナのネットワークやストレージの性能を最適化する必要があり、ルートレスコンテナは特定のシナリオでしか使用できません。この種のコンテナがより幅広いシナリオで利用できるように、コミュニティがコンテナのセキュリティと効率を改善し続けてくれることを期待しています。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ