Dockerコンテナは、プロセスの一種であり、コンテナホストのカーネルを共有するために、セキュリティ上のリスクがある。このコンテナの動作原理上のリスクを軽減するために、さまざなの試みが実施されている。そこで、Dockerコンテナの基本的仕組みから、コンテナの隔離性を高めるためのOSSプロジェクトを整理する。
Dockerコンテナの仕組みとセキュリティ
Dockerコンテナの仕組みの基本は、次の3点である。
- Kernel namespaces (カーネル・ネームスペース)
- Control groups (コントロール・グループ)
- Linux kernel capabilities (Linux カーネル・ケーパビリティ)
これら3点について、セキュリティの観点から確認していく。
カーネル・ネームスペース
-
docker run
を使ってコンテナを起動すると、バックグラウンドで、カーネル・ネームスペースとコントロールグループのセットを作成する。 - カーネル・ネームスペースは、プロセスの分離の最も簡単な形式を提供する。つまり、コンテナ上のプロセスが、同一ホストのプロセスやコンテナ上のプロセスを認識できなくする。
- カーネル・ネームスペースによって、各コンテナは独自のネットワークスタックを取得する。それぞれのコンテナにはIPアドレスが付与され、コンテナ間でIPトラフィックが許可される。相互にpingを実行し、UDPパケットを送受信し、TCP接続を確立できる。必要に応じて制限も可能。
- Dockerホスト上のすべてのコンテナーは、ブリッジインターフェイス上にあり、これはイーサネットを介して接続されたサーバーのようなものである。
コントロール・グループ
- コントロールグループは、リソースの使用量の集計と制限を受け持つ。
- コントロールグループは、多くの有用なメトリックを提供し、各コンテナがメモリ、CPU、ディスクI / Oの公平なシェアを確保するのにも役立つ。
- 重要なことは、コントロールグループのリソース使用制限によって、1つのコンテナが、CPUなどのリソースを使い果たして、システムダウン防止できる。そのため、サービス拒否攻撃を防ぐために不可欠。
- パブリックおよびプライベートPaaSなどのマルチテナントプラットフォームでは、一部のアプリケーションが誤動作した場合でも一貫したパフォーマンスを保証するために重要。
Linux カーネル・ケーパビリティ
- コンテナが「実際の」ルート権限をまったく必要としない、そのため、Dockerは制限された権限セットでコンテナを起動。
- コンテナ内のrootユーザーは、コンテナホストのrootユーザーよりもはるかに少ない権限で動作、以下は拒否例
- コンテナ上での mount操作を拒否
- パケットのスプーフィングを防ぐため、生のソケットへのアクセスを拒否
- 新しいデバイスノードの作成、ファイルの所有者の変更、属性(不変フラグを含む)の変更など、一部のファイルシステム操作へのアクセスを拒否
- これは侵入者がコンテナ内のrootに昇格したとしても、深刻な損害を与えたり、ホストにエスカレートすることがはるかに困難となる
これだけでは、少し解りにくいので、Linux Capability の例によって補足する。次の例は、setcap / getcap コマンドで、pingコマンドからRAWソケットの利用権限を奪う例である。setcap cap_net_raw-ep /bin/ping
は、pingコマンドから、CAP_NET_RAW の権限を -ep
によって奪う。このコマンドの実行後、pingコマンドを実行すると、ping: icmp open socket: Operation not permitted
のエラーを返すようになる。
vagrant@node1:~$ ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=63 time=1.04 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=63 time=1.28 ms
^C
--- 192.168.1.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 1.049/1.167/1.285/0.118 ms
vagrant@node1:~$ sudo setcap cap_net_raw-ep /bin/ping
vagrant@node1:~$ sudo getcap /bin/ping
/bin/ping =
root@node1:~# exit
vagrant@node1:~$ ping 192.168.1.1
ping: icmp open socket: Operation not permitted
逆に +ep
に置き換えると、RAWソケットを利用する権限を与える。ここでは、setcap/getcap というコマンドを利用したが、Dockerの実装では、Linux システムコールによって、権限の制限を実施している。このような権限の操作は、コンテナ上のアプリに影響しないが、悪意のあるユーザーによる攻撃の方法を大幅に減らす。デフォルトでは、Dockerは必要な機能以外のすべての機能を剥奪して実行する。これはブラックリストのアプローチではなくホワイトリストであり、ここに挙げられた権限を除いでコンテナは起動される。
Dockerコンテナ実行の主なリスクは、コンテナに与えられたデフォルトの機能のセットが、カーネルの脆弱性と組み合わせにおいて、不完全な分離となる可能性があること。これに対するコンテナのユーザーの対策としては、プロセスに明示的に必要な機能を除くすべての機能を削除することであり、つまり、rootユーザーで実行しないことである。
Linux Capability の参考資料
- capabilities - overview of Linux capabilities, http://manpages.ubuntu.com/manpages/bionic/man7/capabilities.7.html
- getcap, setcap and file capabilities, https://www.insecure.ws/linux/getcap_setcap.html
- Linux capabilities 101, https://linux-audit.com/linux-capabilities-101/
- Taking Advantage of Linux Capabilities, https://www.linuxjournal.com/article/5737
Dockerデーモンの攻撃対象面 (Docker daemon attack surface)
Dockerでコンテナを実行することは、Dockerデーモンの子プロセスとして、プログラムを実行することである。これを具体的に理解するために、以下は Dockerデーモン が動作する Kubernetes の ワーカーノードで ps axf
を実行した結果である。このノードでは、dockered の子プロセスとして、kube-proxy、flanneld、nginxなどのポッドが動作している事が読み取れる。
root@node1:# ps axf
1194 ? Ssl 19:12 /usr/bin/dockerd -H fd://
1413 ? Ssl 3:37 \_ docker-containerd --config /var/run/docker/containerd/containerd.toml
3116 ? Sl 0:02 \_ docker-containerd-shim -namespace moby -workdir /var/lib/docker/con
3140 ? Ss 0:00 | \_ /pause
3187 ? Sl 0:01 \_ docker-containerd-shim -namespace moby -workdir /var/lib/docker/con
3229 ? Ss 0:00 | \_ /pause
3355 ? Sl 0:02 \_ docker-containerd-shim -namespace moby -workdir /var/lib/docker/con
3401 ? Ssl 1:03 | \_ /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.c
3775 ? Sl 0:02 \_ docker-containerd-shim -namespace moby -workdir /var/lib/docker/con
3850 ? Ssl 0:38 | \_ /opt/bin/flanneld --ip-masq --kube-subnet-mgr
4670 ? Sl 0:02 \_ docker-containerd-shim -namespace moby -workdir /var/lib/docker/con
4703 ? Ss 0:00 | \_ /pause
4740 ? Sl 0:01 \_ docker-containerd-shim -namespace moby -workdir /var/lib/docker/con
4785 ? Ss 0:00 | \_ /pause
4743 ? Sl 0:01 \_ docker-containerd-shim -namespace moby -workdir /var/lib/docker/con
4806 ? Ss 0:00 | \_ /pause
5068 ? Sl 0:02 \_ docker-containerd-shim -namespace moby -workdir /var/lib/docker/con
5086 ? Ss 0:00 | \_ nginx: master process nginx -g daemon off;
5118 ? S 0:00 | \_ nginx: worker process
5124 ? Sl 0:02 \_ docker-containerd-shim -namespace moby -workdir /var/lib/docker/con
5149 ? Ss 0:00 | \_ nginx: master process nginx -g daemon off;
5177 ? S 0:00 | \_ nginx: worker process
5192 ? Sl 0:02 \_ docker-containerd-shim -namespace moby -workdir /var/lib/docker/con
5211 ? Ss 0:00 \_ nginx: master process nginx -g daemon off;
5239 ? S 0:00 \_ nginx: worker process
コンテナは、前述の3つのLinuxカーネルの機能によって、同じDockerホスト上のプロセスと分離されているが、コンテナのファイルシステムは、Dockerホスト上に存在する。そのことは次のことを意味する。
- Dockerホストとゲストコンテナーの間でディレクトリを共有
- Dockerホストからコンテナのファイルを変更可能
- コンテナは制限なしでホストファイルシステムを変更可能
この事からDockerホストに第三者の侵入を許してはいけないことが解る。これは Kubernetes のノードに対しても同様である。
Dockerdデーモンの制御権
dockerコマンドの命令は、Dockerデーモンにソケットを使って伝えられる。そのため、前述のプロセスのリストからも解るように、デフォルトでは/usr/bin/dockerd -H fd://
によって、Unixドメインのソケットを使ってコマンドを受ける。このUnixドメインの通信は、同一ホストに限定され、ネットワークからのメッセージを受け取らない。しかし、もちろん、Dockerデーモンのオプションを変更することで、INETドメインのソケットに変更することで、外部のホストからネットワーク経由によって、Dockerdを操作できるがセキュリティ上のリスクが高まるため、実施するべきではない。
一方、Kubernetesのクラスタ環境で、Dockerを利用する場合は、このリスクに対応するために Dockerd は外部からの通信を受け付けない。また、コンテナランタイムを管理する containerd に命令を与えるためのマスターノードとワーカーノードの経路にはkubeletが利用され、相互に証明書を交わすことで、通信可能な相手先を厳重に限定している。
Dockerホストのセキュリティ強化
ここまでのスタディにより、コンテナのホストのセキュリティーは、コンテナのセキュリティーを確保することにとって極めて重要である事がわかった。
IBM Cloud の Kubernetes と OpenShift などのマネージドサービスでは、コンテナが動作するKubenetes のマスターやワーカーノードへのログインが禁止されており、ノードのセキュリティ対策はIBMへ委任することになるが、クラウドのインスタンスに、Docker、 OpenShift や IBM Cloud Paks をインストールして運用する場合には、必要な知識である。
Docker Docsの中から、Dockerホストのセキュリティ強化について、以下、3点の興味深いドキュメントを挙げて要約してみたい。
- Seccomp security profiles for Docker
- AppArmor security profiles for Docker
- Get Docker EE for Red Hat Enterprise Linux
Seccomp
セキュア・コンピューティング・モード(seccomp)を使用して、コンテナ内で使用可能なLinuxシステムコールを制限できる。これはカーネルの機能であり、seccomp()システムコールの呼び出しプロセスをseccomp()に指定する状態で動作させる。それによって、Dockerは、-security-optパラメーターを使用してseccompプロファイルをコンテナーに関連付けることができる。
Dockerデフォルトのseccompプロファイルは、ホワイトリストであり、指定が無ければシステムコールへのアクセスを拒否する。そのため、ホワイトリストとして必要なシステムコールを登録する。デフォルトのseccompプロファイルは、最小限の権限でDockerコンテナを実行するためであり変更は推奨されない。
参考資料
Docker Docs, Seccomp security profiles for Docker, https://docs.docker.com/engine/security/seccomp/
AppArmor
AppArmor(アプリケーションアーマー)は、システム管理者がプログラムごとのプロファイルでプログラムの機能を制限できるようにするLinuxカーネル セキュリティモジュールである。AppArmorプロファイルを使用すると、ネットワークアクセス、RAWソケットアクセス、ファイルシステム上のファイルの読み取り、書き込み、実行の許可などの機能を許可できる。AppArmorは必須アクセス制御(MAC)により、従来のUnixの随意アクセス制御(DAC)モデルを補完する。
Dockerは、AppArmorと連携して、docker-defaultという名前のコンテナのデフォルトプロファイルを自動的に生成してロードする。このAppArmorプロファイルは、Dockerデーモンではなく、コンテナに適用される。コンテナを実行するにあたって、--security-opt apparmor=docker-default
を追加することで、コンテナにAppAprmorプロファイルが適用される。
$ docker run --rm -it --security-opt apparmor=docker-default hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete
Digest: sha256:451ce787d12369c5df2a32c85e5a03d52cbcef6eb3586dd03075f3034f10adcd
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
参考資料 AppArmor security profiles for Docker
SELinux
Security-Enhanced Linux(SELinux)は、必須アクセス制御(MAC)を含むアクセス制御セキュリティポリシーをサポートするメカニズムを提供するLinuxカーネル セキュリティモジュールである。SELinuxは、アーキテクチャは、セキュリティ決定とセキュリティポリシーの施行を分離し、セキュリティポリシーの施行に関連するソフトウェアを合理化する。SELinuxの基礎となる重要な概念は、米国国家安全保障局(NSA)によるいくつかの初期のプロジェクトにまで遡ることができる。
SELinuxを統合するLinuxカーネルは、ユーザープログラムとシステムサービス、およびファイルとネットワークリソースへのアクセスを制限する必須のアクセス制御ポリシーを適用する。この制限メカニズムは、従来のLinux(任意)アクセス制御メカニズムとは独立して動作する。 rootスーパーユーザーという概念はなく、setuid/setgidバイナリへの依存など、従来のLinuxセキュリティメカニズムのよく知られている欠点を共有しない。
Docker Enterprise Edition を RHELへ導入する場合の SELinux の設定は、次の参考資料を参照のこと。
参考資料 Get Docker EE for Red Hat Enterprise Linux
コンテナ・エンジン
コンテナ・エンジンについて、次の7つを挙げ、それぞれのセキュリティ上の特徴について書いてみる。
Dockerコンテナエンジン
Dockerコンテナエンジンは、様々なLinuxディストリビューションやWindows, macOS の環境で動作する。コア部分のContainerdは、Docker社からCNCFへ寄贈された。セキュリティ上の考慮点は前述の通りである。デフォルトでは、dockerコマンド(クライアント)の命令は、ソケットのサーバーである Dockerd が受けて、Containerdにコマンドを渡し、最終的にrunCでコンテナを実行する。
Containerd
Containerd は、Kubernetesのコンテナ・エンジンとして、デファクトスタンダード的な存在になっている。KubeletとCRI (Container Runtime Interface) に定めるインタフェースをgRPCを使って直接連携する。
Open Container Initiative(OCI)仕様に従ってコンテナを生成および実行するためのコマンドラインツールをプラグインとして利用することができる。Containerdでは、コンテナランタイムとして、runC, runhcs, Kata Containers などを利用できる。これらの詳細は後述する。また Containerdは、CNCFのプロジェクトの一つである。
CRI-O
CRI-Oは、OCIに準拠するコンテナ・イメージをサポートし、任意のコンテナレジストリからイメージを取得できる。これは、Docker、Moby、または、rkt などに変わる軽量のコンテナ・エンジンである。CRI-Oも Containerd と同様に、Kubernetes CRI(Container Runtime Interface)の実装である。そのため、Kubernetes のコンテナランタイムの Docker や Containerd の置き換えて利用できる。また、CRI-Oは、OCI(Open Container Initiative)に準拠する runC, Kata Containers などを利用できる。原則として、OCI準拠のランタイムはプラグインとして置き換えることができる。
CRI-Oは、CNCFのプロジェクトの一つであり、Red Hat、SUSE, Intel, Hyper, IBM が開発に貢献している。
gVisor
上記の Conatinerd や CRI-O では、Seccomp, AppArmor, SELinux 組み合わせて、ルールベースのセキュリティ強化をおこなう。一方、gVisor は、前述の2者とは異なるコンテナの隔離機構を提供する。次の図に表すようにgVisorは、アプリケーションすなわちコンテナ内部からのシステムコールを捕捉して、ゲストカーネルで実行する。これには仮想サーバーを利用しない。さらに、gVisorはルールベースの制御を採用して、多層防御を提供する。
gVisorは、仮想マシンとゲストカーネルの機能に加えて、Seccomp の様な役割を果たす。このアーキテクチャは、コンテナを専用仮想マシン上で実行する方式と比較して、柔軟性が増し、仮想マシンとゲストOSが消費するリソースより、遥かに少なくなる。しかし、コンテナ実行の互換性の低下とシステムコールのオーバーヘッドの増加、すなわち、パフォーマンスの劣化という代償を伴う。
rkt
CoreOSのrktは、DockerのContainerdと共に、CNCFに寄贈され、containerd プロジェクトに貢献することになった。CoreOSはRed Hatに買収された。
参考資料
- CoreOS's rkt and Docker's containerd jointly donated to CNCF, https://coreos.com/blog/rkt-container-runtime-to-the-cncf.html
- https://coreos.com/blog/rkt-accepted-into-the-cncf.html
- rkt is accepted into the CNCF, https://www.redhat.com/en/about/press-releases/
- Red Hat to Acquire CoreOS, Expanding its Kubernetes and Containers Leadership, https://www.redhat.com/en/about/press-releases/red-hat-acquire-coreos-expanding-its-kubernetes-and-containers-leadership
podman
Red Hatが主体的にコミュニティを牽引して開発を進めるもので、dockerコマンドの代わりに、podman で代用できる互換性を保っている。さらに、Dockerと異なりDockerdに相当するデーモンを必要としないデーモンレスで動作する。そして、コンテナをルートレスで起動できる。OpenShftのチュートリアルやドキュメントにdockerコマンドと併記されることが多いためQiita Dockerの置き換えを目指す ルートレス&デーモンレスの Podman 入門に調べたメモを書いた。
Moby
Mobyは、Dockerによって進められるプロジェクトであり一般向けのコンテナ・エンジンではなく、コンテナベースのシステムを構築したい人のためのライブラリやフレームワークを提供する。
コンテナ・ランタイム
コンテナ・ランタイムには、コンテナをプロセスとして実行するタイプ(runC, runhsc)と、セキュリティ強化のためコンテナ専用仮想マシンで実行して、強力な隔離を目指すもの(kata containers)がある。
- runC https://github.com/opencontainers/runc
- runhcs https://docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/containerd#hcs
- kata containers https://katacontainers.io/
runCは、OCI仕様に従ってコンテナを生成および実行するための Linux の CLIツールである。通常、runCのコマンドを、ダイレクトに実行することはしないが、runcコマンドとして利用することもできる。また、root権限以外(ルートレス)で実行する機能も備わっている。
runhcsは、マイクロソフトが開発する Windows 上でコンテナを動かすためのコンテナ・ランタイムである。これは runC から分岐したプロジェクトで、OCI (Open Container Initiative) の定めるスペックに従った コマンドライン・クライアントである。runhcsコマンドは、Windows OS 上で動作して、HCS (Host Compute Service)と連携して、コンテナの生成と管理を実行する。また、プロセスタイプのコンテナと、Hyper-Vで隔離された Windows と Linux のコンテナを実行できる。
Kata containersは、オープンソース・コミュニティ OpenStack Foundation (OSF) で開発されるセキュア・コンテナランタイムである。特徴は、コンテナを専用の軽量仮想マシン上で実行することで、コンテナを他のワークロードから強力に隔離するもので、ハードウェア仮想化機能を利用してコンテナのような軽快な感じで動作する。 サポートするCPUアーキテクチャは、AMD64, ARM, IBM pSeries, IBM zSeries, x86_64 がある。
その他、IBMで開発が試みられたコンテナランタイムを挙げておく。
runqは、KVM/Qemu仮想マシンで、Dockerイメージを実行するruncに基づくハイパーバイザーベースのDockerランタイムで、x86_64とメインフレーム s390x用に開発された。Dockerのdockerd、containerd, runcには変更がなく、runcとrunqが共存できる。
Nabla コンテナは、Unikernel として知られた ライブラリOSを利用して、7つのシステムコールだけの使用を許可する。これにより、攻撃面を減らす。
NIST アプリケーションコンテナセキュリティガイド NIST.SP.800-190
米国国立標準技術研究所(NIST:National Institute of Standards and Technology、以下、NIST と称す)によって作成され、独立行政法人 情報処理推進機構によって日本語化されたファイルが公開されている。
この中では、コンテナの使用に関連する潜在的なセキュリティ上の懸念事項を説明し、これらの懸念事項に対処するための推奨事項が記載されている。
推奨事項
- コンテナによって可能となったアプリケーションを開発、実行、サポートする新しい手段を後押しするために、組織の運用文化と技術的プロセスを調整する。
- アタックサーフェスを減らすために、汎用ホストOSではなく、コンテナ専用ホストOSを使用する。
- 単一のホストOSカーネル上で、同じ目的、機微性および脅威に対する態勢を持つグループコンテナのみを使用し、更なる多層防御を可能とする。
- コンテナ専用の脆弱性管理ツールとイメージのプロセスを採用し、侵害を防ぐ。
- トラステッドコンピューティングの基盤を提供するために、ハードウェアによる対策を検討する。
- コンテナに対応したランタイム防御ツールを使用する。
下記のそれぞれの潜在リスクを挙げ、対策方法が記述されている。
- イメージのリスク
- レジストリのリスク
- オーケストレータのリスク
- コンテナのリスク
- ホストOSのリスク
参考リンク
まとめ
今回は、コンテナの実行環境を中心に整理した。この要点と思われる部分を箇条書きにする。
- Dockerコンテナの仕組みの基本は、Kernel namespaces (カーネル・ネームスペース)、Control groups (コントロール・グループ)、Linux kernel capabilities (Linux カーネル・ケーパビリティ)
- Dockerデーモンの攻撃対象面 としては、コンテナはプロセスであり、プロセス同士は隔離されているが、リソースはDockerホスト上に存在する。そのため、コンテナホストへの侵入を許してはいけない。
- クラウドのKubernetesサービスなどでは、コンテナホストにあたる、ワーカーノードは、ユーザーのログインを禁止している。しかし、自前でKubernetesをインストールする場合は、コンテナホストに侵入されないように対策を行う必要がある。
- Dockerdデーモンのローカルホスト以外から、コマンドを受け取らないように、UNIXドメインに限定する。また、必要に応じて、Seccomp, AppArmor, SELinux を併用して、アクセスを制限してセキュリティを強化する。
- コンテナ・エンジンの gVisorは、仮想マシンを利用しないゲストカーネルで、コンテナのシステムコールを実行することで、セキュリティ上のリスクを軽減する。ただし。性能劣化と互換性の課題は発生する。
- コンテナ・ラインタイムの Kata containers は、軽量専用仮想マシン上でコンテナを実行することで、高い隔離性を確保して、セキュリティ上のリスクを軽減する。ただし、プロセスとして起動する場合と比べて起動時間は伸びると考えられる。
その他参考資料
- Dockerセキュリティ: 今すぐ役に立つテクニックから,次世代技術まで, https://www.slideshare.net/AkihiroSuda/docker-125002128
- Whose Job Is It Anyway? Kubernetes, CRI, & Container Runtimes, https://www.slideshare.net/PhilEstes/whose-job-is-it-anyway-kubernetes-cri-container-runtimes