Trivyのサーバモードを試してみた話

この記事は Recruit Engineers Advent Calendar 2019 の 14日目の記事です。

概要

Trivyはknqyf263氏が開発し、現在Aquasecurity社でホスティングされているコンテナイメージのスキャンツールです。

github.com

今回はTrivyに新たにサーバモードが追加されましたので、それを検証した結果を書こうかと思います。

本記事では既存のTrivyについて紹介し、その後サーバモードについて紹介します。

 

Trivyについて

Trivyはコンテナイメージを解析し、OSパッケージとコンテナに内包されているGemfile.lockやPipfile.lockといった依存関係ファイルからアプリケーションパッケージの脆弱性を検知します。

サーバモードの紹介をする前に実際にTrivyを実行し、概要を紹介します。 Macを使って検証します。 下記のコマンドでインストールが可能です。

brew install aquasecurity/trivy/trivy

次にtrivyコマンドでコンテナイメージをスキャンします。

trivy knqyf263/vuln-image:1.2.3

一部結果を省略しておりますが、alpineとnodeの脆弱性が検知されています。

f:id:masahiro331:20191220020030p:plain f:id:masahiro331:20191220015946p:plain

コンテナの脆弱性スキャナはTrivy以外にもClairやAnchoreなどが有名ですが、インストールしてから実際にコンテナイメージを検査するまでの速度はTrivyが一番早いと思います。

サーバモードの概要 

v0.3.0のリリースでTrivyのクライアントモードとサーバモードが実装がされました。
Release v0.3.0 · aquasecurity/trivy · GitHub

Trivyのクライアントを使ってコンテナイメージを解析し、OS情報、OSパッケージ、アプリケーションパッケージを抽出してprotocol bufferを用いてサーバに送信します。
Trivyのサーバは受け取ったデータを元に脆弱性を検知し、クライアントに返却します。

検証

まずはTrivyサーバを起動します。

$ trivy server
2019-12-24T01:01:56.811+0900    INFO    Listening localhost:4954...

次にTrivyクライアントを使って脆弱性を検知します

$ trivy client -s "HIGH" centos

centos (centos 8.0.1905)
========================
Total: 24 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 24, CRITICAL: 0)

特に書くことがないくらい簡単にcentos脆弱性検知ができました。
Trivyをインストールしてから数分程度でサーバ構築をして、脆弱性検知までできるのはかなり魅力的だと思います。

おまけ

ここまでだと文量が少なくアドベントカレンダーにならないのでTrivyサーバを自家製のクライアントで利用してみます。

PythonでTrivyクライアントを作ってみた

Trivyのクライアントはtwirpというprotobufクライアントのジェネレータを用いて作成されています。
今回はtwirpを使ってPythonのTrivyクライアントを作成し、脆弱性検知をしようと思います。
GitHub - twitchtv/twirp: A simple RPC framework with protobuf service definitions

クライアントの生成

GitHubを見ると公式でPythonクライアントのジェネレータをサポートしているみたいですが、バイナリが見つからなかったためbuildしていきます。(見つけた方は教えてください)
以下のように、TrivyのPythonクライアントを生成します。

$ mkdir -p ~/work/trivy-client-py/ && cd ~/work/trivy-client-py/
# はじめにTrivyからprotoファイルを取得します
$ git clone https://github.com/aquasecurity/trivy
$ cp trivy/rpc/detector/service.proto trivy.proto 

# 次に twirpでpython用のジェネレータを取得
$ cd $GOPATH/src/github.com/twichtv && git clone https://github.com/twitchtv/twirp.git && cd twirp
$ go build -o /usr/local/bin/protoc-gen protoc-gen-twirp_python/main.go

# protobufでpythonスクリプトを作成
$ cd ~/work/trivy-client-py
$ protoc --proto_path=$GOPATH/src:. --twirp_out=. --python_out=. trivy.proto
$ ls 
trivy/      trivy.proto      trivy_pb2.py       trivy_pb2_twirp.py

※ protobufの仕様などについては割愛させていただきます

クライアントの実装

ここまでTrivyのPythonクライアントのインターフェースが作成できました。
次にPythonクライアントを実装します。

# 必要なモジュールをインストール
$ pipenv install protobuf

以下は、Rubyパッケージの脆弱性検知とalpineの脆弱性検知をするPythonスクリプトになります。

from trivy_pb import trivy_pb2_twirp as twirp
from trivy_pb import trivy_pb2 as trivy

TRIVY_SERVER = "http://localhost:4954"

def detected_os_vulnerability(os_family, os_name, pb_packages):
    request = trivy.OSDetectRequest(
        os_family=os_family, os_name=os_name, packages=pb_packages
    )
    return twirp.OSDetectorClient(TRIVY_SERVER).detect(request)

def detected_lib_vulnerability(file_path, pb_libraries):
    request = trivy.LibDetectRequest(file_path=file_path, libraries=pb_libraries)
    return twirp.LibDetectorClient(TRIVY_SERVER).detect(request)

if __name__ == "__main__":
    pb_libraries = [
          trivy.Library(
              name="nokogiri",
              version="1.10.3",
          )
    ]
 response = detected_lib_vulnerability("Gemfile.lock", pb_libraries)
    print(response)

    pb_packages = [
          trivy.Package(
              name="openldap",
              version="2.4.47",
              epoch=0,
              arch="",
              release="",
          )
    ]
 response = detected_os_vulnerability("alpine", "3.9", pb_packages)
    print(response)

ディレクトリ構成は以下のようにしました。

$ tree 
trivy-client-py/
├── Pipfile
├── Pipfile.lock
├── main.py
├── service.proto
└── trivy_pb
    ├── __init__.py
    ├── trivy_pb2.py
    └── trivy_pb2_twirp.py
動作確認

Trivyサーバを起動させます。

$ trivy server
2019-12-24T01:01:56.811+0900    INFO    Listening localhost:4954...

PythonクライアントでTrivyサーバにリクエストを送ってみたいと思います。

$ pipenv run python main.py
detected_vulnerabilities {
  vulnerability_id: "CVE-2019-5477"
  pkg_name: "nokogiri"
  installed_version: "1.10.3"
  fixed_version: ">= 1.10.4"
  title: "Rexical Command Injection Vulnerability"
  description: "A command injection vulnerability in Nokogiri v1.10.3 and earlier allows commands to be executed in a subprocess via Ruby\'s `Kernel.open` method. Processes are vulnerable only if the undocumented method `Nokogiri::CSS::Tokenizer#load_file` is being called with unsafe user input as the filename. This vulnerability appears in code generated by the Rexical gem versions v1.0.6 and earlier. Rexical is used by Nokogiri to generate lexical scanner code for parsing CSS queries. The underlying vulnerability was addressed in Rexical v1.0.7 and Nokogiri upgraded to this version of Rexical in Nokogiri v1.10.4."
  references: "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5477"
  references: "https://github.com/sparklemotion/nokogiri/issues/1915"
  references: "https://github.com/tenderlove/rexical/blob/master/CHANGELOG.rdoc"
  references: "https://github.com/tenderlove/rexical/blob/master/CHANGELOG.rdoc#107--2019-08-06"
  references: "https://github.com/tenderlove/rexical/commit/a652474dbc66be350055db3e8f9b3a7b3fd75926"
  references: "https://groups.google.com/forum/#!msg/ruby-security-ann/YMnKFsASOAE/Fw3ocLI0BQAJ"
  references: "https://hackerone.com/reports/650835"
  references: "https://lists.debian.org/debian-lts-announce/2019/09/msg00027.html"
}
detected_vulnerabilities {
  vulnerability_id: "CVE-2019-13057"
  pkg_name: "openldap"
  installed_version: "2.4.47"
  fixed_version: "2.4.48-r0"
  title: "openldap: Information disclosure issue in slapd component"
  description: "An issue was discovered in the server in OpenLDAP before 2.4.48. When the server administrator delegates rootDN (database admin) privileges for certain databases but wants to maintain isolation (e.g., for multi-tenant deployments), slapd does not properly stop a rootDN from requesting authorization as an identity from another database during a SASL bind or with a proxyAuthz (RFC 4370) control. (It is not a common configuration to deploy a system where the server administrator and a DB administrator enjoy different levels of trust.)"
  references: "http://www.openldap.org/lists/openldap-announce/201907/msg00001.html"
  references: "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-13057"
  references: "https://lists.debian.org/debian-lts-announce/2019/08/msg00024.html"
  references: "https://openldap.org/its/?findid=9038"
  references: "https://security.netapp.com/advisory/ntap-20190822-0004/"
  references: "https://usn.ubuntu.com/4078-1/"
  references: "https://usn.ubuntu.com/4078-2/"
  references: "https://www.openldap.org/its/?findid=9038"
  references: "https://www.openldap.org/lists/openldap-announce/201907/msg00001.html"
}

RubyパッケージではnokogiriのCVE-2019-5477が検出されており、AlpineのopenldapではCVE-2019-13057が検出できています。

まとめ

Trivyのクライアントを実装することで、コンテナではなくアプリケーションパッケージやOSパッケージの単位で脆弱性検知ができるようになりました。
サーバ情報の構成管理しているような環境だとDBからパッケージ情報をとってきて、Trivyで脆弱性検知するといった活用ができるのではと思います。

作成したクライアントはgithubに供養します。

github.com