Dockerコンテナを本番環境で使うためのセキュリティ設定

#はじめに
Dockerを開発環境で使うことが多くなってきてますね。
使い捨てできる環境は本当に便利なので、本番環境にも使いたいなーと思って、本番運用で注意すべきセキュリティ周りを調べてみました。

#基本的な考え方
基本的な考え方は以下になります。

1. コンテナ内部に入られるな
1. 権限は最小限にせよ
1. 監視を怠るな

DockerといえどVPSやオンプレのセキュリティ設定と考え方は同じですね。
ここではDockerにまつわる話を書いていきます。

# コンテナ内部に入られるな
## 信頼できるイメージを使う
多くの場合、ベースとなるピュアなOSイメージはDockerHub上のイメージを使いますが、元となるイメージがセキュアであるかどうかを確認して使うようにしましょう。
既知の脆弱性を含んでいる場合や、最悪の場合、悪意のあるスクリプトが仕込まれている場合があります。
既知の脆弱性が含まれているかどうかは[Docker Security Scanning](https://docs.docker.com/docker-cloud/builds/image-scan/)によって可視化されており、各イメージのTagsビューで確認が可能です。

例）[Mongoイメージの脆弱性診断の結果](https://hub.docker.com/r/library/mongo/tags/)

![image](https://qiita-image-store.s3.amazonaws.com/0/119359/15532c88-e853-77f0-d168-7ddaa0f83848.png)

## イメージの正しさを証明する
Dockerにはcontent trustという機能があり、証明書を使って、イメージの発行元の改ざんを検知する方法があります。
デフォルトではこの機能はオフになっており、設定しておくことをオススメします。

```bash
export DOCKER_CONTENT_TRUST=1
```
[【参考訳】 Docker Content Trust 入門](http://pocketstudio.jp/log3/2015/08/14/content-trust-docker-1-8-ja/)

## コンテナ間通信を閉じる
Dockerのデフォルトの設定では外部にポートは開かれていませんが、同一ネットワーク内の全てのコンテナは相互通信が可能です。
多くのケースでは特定のコンテナのみ通信できていれば問題ないため、この機能をオフにしておくことをオススメします。
コンテナ間通信はDocker Daemonのiccオプションをfalseにすることで可能です。

##### docker-machineを利用している場合（OSX, Windows）

```bash
docker-machine ssh default "echo $'EXTRA_ARGS=\"--icc=false\"' | sudo tee -a /var/lib/boot2docker/profile && sudo /etc/init.d/docker restart"
```

明示的にコンテナ間の接続を許可する場合は、docker-compose.ymlの```links:```argumentやdocker runの```--link```optionを使って必要なコンテナのみリンクさせるようにしましょう。

# 権限は最小限にせよ
## 更新権限を剥奪する
```docker run```の```-v```optionやdocker-compose.ymlの```volumes:```でファイルをマウントする場合、```:ro```の記述を行うことでコンテナ内の権限をRead-onlyにすることができます。

##### docker run の場合
```bash
docker run -v .:/go/src/app:ro [container id]
```

##### docker-compose の場合
```Docker:docker-compose.yml
app:
  build: .
  volumes:
    - ".:/go/src/app:ro"
```


ボリュームは複数指定が可能なので更新可能なフォルダとそれ以外で分離させることをオススメします。

## root権限を剥奪する
多くの公式イメージはrootユーザで動作します。
この状態で、もしコンテナ内に侵入を許した場合、あらゆる権限で操作が可能な上、コンテナ内のrootユーザとホストのrootユーザはuidが一緒のため、ホスト側もroot権限でアクセスが可能になります。

rootで動作が必要なケースは、せいぜいイメージのビルド時かと思うので、root以外のユーザを作成し、アプリケーションを実行することを忘れないでください。

```Docker:Dockerfile
# Golang Image
FROM golang:onbuild

# install requirements
RUN go get github.com/pilu/fresh

# switch execute user
RUN useradd golanguser
USER golanguser

# EXPOSE used port
EXPOSE 5000
```

# 監視を怠るな
## ログを集約する
Dockerの場合、アプリケーションログやアクセスログをファイル出力するのは理想的ではなく、標準出力や標準エラーへ出力するように設定しておきましょう。
ホスト側で```docker logs [CONATINER ID]```や```docker-compose logs```でログが拾えるようになります。

ただしホストが複数にまたがる場合、分散してしまうので、ログを集約するサービスを利用するとよいです。
代表されるサービスには[Graylog](https://www.graylog.org/), [Logstash](https://www.elastic.co/products/logstash), [Fluentd](http://www.fluentd.org/)があります。
不正なプロセスやリクエストが飛んでいないか監視できるようにしておきましょう。

## コンテナをモニタリングする
以下のようなコマンドをホストで実行することで、コンテナが使用しているリソース量を監視できます。
特定のコンテナで異常な数値が出ていないか監視できるようにしておきましょう。

```bash
> docker stats $(docker inspect -f {{.NAME}} $(docker ps -q))

CONTAINER           CPU %               MEM USAGE / LIMIT       MEM %               NET I/O               BLOCK I/O           PIDS
94ff079947e3        0.00%               9.438 MiB / 995.8 MiB   0.95%               61.92 kB / 50.82 kB   0 B / 8.192 kB      9
77ca0ba03e47        0.09%               6.309 MiB / 995.8 MiB   0.63%               39.96 kB / 34.87 kB   0 B / 8.192 kB      3
```

ただしこの手法の場合、ホストが複数ある場合にモニタリングが分散してしまいます。
複数のホストで動くコンテナを一括で監視したい場合は、[NewRelic](https://newrelic.com/), [datadog](https://www.datadoghq.com/lpg/?utm_source=Advertisement&utm_medium=GoogleAdsNon1stTierBrand&utm_campaign=GoogleAdsNon1stTierBrand-NonENES&utm_content=Datadog&utm_keyword=datadog&utm_matchtype=e&gclid=CjwKEAiAyO_BBRDOgM-K8MGWpmYSJACePQ9C3g0AqOxQ70wonZW1VMKgyQj2aLnvl6wboVUQGYWWvRoCUgrw_wcB), [Mackerel](https://mackerel.io/ja/?gclid=CjwKEAiAyO_BBRDOgM-K8MGWpmYSJACePQ9CUovjG5N5Ni95GjzYRugqsgIe60Skq45iKHMWFdeLqhoCtrDw_wcB)等でDocker向けソリューションがあります。


# 番外編：インシデント発生時の対応
万全を期しても脅威にさらされるケースに備えておきましょう。
コンテナに異常が発生している場合は、すぐにコンテナを停止させ、各種ログのチェックや```docker diff [CONTAINER ID]```で元のイメージから変更が加えられていないかを確認しましょう。
大切なのは、外部からの攻撃なのか、内部に侵入を許したのかを確認することです。
すぐに判断できるようインシデント発生時の対応は確立させておくと安心ですね。