【Dockerの最新機能を使ってみよう】Dockerコンテナで利用できるリソースや権限を制限する 3ページ

コンテナに対しSELinuxやAppArmorを使った制限を適用する

 Linuxでは、「SELinux」や「AppArmor」といったセキュリティ機構が利用できる。これらはアプリケーションが利用する各種リソースに対し、UNIX/Linuxの伝統的なアクセス制限機構であるパーミッションを超えた強制的なアクセス制限を提供するものだ。Red Hat Enterprise LinuxやCentOSなどのその互換ディストリビューションおよびFedoraではSELinuxが、UbuntuではAppArmorが採用されている。

 SELinuxでは「ラベル」という属性を各種リソースに対して指定することで、各プロセスが利用できるリソースを制限できる。また、AppArmorではプロセスに対し「ドメイン」という属性を設定し、それに対応するプロファイルと呼ばれる設定ファイル中で各種リソースに対するアクセスの可否を記述することで、各プロセスが利用できるリソースを制限できる。

 Docker 1.3.0では、このSELinuxやAppArmorをDockerで利用するためのオプションとして、「–security-opt」オプションが追加された。このオプションはdockerコンテナ内で実行されるプロセスに対し、SELinuxのラベルやAppArmorのドメインを割り当てるものだ。

 たとえば、AppArmorが有効化されているホスト上でコンテナに対し「docker_httpd」というドメインを指定するには、次のようなオプション付きで「docker run」コマンドを実行すれば良い。

--security-opt=apparmor:docker_httpd

 また、SELinuxが有効化されているホスト上では、表5の書式でコンテナに割り当てるラベルを指定できる。

表5 SELinux向けのラベル指定書式
書式説明
label:user:<ユーザーID>コンテナに指定したSELinux user IDを割り当てる
label:role:<ロール>コンテナに指定したroleを割り当てる
label:type:<タイプ>コンテナに指定したtypeを割り当てる
label:level:<レベル>コンテナに指定したlevelを割り当てる
label:disableコンテナにラベルを割り当てない

 なお、SELinuxが有効になっている環境でラベルを指定しなかった場合、コンテナには自動的に「svirt_lxc_net_t」というタイプが割り当てられる。各プロセスに割り当てられているラベルは、以下のように「-Z」オプション付きでpsコマンドを実行することで確認できる。

# docker run -ti --rm busybox

↓別のシェルで実行する
# ps uxZ
  
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 root 3570 0.0  2.9 190636 29748 pts/0 Sl+ 15:56   0:00 docker run -ti --rm busybox
  
system_u:system_r:svirt_lxc_net_t:s0:c144,c189 root 3644 0.1  0.0 1208 4 pts/1 Ss+  15:56   0:00 sh
  

 これにより、SELinuxが有効になっている環境ではデフォルトでDockerコンテナがアクセスできるリソースが制限されるようになっている。たとえば、SELinuxが有効になっている場合はコンテナから/rootディレクトリへのアクセスは行えない。次の例は、/rootディレクトリをコンテナ内の/hostrootディレクトリににマウントして操作しようとするものだが、このディレクトリに対してlsコマンドやtouchコマンドを実行すると、「Permission denied」としてアクセスが拒否されていることが分かる。

↓SELinuxのモードを確認する。「Enforcing」なら有効化されている
# getenforce
Enforcing

↓/rootディレクトリをコンテナ内の/hostrootディレクトリにマウントしてbusyboxコンテナを起動
# docker run -ti -v /root:/hostroot busybox

↓コンテナ内から/rootへのアクセスを試みる
/ # cd /hostroot/
/hostroot # ls
ls: can't open '.': Permission denied
/hostroot # touch /hostroot/foo
touch: /hostroot/foo: Permission denied

 なお、次のようにしてSELinuxを「Permissive」(権限のチェックのみ実行し、実際のアクセス制限は行わない)に設定すると、同様の操作はエラーとならずに実行できていることが分かる。

↓SELinuxのモードを「Permissive」に設定しDockerデーモンを再起動する
# setenforce 0
# getenforce
Permissive
# systemctl restart docker 

↓/rootディレクトリをコンテナ内の/hostrootディレクトリにマウントしてbusyboxコンテナを起動
# docker run -ti -v /root:/hostroot busybox

↓touchコマンドもlsコマンドも成功する
/ # touch /hostroot/foo
/ # ls /hostroot/
anaconda-ks.cfg  foo              rpmbuild

seccompによるシステムコールの呼び出し制限

 Docker 1.10では上記のSELinuxやAppArmorを使ったアクセス制御に加えて、「seccomp(Secure Computing Mode)」と呼ばれる機構を利用したセキュリティ機構が追加されている。seccompはLinuxカーネル2.6.12以降で実装されたシステムコール呼び出しを制限するための機構で、これを利用することでコンテナ内のプロセスが呼び出せるシステムコールを制限できる。

 使用中のLinuxカーネルがseccompに対応しているかどうかは、次のようにして確認できる。

cat /boot/config-`uname -r` | grep CONFIG_SECCOMP=

 ここで、「CONFIG_SECCOMP=y」と出力されればseccompが利用可能だ。

 Dockerでは、以下のように「–security-opt」オプションに「seccomp:」とともにJSON形式で利用可能なシステムコールを記述したファイルを指定できる。

--security-opt seccomp:<ファイル名>

 なお、Dockerがデフォルトで許可するシステムコールを記述した設定ファイルがGitHubで公開されているので、利用の際はこれをベースにカスタマイズを行うと良いだろう。この設定ファイル内では許可されるシステムコールが列挙されているので、禁止したいシステムコールの部分をファイル内から削除すれば良い。

 たとえば、ディレクトリを作成する「mkdir」システムコールを禁止するには、下記の部分を削除すれば良い。

{
  "name": "mkdir",
  "action": "SCMP_ACT_ALLOW",
  "args": []
},

 このように変更した設定ファイルを「–security-opt」オプションで指定して実行したコンテナ内でmkdirコマンドを実行すると、次のように「Operation not permitted」とのエラーが発生し、システムコールが実行できていないことが分かる。

# docker run -ti --security-opt seccomp:default.json busybox 
↓コンテナ内でmkdirコマンドを実行する
/ # mkdir foo
mkdir: can't create directory 'foo': Operation not permitted

 なお、Dockerがデフォルトで許可する/禁止するシステムコールについてはSeccomp security profiles for Dockerドキュメントで説明されている。デフォルトで禁止されているシステムコールはシステム管理に使われるもので、通常コンテナ内で必要とするケースは少ないが、もしこれらシステムコール制限を無効化したい場合、次のように「–security-opt」オプションを「seccomp=unconfined」という引数を付けて指定すれば良い。

--security-opt seccomp=unconfined

Dockerコンテナのセキュリティ

 Dockerのセキュリティ的な問題として、何らかの方法でコンテナ内からコンテナ外へのアクセスが可能になってしまった場合、そのアクセスがroot権限で行われてしまうという点が以前から指摘されていた。

 たとえば、root権限以外でdockerコマンドを実行させるために、Dockerへのアクセスに使用される/var/run/docker.sockファイルのグループを「docker」といったroot以外のグループに設定し、かつそのグループに読み書きアクセスを与える、というケースがある。

# chgrp docker /var/run/docker.sock
# chmod g+rw /var/run/docker.sock
# ls -l /var/run/docker.sock 
srw-rw---- 1 root docker 0 Feb  2 17:16 /var/run/docker.sock

 このような設定をおこなうと、dockerグループに所属しているユーザーがdockerコマンドを使ってコンテナを起動できるようになる。

$ id
uid=1001(hylom) gid=1001(hylom) groups=1001(hylom),113(docker)
$ docker run -ti busybox
/ # 

 しかしこの設定は、dockerグループに所有するユーザーにroot権限を与えたのと同じ事を意味する。以下のようにルートディレクトリをコンテナ内にボリュームとしてマウントすることで、Dockerホストのすべてのファイル/ディレクトリへのアクセスが可能になるからだ。

$ docker run -ti -v /:/hostroot busybox

 このようにホストのルートディレクトリをコンテナ内にマウントする行為はセキュリティ的に問題であり、意図的に行うことはないだろうが、うっかりミスでホスト上で重要なデータが格納されているディレクトリをコンテナ内にマウントしてしまう、という可能性は十分あり得る。そういった場合、SELinuxやAppArmorの設定や、User Namespacesによる設定を適切におこなっておけば、問題が発生した際の被害を最小限に抑えることが可能となる。うまく利用して、セキュリティの向上に役立てて欲しい。