これは、なにをしたくて書いたもの?
- Dockerイメージの脆弱性スキャンを実行できるツールについて、ちょっと調べてみて
- Clairというものが良さそうだったので、まずはこちらを試してみようと
GitHub - coreos/clair: Vulnerability Static Analysis for Containers
Clairとは?
CoreOS社が開発している、コンテナイメージの脆弱性スキャンツールです。
GitHub - coreos/clair: Vulnerability Static Analysis for Containers
以下のような、特徴を持ちます。
- Clairに設定された一連のデータソースから、脆弱性に関するメタデータをデータベースに取り込む
- データソースは複数設定可能で、メタデータは定期的に更新される
- APIでスキャンを実行でき、またWebhookの機能も備える(CI/CDで利用)
ビルトインされているデータソースには、以下のものがあります。
Data Sources for the built-in drivers
Debian Security Bug Tracker、Ubuntu CVE Tracker、Red Hat Security Data、Oracle Linux Security Data、SUSE OVAL Descriptions、
Alpine SecDB、NIST NVDですね。
基本的な用語は、こちらを参照。
clair/terminology.md at master · coreos/clair · GitHub
clair/drivers-and-data-sources.md at master · coreos/clair · GitHub
参考)
10+ top open-source tools for Docker security | TechBeacon
Dockerイメージの脆弱性診断をPortus & Clairで - Speaker Deck
Clair によるコンテナ・イメージの脆弱性検出 第1回 | オブジェクトの広場
Clair によるコンテナ・イメージの脆弱性検出 第2回 | オブジェクトの広場
Clair によるコンテナ・イメージの脆弱性検出 第3回 | オブジェクトの広場
Clairを起動してみる
では、まずはClairを起動してみましょう。
手順は、こちら。
clair/running-clair.md at master · coreos/clair · GitHub
Clair自体はDockerイメージが提供されているようなので、今回はDocker Composeを使って起動する方法を取ってみます。
なお、データベースとしてはPostgreSQLが必要です(設定ファイルの構文的には、他のものも書けそうな雰囲気があるものの、
現状はPostgreSQLに対する実装しかありません)。
手順に従って、docker-compose.ymlをダウンロード。
$ wget https://raw.githubusercontent.com/coreos/clair/master/contrib/compose/docker-compose.yml
中を見ると、割とlatest指定が多く…。
version: '2' services: postgres: container_name: clair_postgres image: postgres:latest restart: unless-stopped environment: POSTGRES_PASSWORD: password clair: container_name: clair_clair image: quay.io/coreos/clair-git:latest restart: unless-stopped depends_on: - postgres ports: - "6060-6061:6060-6061" links: - postgres volumes: - /tmp:/tmp - ./clair_config:/config command: [-config, /config/config.yaml]
というか、latest(とmaster)しかない?
quay.io / coreos / clair-git / tags
ここで、提供されているイメージの種類に関する説明を読むと、ちゃんと書いてありました。
Official Container Repositories
- clair … Stable releases
- clair-jwt … Stable releases with an embedded instance of jwtproxy
- clair-git … Development releases
clair-gitは、開発中のものですと…。だから、latestしかないんですね…。
今回は、clair:v2.0.7を使うことにします。
quay.io / coreos / clair / tags
修正後のdocker-compose.yml
version: '2' services: postgres: container_name: clair_postgres image: postgres:11.1 restart: unless-stopped environment: POSTGRES_PASSWORD: password clair: container_name: clair_clair image: quay.io/coreos/clair:v2.0.7 restart: unless-stopped depends_on: - postgres ports: - "6060-6061:6060-6061" links: - postgres volumes: - ./:/etc/clair/ # command: [-config, /etc/clair/config.yaml] ## default "/etc/clair/config.yaml"
volumesでカレントディレクトリを「/etc/clair」にマウントするようにしていますが、これはClairの起動には設定ファイルが
必要で、デフォルトの読み込み先は「/etc/clair」だからです(「-config」オプションで指定することもできます)。
$ docker container run -it --rm --name clair quay.io/coreos/clair:v2.0.7 -h Usage of /clair: -config string Load configuration from the specified file. (default "/etc/clair/config.yaml") -cpu-profile string Write a CPU profile to the specified file before exiting. -insecure-tls Disable TLS server's certificate chain and hostname verification when pulling layers. -log-level string Define the logging level. (default "info")
というわけで、一緒に設定ファイルが必要なので、合わせて取得。
$ wget https://raw.githubusercontent.com/coreos/clair/master/config.yaml.sample -O config.yaml
こちらも、ドキュメントに習って修正。PostgreSQLへの接続設定(「source」)を変える必要があります。
config.yaml
clair: database: # Database driver type: pgsql options: # PostgreSQL Connection string # https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING source: postgresql://postgres:password@postgres:5432?sslmode=disable&statement_timeout=60000 cachesize: 16384 paginationkey: api: addr: "0.0.0.0:6060" # Health server address # This is an unencrypted endpoint useful for load balancers to check to healthiness of the clair server. healthaddr: "0.0.0.0:6061" # Deadline before an API request will respond with a 503 timeout: 900s # Optional PKI configuration # If you want to easily generate client certificates and CAs, try the following projects: # https://github.com/coreos/etcd-ca # https://github.com/cloudflare/cfssl servername: cafile: keyfile: certfile: updater: # Frequency the database will be updated with vulnerabilities from the default data sources # The value 0 disables the updater entirely. interval: 2h enabledupdaters: - debian - ubuntu - rhel - oracle - alpine notifier: # Number of attempts before the notification is marked as failed to be sent attempts: 3 # Duration before a failed notification is retried renotifyinterval: 2h http: # Optional endpoint that will receive notifications via POST requests endpoint: # Optional PKI configuration # If you want to easily generate client certificates and CAs, try the following projects: # https://github.com/cloudflare/cfssl # https://github.com/coreos/etcd-ca servername: cafile: keyfile: certfile: # Optional HTTP Proxy: must be a valid URL (including the scheme). proxy:
設定ファイルの内容は、以下の内容で構成されているようですね。
- 脆弱性情報などを格納するデータベース(PostgreSQL)に関する設定
- Clairとして提供するAPIに関する設定
- 脆弱性情報のデータソースと、その更新頻度に関する設定
- Webhookに関する設定
APIとしての設定は、こちらですね。Clairを、6060ポートをリッスンさせるように起動していることがわかります。
api: addr: "0.0.0.0:6060" # Health server address # This is an unencrypted endpoint useful for load balancers to check to healthiness of the clair server. healthaddr: "0.0.0.0:6061" # Deadline before an API request will respond with a 503 timeout: 900s # Optional PKI configuration # If you want to easily generate client certificates and CAs, try the following projects: # https://github.com/coreos/etcd-ca # https://github.com/cloudflare/cfssl servername: cafile: keyfile: certfile:
この「enabledupdaters」で指定されているのが脆弱性に関するデータソースなようですが、
updater: # Frequency the database will be updated with vulnerabilities from the default data sources # The value 0 disables the updater entirely. interval: 2h enabledupdaters: - debian - ubuntu - rhel - oracle - alpine
指定できそうなのはこのあたりでしょうかね。
https://github.com/coreos/clair/tree/master/ext/vulnsrc
NIST NVDがないのでは?と思うかもしれませんが、こちらはデータソースからの情報取得後、追加情報として収集されるようです…。
https://github.com/coreos/clair/tree/master/ext/vulnmdsrc
更新間隔は、今回はサンプルのままなので2時間…。
起動。
$ docker-compose up
起動すると、データソースからの情報の取得が始まります。
これにはけっこう時間がかかり、次のようなログが出力されると完了、となります(update finished)。
clair_1 | {"Event":"finished fetching","Level":"info","Location":"updater.go:242","Time":"2019-01-12 09:54:32.348079","updater name":"alpine"} clair_1 | {"Event":"finished fetching","Level":"info","Location":"updater.go:242","Time":"2019-01-12 09:54:33.349210","updater name":"debian"} clair_1 | {"Event":"could not parse package version. skipping","Level":"warning","Location":"ubuntu.go:316","Time":"2019-01-12 09:55:47.918274","error":"invalid version","version":"0.27-1+deb7u1build0.12.04.1, 0.28-1+deb8u1"} clair_1 | {"Event":"finished fetching","Level":"info","Location":"updater.go:242","Time":"2019-01-12 09:55:48.706536","updater name":"ubuntu"} clair_1 | {"Event":"finished fetching","Level":"info","Location":"updater.go:242","Time":"2019-01-12 10:03:25.997211","updater name":"oracle"} clair_1 | {"Event":"finished fetching","Level":"info","Location":"updater.go:242","Time":"2019-01-12 10:04:31.010574","updater name":"rhel"} clair_1 | {"Event":"adding metadata to vulnerabilities","Level":"info","Location":"updater.go:268","Time":"2019-01-12 10:04:31.023454"} clair_1 | {"Event":"update finished","Level":"info","Location":"updater.go:213","Time":"2019-01-12 10:27:41.691657"}
つまり、10分くらい待とう、と。
そこまで経過したら、準備は完了となります。
ところで、関連するGitHubリソースのリンク先にずっとmasterブランチを指定しているのが微妙なのですが、最近ディレクトリ構成が
変わったようで、最新安定版のタグだと今のmasterと全然違う形なので、まあ…参考程度に…くらいで…。
あと、ClairのDockerイメージが置かれていたQuay.io。こちらもDocker Hubと同じくコンテナのレジストリサービスなのですが、
CVEを元にしたセキュリティスキャンも行ってくれるサービスのようです。
無料プランでも、セキュリティスキャンは利用できるのだとか。
確認してみる
それでは、コンテナイメージをスキャンして確認してみたいと思います。
スキャン方法なのですが、以前は「analyze-local-images」というツールが使われていたようなのですが、今は非推奨と
なっています。
GitHub - coreos/analyze-local-images: deprecated tool for interacting with Clair locally
ではどうするか?ですが、今はclairctlかClair scannerを使うようです。
Clair Usage - deprecated · Issue #1 · coreos/analyze-local-images · GitHub
security - What tool is able to analyze images by connecting to clair? - DevOps Stack Exchange
今回は、Clair scannerを使ってみます。
GitHub - arminc/clair-scanner: Docker containers vulnerability scan
README.mdを見ていると、自分でビルドしなくてはいけない雰囲気を感じますが、releasesからビルド済みバイナリを取得
できるので、今回はこちらを使用します。
$ wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64 -O clair-scanner $ chmod a+x clair-scanner
Clair scannerは、ローカルのDockerイメージの情報をClairに送り、Clairから結果を受け取ることで脆弱性スキャン結果を
表示します。
Dockerイメージは、Clair scannerが動作するローカル環境にある必要があります。ちなみに、Clairが動作している環境には、
Dockerイメージが存在する必要はありません。
Clairから結果を受け取るという話に戻すと、ローカル環境はIPアドレスが192.168.0.2なのですが、こちらに対して
Docker Compose上で動作しているClairから脆弱性結果が送信されるイメージになります。
Clair scannerからClairに対して、イメージの情報を送信するのですが、このレスポンスにスキャン結果があるわけではなく、
あくまでClairからHTTPリクエストが送信されてきます。
このため、Clair scannerは短命ですが、Clairから脆弱性スキャン結果を受け取るサーバーにもなります。
ヘルプ。
$ ./clair-scanner Error: incorrect usage Usage: clair-scanner [OPTIONS] IMAGE Scan local Docker images for vulnerabilities with Clair Arguments: IMAGE="" Name of the Docker image to scan Options: -w, --whitelist="" Path to the whitelist file -t, --threshold="Unknown" CVE severity threshold. Valid values; 'Defcon1', 'Critical', 'High', 'Medium', 'Low', 'Negligible', 'Unknown' -c, --clair="http://127.0.0.1:6060" Clair URL --ip="localhost" IP address where clair-scanner is running on -l, --log="" Log to a file --all, --reportAll=true Display all vulnerabilities, even if they are approved -r, --report="" Report output file, as JSON
「-c」でClairに対するURL、「--ip」でClair scannerがどのIPアドレスでバインドさせるかを指定します。
「--ip」で指定されたIPアドレスに対して、Clairから結果が送信されてきます。デフォルトは「localhost」なので、今回のように
ClairをDockerコンテナ上で動かしていると、結果の受け取り先が見つからずに実行に失敗します。
今回は、こんな感じの指定になります。「-c」で指定しているのは、Docker Compose上のClairのIPアドレスです。
$ ./clair-scanner -c http://172.19.0.3:6060 --ip=192.168.0.2 [Dockerイメージ]
あと、「-w」でホワイトリストの指定もできるようですが、今回はパス…。
サンプルは、このあたりです。
https://github.com/arminc/clair-scanner/blob/master/example-alpine.yaml
https://github.com/arminc/clair-scanner/blob/master/example-whitelist.yaml
試しに、ApacheのDockerイメージに対して、スキャンを行ってみましょう。
まずは、イメージをpull。
$ docker pull httpd:2.4.37
実行。
$ ./clair-scanner -c http://172.19.0.3:6060 --ip=192.168.0.2 httpd:2.4.37 2019/01/12 22:26:16 [INFO] ▶ Start clair-scanner 2019/01/12 22:26:23 [INFO] ▶ Server listening on port 9279 2019/01/12 22:26:23 [INFO] ▶ Analyzing 4101911ad4bd7142bc97f3d255c3e5a2ad202246ae5a9f0e3b712865198e2fe3 2019/01/12 22:26:23 [INFO] ▶ Analyzing d7cffd99b16a032223434d27ac88ef1425ef6d86067d81768e78964168782a29 2019/01/12 22:26:23 [INFO] ▶ Analyzing f7081d1ca73a53dc13351dcb12539a49fd97713e8647780734b31150d315d293 2019/01/12 22:26:23 [INFO] ▶ Analyzing 0eadcd43b848bf964f4d0166ab7a96b8f9d740f2b6a96249a48565f162d740ca 2019/01/12 22:26:23 [INFO] ▶ Analyzing e109fadbe58ded0d1013edfa38040d50d0078a7ec1d47ea016e3c8222e7f1b0f 2019/01/12 22:26:23 [WARN] ▶ Image [httpd:2.4.37] contains 146 total vulnerabilities
結果は、こんな感じで表示されます(大量に…)。
2019/01/12 22:26:23 [ERRO] ▶ Image [httpd:2.4.37] contains 146 unapproved vulnerabilities +------------+-----------------------------+--------------+------------------------+--------------------------------------------------------------+ | STATUS | CVE SEVERITY | PACKAGE NAME | PACKAGE VERSION | CVE DESCRIPTION | +------------+-----------------------------+--------------+------------------------+--------------------------------------------------------------+ | Unapproved | High CVE-2018-14614 | linux | 4.9.130-2 | An issue was discovered in the Linux kernel | | | | | | through 4.17.10. There is an out-of-bounds | | | | | | access in __remove_dirty_segment() in | | | | | | fs/f2fs/segment.c when mounting an f2fs image. | | | | | | https://security-tracker.debian.org/tracker/CVE-2018-14614 | +------------+-----------------------------+--------------+------------------------+--------------------------------------------------------------+ | Unapproved | High CVE-2018-14610 | linux | 4.9.130-2 | An issue was discovered in the Linux kernel | | | | | | through 4.17.10. There is out-of-bounds access | | | | | | in write_extent_buffer() when mounting and | | | | | | operating a crafted btrfs image, because of a | | | | | | lack of verification that each block group has | | | | | | a corresponding chunk at mount time, within | | | | | | btrfs_read_block_groups in fs/btrfs/extent-tree.c. | | | | | | https://security-tracker.debian.org/tracker/CVE-2018-14610 | +------------+-----------------------------+--------------+------------------------+--------------------------------------------------------------+ | Unapproved | High CVE-2018-9517 | linux | 4.9.130-2 | In pppol2tp_connect, there is possible memory | | | | | | corruption due to a use after free. This could | | | | | | lead to local escalation of privilege with System | | | | | | execution privileges needed. User interaction is | | | | | | not needed for exploitation. Product: Android. | | | | | | Versions: Android kernel. Android ID: A-38159931. | | | | | | https://security-tracker.debian.org/tracker/CVE-2018-9517 | +------------+-----------------------------+--------------+------------------------+--------------------------------------------------------------+ | Unapproved | High CVE-2017-1000379 | linux | 4.9.130-2 | The Linux Kernel running on AMD64 systems will | | | | | | sometimes map the contents of PIE executable, | | | | | | the heap or ld.so to where the stack is mapped | | | | | | allowing attackers to more easily manipulate the | | | | | | stack. Linux Kernel version 4.11.5 is affected. | | | | | | https://security-tracker.debian.org/tracker/CVE-2017-1000379 | +------------+-----------------------------+--------------+------------------------+--------------------------------------------------------------+ | Unapproved | High CVE-2018-14611 | linux | 4.9.130-2 | An issue was discovered in the Linux kernel | | | | | | through 4.17.10. There is a use-after-free in | | | | | | try_merge_free_space() when mounting a crafted btrfs | | | | | | image, because of a lack of chunk type flag checks | | | | | | in btrfs_check_chunk_valid in fs/btrfs/volumes.c. | | | | | | https://security-tracker.debian.org/tracker/CVE-2018-14611 | +------------+-----------------------------+--------------+------------------------+--------------------------------------------------------------+ 〜省略〜
Severity(重大度)は、以下のように表示されます。
High CVE-2018-14614
先頭に付いているのがそうで、収集したデータソースからの脆弱性情報は、以下のようにマッピングされます。
- Defcon1(使われてなさそう)
- Critical
- High
- Medium
- Low
- Negligible
- Unknown
マッピングしているのは、例えば以下あたり。
https://github.com/coreos/clair/blob/master/ext/vulnsrc/ubuntu/ubuntu.go
func SeverityFromPriority(priority string) database.Severity { switch priority { case "untriaged": return database.UnknownSeverity case "negligible": return database.NegligibleSeverity case "low": return database.LowSeverity case "medium": return database.MediumSeverity case "high": return database.HighSeverity case "critical": return database.CriticalSeverity default: log.Warningf("could not determine a vulnerability severity from: %s", priority) return database.UnknownSeverity } }
https://github.com/coreos/clair/blob/master/ext/vulnsrc/rhel/rhel.go
func severity(sev string) database.Severity { switch strings.Title(sev) { case "Low": return database.LowSeverity case "Moderate": return database.MediumSeverity case "Important": return database.HighSeverity case "Critical": return database.CriticalSeverity default: log.Warningf("could not determine vulnerability severity from: %s.", sev) return database.UnknownSeverity } }
確認したい、対処したい重大度に合わせて、このあたりを見ていくのでしょうねぇ。
まとめ
Dockerイメージの脆弱性スキャンに使えるツール、Clairを試してみました。
とりあえず動かしてみただけなので、CI/CDに組み込むとかそういう高尚なところは全然調べられていませんが…。
コンテナを使うなら、こういうところも気にしていかないとなーという気がします。