ytooyamaのブログ

サーバ構築とか、仕事で発見したこととか、趣味のこととかを書いています。

コンテナーとセキュリティーについて調べたのをまとめる

追記

「何が問題なのかわからない」という声があったので補足します。 Dockerの-vオプションや仕組みを理解しているユーザーやDockerを構築した人自身が使っているだけであれば、気をつけるだけでいいと思っています。

Dockerを複数人で使っているとか、Kubernetesクラスターのランタイム(CRI)としてこれらのエンジンを使った場合にも、今回取り上げたようなことをKubernetes上で実現できるので、オンプレやフルマネージドKubernetesを利用している人は気をつけないといけないでしょうという話です。どちらかというとこの問題ってrunCを使っているからなのかなと思っています。

導入

コンテナーとセキュリティーについて、現在調査をしています。 Linuxにおけるセキュリティーというと、まず思い浮かべるのがSELinuxでしたが、他にも色々存在する事がわかりました。

調べたら次のような情報を見つけました。また石川さんがごめんなさいされていますね。

blog.1q77.com

一つ目の問題

Dockerには-vオプションでDockerホストのディレクトリーパスをマウントする機能があります。 手軽にコンテナーに対して永続データの置き場所を提供できるので便利である反面、注意して使わないとセキュリティー的にもインフラの継続性的にも問題があります。

例えば先のサイトにも書かれていましたが、次のようなコマンドを実行しても拒否されることなく結果が出力されてしまいます。

[root@localhost ~]# docker run -it -v /:/rootfs fedora bash

[root@dab3405e670b /]# cat /rootfs/etc/shadow
root:!::0:99999:7:::
bin:*:18178:0:99999:7:::
daemon:*:18178:0:99999:7:::
...
[root@dab3405e670b /]# echo test >> /rootfs/etc/passwd
[root@dab3405e670b /]# 

これは、Dockerホストの / をコンテナーの/rootfsにマウントして、コンテナー内部からDockerホストのファイルを参照したり、書き換えるということを認証もなく実行できてしまう...という問題です。コンテナーはVMと違ってカーネルを共用していますし、コンテナーはrootユーザーで動いているからできる芸当ですね。

RHEL DockerやPodmanでは

SELinuxが有効な状態でRHEL DockerもしくはPodman、CRI-Oなどを使っていると、上記のような問題を制限できます。試しに同じコマンドをFedoraとPodmanで実行してみましょう。次のようにPermission deniedと表示され、コマンドの実行を拒否されます。

[root@localhost ~]# podman run -it -v /:/rootfs fedora bash

[root@1365e5cc2291 /]# cat /rootfs/etc/shadow
cat: /rootfs/etc/shadow: Permission denied
[root@1365e5cc2291 /]# echo test >> /rootfs/etc/passwd
bash: /rootfs/etc/passwd: Permission denied

RHEL DockerとはRHEL 7、CentOS 7、過去のバージョンのFedoraで提供されていたDocker 1.13.1までのバージョンのことです。その名の通りRed Hatがメンテナンスをしていたバージョンです。ちなみにDocker CEではSELinuxによる保護は限定的です。

Docker CEでSELinux Supportを有効にする

Docker CE 19.03.5ではSELinux Supportを有効にすることで保護が可能です。 デフォルトは無効にされています。 daemon.json に以下のように追記してdockerサービスを再起動してください。以降は有効になります。

利用するには事前にcontainer-selinuxパッケージの導入が必要です。

{
    "seccomp-profile": "/root/custom-seccomp.json",
    "selinux-enabled": true
}

daemon.jsonはファイル名が示すように、jsonの形式で設定を記述していきます。jsonなれしている人なら大して苦労しないと思うのですが、初心者だったらここに設定を書いて設定を適用するだけでも苦労すると思います。カッコの綴じ方と複数設定を記述するときに最後の設定以外にはカンマがつくところとか、まだ設定が少ないうちはいいけど、daemon.jsonにいろいろな設定を記述していくつ複雑になってよくわからなくなるので、Podmanのほうが好きです。私は。

ちなみに現時点でDocker CEはCentOS8をサポートしていません。もしDockerが使いたいのではなくてコンテナーが使いたいのであれば、Podmanをおすすめします。 また、現時点のDockerはcgroup v2をサポートしていません。Fedora 31で使うには次のようにcgroup v2をオフにする必要があります。

medium.com

Podmanはcgroup v2環境でも(もちろんv1環境でも)動作します。podman-dockerというパッケージを追加インストールすると、dockerコマンドを叩いてもよきにはからってpodmanに置き換えて実行してくれます。長くDockerに対して言われていた「dockerdみたいなデーモンがコンテナーが実行されているか否かに関わらず実行され続ける」問題もPodmanにはありません。なぜならPodmanはコンテナーしか動かないからです( systemctl start podman とかやろうとしてもサービスは見つかりません)。

もう一つの問題

SELinuxを使うとある程度、コンテナーのセキュリティインシデントに対して防御できることがわかりましたが、今日はここで終わりではありません。SELinuxを有効にした状態でも防げない問題も存在します。次をご覧ください。

[root@localhost ~]# docker run -v /:/rootfs -it fedora bash

[root@0336f1e252ac /]# chroot /rootfs
sh-5.0# do
do            docker-init   dockerd       done          dosfsck       
docker        docker-proxy  domainname    dos2unix      dosfslabel    
sh-5.0# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS              PORTS               NAMES
0336f1e252ac        fedora              "bash"              About a minute ago   Up About a minute                       modest_lalande

これは何をやっているかというと、コンテナーでchrootしてコンテナーから抜けるというチャレンジをしてみました。これもとくに認証されずに実行できてしまいます。

コマンドの3行目でコンテナーから抜けています。そのあと、ホスト上でdockerコマンドを実行してみました。抜けてきたコンテナーが実行されていることがわかります。ちなみに「exit」を実行するとコンテナーに戻る事が可能です。悪意のあるユーザーやソフトウェアが含まれたコンテナーにより、ホスト上でコード実行される危険性があるということです。これに対する対策が必要であることがわかりました。

seccomp(secure computing mode)を使おう

Dockerはseccomp(secure computing mode)という、Linuxカーネル上でアプリケーションのサンドボックスメカニズムを提供するためのセキュアコンピューティングの機能をサポートしています。次が公式のドキュメントです。

docs.docker.com

seccompは多くの他のコンテナーランタイムでもサポートしています。ある程度のSyscallはコンテナー内で実行された場合にブロックしてくれます。しかし、デフォルトの設定ではchrootは許可されているようです。Dockerのデフォルトの定義は先のドキュメントにも書かれていますがGitHubに公開されています。

github.com

Dockerではdocker run実行時に--cap-add、--cap-dropを使って特定の機能を許可する、拒否することが可能です。 以下は実行例です。dockerコマンドをpodmanに差し替えても同じ結果になります。

[root@localhost ~]# docker run -v /:/rootfs --cap-drop SYS_CHROOT -it fedora /bin/bash

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

これは次のようにjson定義ファイルを指定して実行することもできます。同様にchrootを拒否できました。dockerコマンドをpodmanに差し替えても同じ結果になります。

[root@localhost ~]# docker run --security-opt seccomp=/root/custom-seccomp.json -v /:/rootfs -it fedora bash

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

上記の実行例は660行目から673行目を取り除いたファイルを使っています。

[2/9追記] カスタムseccompプロファイルを常に利用する

毎回docker runもしくはpodman run実行のたびにseccompプロファイルを指定するのは大変ですからデフォルトの設定にしたいと思うかもしれません。 Dockerの場合はdaemon.jsonに次のように記述すると対応可能です。その他の設定についてはリファレンスをご覧ください。

{   
    "seccomp-profile": "/root/custom-seccomp.json"
}

Podmanについてはデフォルトのseccompプロファイルが読み込まれた状態になっていますので、これを編集すると編集後に作成したコンテナーは保護されます。/usr/share/containers/seccomp.json にデフォルトのプロファイルがあります。Podmanの設定はいくつかあるので公式のガイドをご覧ください。

rootlessの試み

SELinuxとseccompを組み合わせて使えば、オペミスや問題のあるコンテナーイメージがあったとしても、一定の問題を回避できることがわかりました。しかし、LinuxのすべてのディストリビューションでSELinuxが使えるわけではありませんから、その場合は別の対策が必要になります。

べつの観点からコンテナーのセキュリティーを観測してみると、roorlessというキーワードが見つかります。これはその名が示すとおり、rootユーザーを使わないでコンテナーと関連するサービスを実行するという試みです。

例えばDockerはrootlessで動くDockerを開発、提供していますし、Red Hat Enterprise Linux 8.1に含まれるPodmanは制限はあるものの、rootlessでの実行をサポートしています。Kubernetesもusernetesという、root権限を必要せずに動くKubernetesを使えるようにしようという試みが行われています。

最後に

SELinuxが有効な環境で正規な理由でホストパスをマウントしたい場合は次のように実行します。 次の例はPodmanで実行した例ですが、同様にDockerでも可能です。パスの最後にzを付加しています。このパスには読み書きが可能です。

# podman run -v /root/data:/mydata:z -it fedora /bin/bash

ここらへんについては次の情報を参考にしてください。

qiita.com

参考情報

Red Hat Enterprise Linux Atomic Host 7 コンテナーセキュリティガイド

Seccomp security profiles for Docker

その他の情報

このブログサイトはJavaScriptを使っていますが、読み込んでいるJavaScriptは全てはてなが提供しているものであり、筆者が設置しているものではありません。