CIで使えるコンテナの脆弱性スキャナ

# 概要
**2019/05/22 CIのcron設定について注意事項を追記**

コンテナの脆弱性スキャナを作ったので紹介します。ここでの脆弱性はWebサービスの脆弱性診断で見つかるようなタイプのものではなく、セキュリティアップデートが提供されるようなものを指しています。重ねて説明しますが脆弱性診断で見つけるような脆弱性ではなく、CVE-IDなどが付与される脆弱性です。

まず最初に、ツールは以下にあります。
https://github.com/knqyf263/trivy

![logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56222/fa4c3187-2042-43af-536e-e858efa94dfd.png)


CIで簡単に使えるように1コマンドで実行可能なものにしました。ただイメージ名を指定するだけで利用可能です。

```
$ trivy [YOUR_IMAGE_NAME]
```

これだけです。イメージ名を指定すれば勝手にレジストリから取得するので手元にイメージがある必要はなく、それもDockerコマンドに依存せず実装してあるので実はDockerのインストールも不要です。

AWS/GCR等のプライベートレジストリにも対応しています。AWSやGCPのクレデンシャルが保存されているマシンで実行すればイメージを勝手に取得するようになっています。また、手元のDockerデーモンにイメージが保存されていればそれを見るようになっているので、その場合はクレデンシャル等なくてもスキャン可能です。

**注意ですが、最初は脆弱性DBを構築するため10分以上かかります。** 2回目以降はすぐ終わるようになるので最初だけ実行したらしばらくコーヒーでも飲んでおいてください。

例えば公式イメージをスキャンする例だと以下のようになります。

```
$ trivy ruby:2.3.0
2019-05-16T23:21:11.306+0900    INFO    Updating vulnerability database...
2019-05-16T23:21:24.338+0900    INFO    Detecting Debian vulnerabilities...
ruby:2.3.0 (debian 8.4)
=======================
Total: 8137 (UNKNOWN: 6, LOW: 326, MEDIUM: 6021, HIGH: 1679, CRITICAL: 105)
+------------------------------+---------------------+----------+----------------------------+----------------------------------+-----------------------------------------------------+
|           LIBRARY            |  VULNERABILITY ID   | SEVERITY |     INSTALLED VERSION      |          FIXED VERSION           |                        TITLE                        |
+------------------------------+---------------------+----------+----------------------------+----------------------------------+-----------------------------------------------------+
| apt                          | CVE-2019-3462       | CRITICAL | 1.0.9.8.3                  | 1.0.9.8.5                        | Incorrect sanitation of the                         |
|                              |                     |          |                            |                                  | 302 redirect field in HTTP                          |
|                              |                     |          |                            |                                  | transport method of...                              |
+                              +---------------------+----------+                            +----------------------------------+-----------------------------------------------------+
|                              | CVE-2016-1252       | MEDIUM   |                            | 1.0.9.8.4                        | The apt package in Debian                           |
|                              |                     |          |                            |                                  | jessie before 1.0.9.8.4, in                         |
|                              |                     |          |                            |                                  | Debian unstable before...                           |
+                              +---------------------+----------+                            +----------------------------------+-----------------------------------------------------+
|                              | CVE-2011-3374       | LOW      |                            |                                  |                                                     |
+------------------------------+---------------------+----------+----------------------------+----------------------------------+-----------------------------------------------------+
| bash                         | CVE-2019-9924       | HIGH     | 4.3-11                     | 4.3-11+deb8u2                    | bash: BASH_CMD is writable in                       |
|                              |                     |          |                            |                                  | restricted bash shells                              |
+                              +---------------------+          +                            +----------------------------------+-----------------------------------------------------+
|                              | CVE-2016-7543       |          |                            | 4.3-11+deb8u1                    | bash: Specially crafted                             |
|                              |                     |          |                            |                                  | SHELLOPTS+PS4 variables allows                      |
|                              |                     |          |                            |                                  | command substitution                                |
+                              +---------------------+----------+                            +                                  +-----------------------------------------------------+
|                              | CVE-2016-0634       | MEDIUM   |                            |                                  | bash: Arbitrary code execution                      |
|                              |                     |          |                            |                                  | via malicious hostname                              |
+                              +---------------------+----------+                            +----------------------------------+-----------------------------------------------------+
|                              | TEMP-0841856-B18BAF | LOW      |                            |                                  |                                                     |
+                              +---------------------+          +                            +----------------------------------+-----------------------------------------------------+
|                              | CVE-2016-9401       |          |                            | 4.3-11+deb8u2                    | bash: popd controlled free                          |
+------------------------------+---------------------+----------+----------------------------+----------------------------------+----------------------------------------------------
...
```

ですがどちらかと言うと自分でビルドしたイメージに対してスキャンすることを想定しています。

動いているものを載せておきます。

![usage.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56222/79d6c885-d23a-7991-cbd7-026515f8b103.gif)

スクリーンショットは以下です

<img width="1266" alt="usage1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56222/17c1cb02-182a-75f8-5777-e67d67a51ae3.png">

<img width="1266" alt="usage2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56222/cbb34672-5549-f97b-5996-deb59150ec23.png">


# 特徴
Trivyの特徴は以下です

- 幅広い脆弱性の検知
    - OSパッケージの脆弱性とアプリケーションの依存ライブラリの脆弱性を検知
    - Red Hat Universal Base Imageにも対応！
- シンプル
    - イメージ名指定するだけで良い
- インストールが容易
    - DB等は一切不要
    - `apt-get`, `yum`, `brew`等でインストールするだけですぐに利用可能
- 高精度
    - 特にAlpine LinuxとRHEL/CentOSの検知精度が高い
- DevSecOps
    - CI/CDで利用しやすい

# 脆弱性検知
まず、TrivyはOSパッケージの脆弱性を検知します。apt-getやyumで入るパッケージに脆弱性が見つかった場合にセキュリティアップデートを適用すると思いますが、それらを自動で検知するものです。しかし、それだけではなくセキュリティアップデートがまだ提供されていないような脆弱性も検知可能になっています。それらはソフトウェアアップデートでは修正できませんが、設定の変更やファイアウォールの設定などで防げる可能性があります。

そして既存のスキャナと大きく異なる点ですが、Trivyはアプリケーションの依存ライブラリの脆弱性を検知します。例えばBundlerで入れたGemやPipenvで入れたPythonパッケージ等です。Dockerを利用している人はその中に自作のアプリケーションを入れてデプロイすることが多いと思いますが、それらの依存関係をlockファイルから引っ張ってきて自動で脆弱性検知します。つまり、以下のようなDockerfileの場合に包括的な脆弱性検知が行われます。

```Dockerfile
FROM ruby:2.6

RUN apt-get update -y && apt-get install -y build-essential nodejs
RUN gem install bundler

WORKDIR /tmp
ADD Gemfile Gemfile
ADD Gemfile.lock Gemfile.lock
RUN bundle install

ENV APP_HOME /app
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
ADD . $APP_HOME

CMD ["rails", "server", "-b", "0.0.0.0"]
```

この例でいうとDebianのパッケージマネージャーでインストールされたパッケージに関する脆弱性と、bundle installしたGemに関する脆弱性の両方が検知されます。最初に貼ったスクリーンショットにの2枚目のような感じです。

ローカルでビルドされたイメージをスキャンするのも簡単です。

```console
$ docker build -t myapp:1.2.3 .
$ trivy myapp:1.2.3 .
```

## OSパッケージ
対応しているOSは以下です。基本はパッケージマネージャでインストールされたものが対象です。自分でmake installしたものは検知されません。

| OS                           | バージョン                       | パッケージ               | 未修正の脆弱性検知 |
| ---------------------------- | ---------------------------------------- | ----------------------------- | :----------------------------------: |
| Alpine Linux                 | 2.2 - 2.7, 3.0 - 3.10                    | apkでインストールされたもの              |                  NO                  |
| Red Hat Universal Base Image | 7, 8                                     | yum/rpmでインストールされたもの          |                 YES                  |
| Red Hat Enterprise Linux     | 6, 7, 8                                  | yum/rpmでインストールされたもの          |                 YES                  |
| CentOS                       | 6, 7                                     | yum/rpmでインストールされたもの          |                 YES                  |
| Debian GNU/Linux             | wheezy, jessie, stretch, buster          | apt/apt-get/dpkgでインストールされたもの|                 YES                  |
| Ubuntu                       | 12.04, 14.04, 16.04, 18.04, 18.10, 19.04 | apt/apt-get/dpkgでインストールされたもの |                 YES                  |

## アプリケーションの依存ライブラリ
以下のファイル名を探して依存関係を検知しています。

- Gemfile.lock
- Pipfile.lock
- composer.lock
- package-lock.json
- yarn.lock
- Cargo.lock

そのため、Gemfile_web01.lock のように名前を変えている場合は現在検知されません。将来的には指定可能にするか自動判別しても良いかも、とは考えています。

# シンプル
上で述べたように単にイメージ名を指定すれば良いだけなので使うのは簡単です。それと加えて、構成もシンプルになっています。既存のスキャナはDBを用意してサーバを起動し、それからクライアント経由でスキャンを〜〜のようになっているものが多いです。Trivyはコマンドさえインストールできればもう実行可能です。あとはイメージ名を指定するだけで実行されます。

# インストール
ただバイナリを落とすだけなのでインストールも簡単です。さらにaptやyum用のリポジトリを用意してあるので、アップデートに追従するのも簡単です。

## Mac OS X / Homebrew
Macを利用している場合はHomebrewでインストールできます。

```
$ brew tap knqyf263/trivy
$ brew install knqyf263/trivy/trivy
```

## RHEL/CentOS
`/etc/yum.repos.d`にリポジトリの設定を入れるだけでOKです。

```
$ sudo vim /etc/yum.repos.d/trivy.repo
[trivy]
name=Trivy repository
baseurl=https://knqyf263.github.io/trivy-repo/rpm/releases/$releasever/$basearch/
gpgcheck=0
enabled=1
$ sudo yum -y update
$ sudo yum -y install trivy
```

or

```
$ rpm -ivh https://github.com/knqyf263/trivy/releases/download/v0.0.13/trivy_0.0.13_Linux-64bit.rpm
```

## Debian/Ubuntu

こちらもGPGの鍵を登録してからsources.listに加えればOKです。
`[CODE_NAME]` の部分は自分の使うOSに変更してください。

CODE_NAME: wheezy, jessie, stretch, buster, trusty, xenial, bionic

```
$ sudo apt-get install apt-transport-https gnupg
$ wget -qO - https://knqyf263.github.io/trivy-repo/deb/public.key | sudo apt-key add -
$ echo deb https://knqyf263.github.io/trivy-repo/deb [CODE_NAME] main | sudo tee -a /etc/apt/sources.list.d/trivy.list
$ sudo apt-get update
$ sudo apt-get install trivy
```

or

```
$ sudo apt-get install rpm
$ wget https://github.com/knqyf263/trivy/releases/download/v0.0.13/trivy_0.0.13_Linux-64bit.deb
$ sudo dpkg -i trivy_0.0.13_Linux-64bit.deb
```

## その他
バイナリを[ここ](https://github.com/knqyf263/trivy/releases)から落としても良いですし、Goの環境がある人は自分でビルドしても良いです。

# 精度
既存の有名な脆弱性スキャナと比較してみました。比較の図を貼ります。
比較したスキャナ: [Clair](https://github.com/coreos/clair), [Quay](https://quay.io/), [MicroScanner(Free)](https://github.com/aquasecurity/microscanner), [Docker Hub](https://hub.docker.com/), [Anchore Engine](https://anchore.com/engine/)

![alpine.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56222/25fa12a1-1672-d591-1fd2-45d15e25b5b8.png)



これはAlpine Linuxの例です。TrivyのTrue Positiveが一番多いことがわかります。False Positiveに関してもほぼないです。Docker Hubの脆弱性検知はやたらと雑なのでFalse Positiveだらけでした。GCRはAlpineに対応していないのか、0件でした。

なぜAlpine Linuxの精度が異なるのかについては後述しています。

詳細を見たい人のために結果を集計したスプレッドシートも置いておきます
https://docs.google.com/spreadsheets/d/16uj9vGh2PHMcVwb_D4h0nYUSvzCAxcnUz9UgQaDCYs4/edit#gid=0

そしてCentOSの比較をしたグラフは以下です。
![centos_include_unfixable.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56222/f0fedd90-d6ea-ee23-f519-c2029b53179a.png)

ここまで差があるとちょっと胡散臭いですが、理由があって既存のスキャナはRHEL/CentOSの未修正の脆弱性を検知しません。未修正の、というのはRed Hatがセキュリティアップデート/パッチを提供していない脆弱性です。しょぼい脆弱性なのでRed Hatが無視した、というものもありますが公開されて間もないためにパッチがないとかupstreamがまだ直ってない、などの危険な脆弱性も含まれます。

未修正の脆弱性を除くとほぼ等しいです。

![centos_only_fixable.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56222/7ea51658-6fb1-781d-bb4f-0d11b97b72c3.png)

# DevSecOps
何となく流行りの言葉に乗っかってみただけで特にDevSecOpsに意味はないです。CIで使いやすいようにしてあるので、イメージをpushする前にクリティカルな脆弱性があればテストを落とす、などといった利用方法が可能です。

脆弱性深刻度のフィルタが可能なのでCRITICALなものだけテストを落とし、HIGHのものは表示はするがテストは落とさない、とかも可能です。

Travis CIとCircleCIの設定例を載せておきます。本当はCircleCIのOrbs用意したりJenkinsプラグインも作ったりしたかったのですが、本体の開発に時間かけすぎたので一旦...

## Travis CI

```
$ cat .travis.yml
services:
  - docker

env:
  global:
    - COMMIT=${TRAVIS_COMMIT::8}

before_install:
  - docker build -t trivy-ci-test:${COMMIT} .
  - wget https://github.com/knqyf263/trivy/releases/download/v0.0.13/trivy_0.0.13_Linux-64bit.tar.gz
  - tar zxvf trivy_0.0.13_Linux-64bit.tar.gz
script:
  - ./trivy --exit-code 0 --severity HIGH --quiet trivy-ci-test:${COMMIT}
  - ./trivy --exit-code 1 --severity CRITICAL --quiet trivy-ci-test:${COMMIT}
cache:
  directories:
    - $HOME/.cache/trivy
```

実行例: https://travis-ci.org/knqyf263/trivy-ci-test  
リポジトリ: https://github.com/knqyf263/trivy-ci-test

## CircleCI

```
jobs:
  build:
    docker:
      - image: docker:18.09-git
    steps:
      - checkout
      - setup_remote_docker
      - restore_cache:
          key: vulnerability-db
      - run:
          name: Build image
          command: docker build -t trivy-ci-test:${CIRCLE_SHA1} .
      - run:
          name: Install trivy
          command: |
            wget https://github.com/knqyf263/trivy/releases/download/v0.0.13/trivy_0.0.13_Linux-64bit.tar.gz
            tar zxvf trivy_0.0.13_Linux-64bit.tar.gz
            mv trivy /usr/local/bin
      - run:
          name: Scan the local image with trivy
          command: trivy --exit-code 0 --quiet trivy-ci-test:${CIRCLE_SHA1}
      - save_cache:
          key: vulnerability-db
          paths:
            - $HOME/.cache/trivy
workflows:
  version: 2
  release:
    jobs:
      - build
```

実行例: https://circleci.com/gh/knqyf263/trivy-ci-test  
リポジトリ: https://github.com/knqyf263/trivy-ci-test[

## 注意
**2019/05/22 追記**
CIで簡単に回せるように、ということでExampleなど書いているのですが厳密には脆弱性が見つかるタイミングとビルドするタイミングは必ずしも一致しません。そのため、しばらくイメージをビルドしていなかったけど脆弱性が見つかったという場合に上のCI設定だけでは検知できません。

きちんと脆弱性を管理したい場合はcronを使ってdailyなどで回す必要があります。少なくともTravis CIやCircleCIにはcronの設定があるので、そちらを使えばよいかと思います。

- https://docs.travis-ci.com/user/cron-jobs/
- https://circleci.com/docs/2.0/workflows/#scheduling-a-workflow

# オプション
普通に使う分には特に難しいオプションないと思うので説明は割愛します。

```
NAME:
  trivy - A simple and comprehensive vulnerability scanner for containers
USAGE:
  main [options] image_name
VERSION:
  0.0.13
OPTIONS:
  --format value, -f value    format (table, json) (default: "table")
  --input value, -i value     input file path instead of image name
  --severity value, -s value  severities of vulnerabilities to be displayed (comma separated) (default: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL")
  --output value, -o value    output file name
  --exit-code value           Exit code when vulnerabilities were found (default: 0)
  --skip-update               skip db update
  --reset                     remove all caches and database
  --clear-cache, -c           clear image caches
  --quiet, -q                 suppress progress bar
  --ignore-unfixed            display only fixed vulnerabilities
  --refresh                   refresh DB (usually used after version update of trivy)
  --debug, -d                 debug mode
  --help, -h                  show help
  --version, -v               print the version
```

使い方は以下に色々例を載せたので見てもらえればと思います。
https://github.com/knqyf263/trivy#examples

# 他の脆弱性スキャナとの比較
他の脆弱性スキャナを使っている方はよくご存知かと思いますが、基本どれも初期セットアップが面倒です。Clairに至ってはどのクライアント使えば良いのか良く分からないぐらいです（しょっちゅう変わる）。

また、サーバ・クライアント型なものが多いのでCIで使うのが面倒です。一応使うことは可能ですが、サクッと導入可能なものではないです。

あとは上にも書きましたが検知精度が異なります。Debian/Ubuntu辺りはほとんど同じ結果になりますが、Alpine LinuxやRed Hat Universal Base Image/RHEL/CentOSあたりは大きく異なる結果になります。

Alpine Linuxの結果が異なる理由は使っている脆弱性情報の差になります。Clair等は以下のリポジトリの脆弱性を見ているのですが、これはあくまでバックポートされた脆弱性を管理しているだけで脆弱性全体ではありません。
https://github.com/alpinelinux/alpine-secdb

Alpineは結構バックポートせずに素直にバージョンを上げることも多いので、全体数に対して上のDBの数はかなり少ないです。

ではTrivyはどうやっているかというとAlpineの[Redmine](https://bugs.alpinelinux.org/projects/alpine/issues)を見ています。ここにはセキュリティ関連のチケットも集約されているので、うまくパースすることで全てのセキュリティアップデートを把握できます。サラッと言いましたが、gitのdiffを見てバージョンがどう変わったか、などを調べたりする涙ぐましい努力があったりはします。それを語ると時間がないので今回は割愛。

そして上の方にも書きましたが、Trivyはアプリケーションの依存ライブラリの脆弱性を検知可能です。他のツールではできません（Anchore Engineは一応少し検知しますが）。

QuayやGCRは使うの簡単で良いのですが、あくまでレジストリにpushされたイメージの脆弱性スキャンなので脆弱性が見つかったらテスト落とすみたいな使い方ではないかなと思います（将来的にはやりそうですが）。CIで利用可能な方が柔軟で良いと考えています。また、特定のレジストリに制限されるのも少し微妙かなと。

# まとめ
詳細はREADMEに書いてあるので見て頂けると幸いです。
https://github.com/knqyf263/trivy

CIでコンテナの脆弱性スキャンやってみたい、とか既存のスキャナは使いにくくて困っていた、などありましたら是非使ってみて何でも良いのでフィードバック頂けるととても嬉しいです。