Trivyのサーバモードを試してみた話
この記事は Recruit Engineers Advent Calendar 2019 の 14日目の記事です。
概要
Trivyはknqyf263氏が開発し、現在Aquasecurity社でホスティングされているコンテナイメージのスキャンツールです。
今回は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の脆弱性が検知されています。
コンテナの脆弱性スキャナは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に供養します。