DockerとSELinuxを組み合わせてDocker環境を強化しよう — | サイオスOSS | サイオステクノロジー

DockerとSELinuxを組み合わせてDocker環境を強化しよう

ここでは、かなり古くなってしまいましたが「DockerとSELinuxを組み合わせてDocker環境を強化しよう」を紹介します。

こんにちは。SIOS OSSエバンジェリスト/セキュリティ担当の面です。

ここでは、かなり古くなってしまいましたが「DockerとSELinuxを組み合わせてDocker環境を強化しよう」を紹介します。


SELinuxについて

SELinuxはRed Hat Enterprise Linuxなどでデフォルトで取り入れられているセキュリティ機構で、Type Enforcemntモデルとリファレンスモニタのコンセプトを用いることにより、Linuxに強制アクセス制御を導入しています。

Dockerも1.3からセキュリティ強化に、このSELinuxを使うことが出来ます。

今回はこのSELinuxをDockerに使ったときの動きを見てみます。

以下、使用する言葉として

  • コンテキスト:プロセス・ファイルに付与されているラベル。ls -lZや、ps -axZなど、Zオプションをつけることで確認できる。形式は [user_u:role_r:type_t:s0]のようになっている。

  • ドメイン:コンテキストの中で、特にTypeの箇所を”ドメイン”と呼ぶ。SELinuxでは、この”ドメイン”を使って、どのドメインがどのドメインにどんなアクセスが出来るかを記述することにより、アクセス権を設定している。

となります。

CentOS 7でのDockerとSELinux

CentOS 7ではSELinuxはデフォルトで有効になっていますので、これを使用します。

また、CentOS 7ではDockerのインストールは、最初からEPELのリポジトリにパッケージがあるので、yumを用いて

“yum –enablerepo=epel -y install docker-io”

でインストールできます。

[root@cent7 ~]# yum --enablerepo=epel -y install docker-io
--snip--
インストール:
docker.x86_64 0:1.10.3-46.el7.centos.14
依存性関連をインストールしました:
audit-libs-python.x86_64 0:2.4.1-5.el7
checkpolicy.x86_64 0:2.1.12-6.el7
docker-common.x86_64 0:1.10.3-46.el7.centos.14
docker-selinux.x86_64 0:1.10.3-46.el7.centos.14
libcgroup.x86_64 0:0.41-8.el7
libseccomp.x86_64 0:2.2.1-1.el7
libsemanage-python.x86_64 0:2.1.10-18.el7
oci-register-machine.x86_64 1:0-1.8.gitaf6c129.el7
oci-systemd-hook.x86_64 1:0.1.4-4.git41491a3.el7
policycoreutils-python.x86_64 0:2.2.5-20.el7
python-IPy.noarch 0:0.75-6.el7
setools-libs.x86_64 0:3.3.7-46.el7
yajl.x86_64 0:2.0.4-4.el7

この時、よく見てみると「docker-selinux」というパッケージも依存関係で一緒に入っていることがわかります。これはdocker用のSELinuxポリシが入っているパッケージで、これを入れることによりDockerでSELinuxオプションを使うことが可能になります。

最後にDockerをシステム起動時に有効になるようにします。

[root@cent7 ~]# systemctl start docker
[root@cent7 ~]# systemctl enable docker

DockerのプロセスのSELinuxコンテキストを確認してみると

[root@cent7 ~]# ps -axZ|grep -i docker
system_u:system_r:docker_t:s0     3133 ?        Ssl    0:07 /usr/bin/docker-current daemon --exec-opt native.cgroupdriver=systemd --selinux-enabled --log-driver=journald

となっており、”system_u:system_r:docker_t:s0″のコンテキスト(docker_tドメイン)でDockerが動作していることがわかります。

また、よく見てみるとdockerプロセスに”–selinux-enabled”というオプションが付加されており、SELinuxが有効になった状態でDockerのプロセスが起動していることがわかります。


SELinuxを有効にしてDockerコンテナを立ち上げてみる

SELinuxを有効にした状態でDockerコンテナを立ち上げるとどうなるかを見てみましょう。

テストとして、CentOS7のDocker上でcentos:latestのDockerイメージを使用し、/bin/bashでシェルを起動するようにしてみます。

まず、”docker pull centos:latest”でCentOSの最新版をDockerで使えるようにします。

[root@cent7 ~]# docker pull centos:latest
Trying to pull repository docker.io/library/centos ...
latest: Pulling from docker.io/library/centos
8d30e94188e7: Downloading 32.43 MB/70.59 MB
--snip--
8d30e94188e7: Pull complete
Digest: sha256:2ae0d2c881c7123870114fb9cc7afabd1e31f9888dac8286884f6cf59373ed9b
Status: Downloaded newer image for docker.io/centos:latest

次に、CentOS7上でroot権限で、このイメージを用いた/bin/bashシェルを起動させます。

[root@cent7 ~]# docker run -it centos:latest /bin/bash
[root@b35d6c5e945d /]#

Dockerコンテナ内でプロセスのコンテキストを確認してみる

このbashシェルの中で、”ps -axZ”として、プロセスのSELinuxコンテキストがどのようになっているかを確認してみます。

[root@b35d6c5e945d /]# ps -axZ
LABEL                              PID TTY      STAT   TIME COMMAND
system_u:system_r:svirt_lxc_net_t:s0:c162,c893 1 ? Ss   0:00 /bin/bash
system_u:system_r:svirt_lxc_net_t:s0:c162,c893 15 ? R+   0:00 ps -axZ
[root@b35d6c5e945d /]#

このように、/bin/bashプロセスに”system_u:system_r:svirt_lxc_net_t:s0:c162,c893″というコンテキストが付与されていることがわかります。

Dockerホスト側でプロセスのコンテキストを確認してみる

ホスト側でプロセスのコンテキストを確認してみると

[root@cent7 ~]# ps -axZ
--snip--
system_u:system_r:svirt_lxc_net_t:s0:c162,c893 3441 pts/1 Ss+   0:00 /bin/bash

となっており、ホスト側で見ても”system_u:system_r:svirt_lxc_net_t:s0:c162,c893″のコンテキストが/bin/bashに付与されていることがわかります。

また、このコンテキストは”pstree -Z”を用いて

 ├─docker-current(`system_u:system_r:docker_t:s0')
│  ├─bash(`system_u:system_r:svirt_lxc_net_t:s0:c162,c893')

となっており、Dockerのプロセス(docker-current)が子プロセスのbashを立ち上げる時に、”system_u:system_r:docker_t:s0″から”system_u:system_r:svirt_lxc_net_t:s0:c162,c893″へのコンテキストの遷移が行われていることがわかります。


色々な検証

次に、この”SElinuxを有効にしたDocker”がどのように振る舞うか、いくつか検証してみましょう。

1. 二つ以上のDockerコンテキストを上げる

Dockerコンテキストを二つ以上起動して、それぞれにどのようなSELinuxコンテキストが付与されるのかを見てみます。

幾つかのターミナルで、先ほどと同じく”docker run -it centos:latest /bin/bash”として/bin/bashシェルを複数起動してみます。

(1つめ)
[root@cent7 ~]# docker run -it centos:latest /bin/bash
[root@b35d6c5e945d /]# ps -axZ
LABEL                              PID TTY      STAT   TIME COMMAND
system_u:system_r:svirt_lxc_net_t:s0:c162,c893 1 ? Ss   0:00 /bin/bash
system_u:system_r:svirt_lxc_net_t:s0:c162,c893 14 ? R+   0:00 ps ax -Z
(2つめ)
[root@cent7 ~]# docker run -it centos:latest /bin/bash
[root@6ba916e81932 /]# ps -axZ
LABEL                              PID TTY      STAT   TIME COMMAND
system_u:system_r:svirt_lxc_net_t:s0:c805,c1019 1 ? Ss   0:00 /bin/bash
system_u:system_r:svirt_lxc_net_t:s0:c805,c1019 15 ? R+   0:00 ps -axZ
(3つめ)
[root@cent7 ~]# docker run -it centos:latest /bin/bash
[root@aed5484df9c6 /]# ps -axZ
LABEL                              PID TTY      STAT   TIME COMMAND
system_u:system_r:svirt_lxc_net_t:s0:c297,c417 1 ? Ss   0:00 /bin/bash
system_u:system_r:svirt_lxc_net_t:s0:c297,c417 14 ? R+   0:00 ps -axZ

ホスト側から見たそれぞれに/bin/bashのプロセスは、下記のように見えます。

[root@cent7 ~]# ps -axZ
--snip--
system_u:system_r:svirt_lxc_net_t:s0:c162,c893 3441 pts/1 Ss+   0:00 /bin/bash
system_u:system_r:svirt_lxc_net_t:s0:c297,c417 5324 pts/3 Ss+   0:00 /bin/bash
system_u:system_r:svirt_lxc_net_t:s0:c805,c1019 5468 pts/5 Ss+   0:00 /bin/bash

このように、それぞれの/bin/bashのコンテキストは”system_u:system_r:svirt_lxc_net_t:s0″までが共通で、MCSで用いられるc(カテゴリ)がそれぞれ固有に振られていることがわかります。

MCSは、過去に@ITの連載記事でも取り上げましたが、アクセス制御対象のオブジェクトにカテゴリを設定してカテゴリ別のアクセスを実現するものになります。

参照リンク:「Red Hat Enterprise Linux 5で始めるSELinux」 / 「第6回 新しく追加されたMulti Category Security」

それぞれのプロセスのカテゴリが異なっているため、万が一Dockerコンテナで脆弱性を付かれてroot権限を乗っ取られたとしても、他のコンテナに影響を及ぼすことが出来ず、結果として被害を局所化(脆弱性が発見されたコンテナのみに最小化)することが出来ます。

2. ホストのディレクトリをコンテナでマウントしてみる

ホスト上のディレクトリをコンテナでマウントして、アクセス権がどうなるかを見てみます。

Dockerコンテナ上でホストのディレクトリをマウントするには、Dockerコンテナを立ち上げる時に

[root@cent7poc /]# docker run -v [ホスト上の共有するディレクトリ]:[コンテナ上でマウントした際のディレクトリ]:[パーミッション] -it [Dockerイメージ] [プログラム]

などとします。今回はホスト上に/Testを(テストのために敢えて)777パーミッションで作成します。

[root@cent7poc /]# ls -lZ /
--snip--
drwxrwxrwx. root root unconfined_u:object_r:default_t:s0 Test

となっています。

これを/Shareとしてマウントしますので、今までと同様にdockerコンテナを立ち上げる時に

[root@cent7poc ~]# docker run -v /Test:/Test:rw -it centos:latest /bin/bash

と実行することにより

[root@5d4919fd6235 /]# ps axZ
LABEL                              PID TTY      STAT   TIME COMMAND
system_u:system_r:svirt_lxc_net_t:s0:c1,c81 1 ? Ss     0:00 /bin/bash
system_u:system_r:svirt_lxc_net_t:s0:c1,c81 14 ? R+    0:00 ps axZ
[root@5d4919fd6235 /]# ls -lZ /
drwxrwxrwx. root root unconfined_u:object_r:default_t:s0 Test
-rw-r--r--. root root system_u:object_r:svirt_sandbox_file_t:s0:c1,c81 anaconda-post.log
--snip--
[root@5d4919fd6235 /]#
[root@5d4919fd6235 /]# ls /
Test               bin  etc   lib    lost+found  mnt  proc  run   srv  tmp  var
anaconda-post.log  dev  home  lib64  media       opt  root  sbin  sys  usr
[root@5d4919fd6235 /]#

と/Testとしてホスト上のディレクトリがマウントされます。

ここで、マウントする際にrw(Read/Write)をオプションとして指定したため、コンテナ上から/Testには書き込みが出来るはずです。しかし、実際に試してみると

[root@5d4919fd6235 /]# touch /Test/testfile
touch: cannot touch '/Test/testfile': Permission denied

となり、書き込むことは出来ません。これは、上述の”ls -laZ”の結果からもわかる通りSELinuxの設定で、/Testには”unconfined_u:object_r:default_t:s0″というコンテキスト(default_tドメイン)が付加されており、/bin/bashのドメイン(svirt_lxc_net_t)がdefault_tドメインのディレクトリ・ファイルに書き込む権限がないため、書き込む行為が弾かれてしまうからです。

同様に、この状態では読み込みの権限もないため、/Testディレクトリの中をlsで覗こうとしても

[root@5d4919fd6235 /]# ls -lZ /Test
ls: cannot open directory /Test: Permission denied

と、SELinuxのパーミッション設定で弾かれてしまいます。

ここで試しに、ホストOS上で

[root@cent7poc /]# setenforce 0
[root@cent7poc /]# getenforce
Permissive 

と、SELinuxによる保護が一時的に無い状態(Permissive)にすると、

[root@5d4919fd6235 /]# ls -lZ /Test
-rw-r--r--. root root system_u:object_r:default_t:s0   testfile
[root@5d4919fd6235 /]# getenforce
Permissive

と、/Testディレクトリ内にファイル”testfile”を作成することが出来ます。この時、作成されたファイルには、親ディレクトリからコンテキストが継承され、system_u:object_r:default_t:s0(default_tドメイン)のコンテキストが付加されます。

ホストOS上のディレクトリをDocker上でマウントして読み込みや書き込みを行いたい場合には、svirt_lxc_net_tドメインがアクセスできるコンテキスト(ドメイン)を、ホストOS上のディレクトリに付与してあげることでアクセスが出来るようになります。例えば

[root@cent7poc /]# chcon -t "svirt_sandbox_file_t" /Test
[root@cent7poc /]# setenforce 1
[root@cent7poc /]# getenforce
Enforcing

のように、/Testに”svirt_sandbox_file_t”を与えてあげると、SELinuxを有効にしても

-rw-r--r--. root root system_u:object_r:svirt_sandbox_file_t:s0 testfile2

のように、/Testディレクトリ内にtestfile2を作成することが出来ます。この時、付加されるコンテキストは、親ディレクトリから継承して”system_u:object_r:svirt_sandbox_file_t:s0″になります。

ちなみに現在はSELinuxが有効(Enforcing)になっていますので、先程作成したtestfile(“system_u:object_r:default_t:s0″のコンテキスト)は、Dockerコンテナ内から/Testディレクトリを覗いた際には

[root@5d4919fd6235 /]# ls -lZ /Test
ls: cannot access /Test/testfile: Permission denied
?---------  ?    ?                                     testfile
-rw-r--r--. root root system_u:object_r:svirt_sandbox_file_t:s0 testfile2

のように、通常のパーミッション情報も取得できない状態になっています。

SELinuxをDockerと組み合わせて使用した場合には、このように大枠(ディレクトリ単位)でDocker側で見える・アクセスできる状態にしておいて、SELinuxを用いて細かいパーミッション設定を行うことが可能です。


今回の実験のまとめ

今回の実験のまとめになります。

  1. SELinuxとDockerは、CentOS/RHELのデフォルトでは組み合わせて(有効になって)立ち上がります。

  2. SELinuxとDockerを組み合わせることにより、Dockerコンテナごとに個別にSELinuxコンテキストを付与して、被害を局所化することが出来ます。

  3. ホストOSのディレクトリをDockerコンテナごとに共有する際にも、SELinuxを用いて細かい単位でパーミッション設定を行うことが出来ます。

—–

タイトルとURLをコピーしました