Edited at

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


概要

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

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

まず最初に、ツールは以下にあります。

https://github.com/knqyf263/trivy

logo.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

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

usage1.png

usage2.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の場合に包括的な脆弱性検知が行われます。

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枚目のような感じです。

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

$ 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


その他

バイナリをここから落としても良いですし、Goの環境がある人は自分でビルドしても良いです。


精度

既存の有名な脆弱性スキャナと比較してみました。比較の図を貼ります。

比較したスキャナ: Clair, Quay, MicroScanner(Free), Docker Hub, Anchore Engine

alpine.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

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

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

centos_only_fixable.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の設定があるので、そちらを使えばよいかと思います。


オプション

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

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を見ています。ここにはセキュリティ関連のチケットも集約されているので、うまくパースすることで全てのセキュリティアップデートを把握できます。サラッと言いましたが、gitのdiffを見てバージョンがどう変わったか、などを調べたりする涙ぐましい努力があったりはします。それを語ると時間がないので今回は割愛。

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

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


まとめ

詳細はREADMEに書いてあるので見て頂けると幸いです。

https://github.com/knqyf263/trivy

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