TrivyのWebAssembly拡張機能、Modulesがすごい

## はじめに

この記事は、[Go 言語 Advent Calendar 2023 シリーズ2](https://qiita.com/advent-calendar/2023/go)の16日目の記事です。

こんにちは。[reo](https://twitter.com/_uhzz_) です。
今年も気づけばアドベントカレンダーの時期ですね。

社のカレンダーにも参加したので、こちらも宣伝させていただきます。

https://qiita.com/uh-zz/items/575232437f885cc4730a

今回はTrivyのWebAssembly拡張について紹介させていただきます。

## 前提

- 以下は詳しく触れません。
    - WebAssembly の概要
    - WebAssemblyランタイム(wazero)について
    - Trivyの全体像について

掲載内容は私自身の見解であり、必ずしも所属する企業や組織の立場、戦略、意見を代表するものではありません。[^1]

## Trivy とは

公式の README にもあるとおり、コンテナイメージからファイルシステム、Gitリポジトリ、Kubernetes、AWSなど幅広いターゲットに対して、既知の脆弱性(CVE)や機密情報、IaCの設定ミスをスキャンします。

https://github.com/knqyf263/trivy

個人的には、コマンドの簡潔さが気に入っています。
サンプルにある通り、コンテナイメージのスキャンはこれだけです。

```
 trivy image python:3.4-alpine
```

スキャン結果は以下のように出力されます。

```
advent-calendar-2023-go (debian 12.2)
=====================================
Total: 40 (HIGH: 38, CRITICAL: 2)

┌───────────────────────┬────────────────┬──────────┬──────────────┬───────────────────┬──────────────────┬──────────────────────────────────────────────────────────────┐
│        Library        │ Vulnerability  │ Severity │    Status    │ Installed Version │  Fixed Version   │                            Title                             │
├───────────────────────┼────────────────┼──────────┼──────────────┼───────────────────┼──────────────────┼──────────────────────────────────────────────────────────────┤
│ git                   │ CVE-2023-25652 │ HIGH     │ affected     │ 1:2.39.2-1.1      │                  │ git: by feeding specially crafted input to `git apply        │
│                       │                │          │              │                   │                  │ --reject`, a path...                                         │
│                       │                │          │              │                   │                  │ https://avd.aquasec.com/nvd/cve-2023-25652                   │
│                       ├────────────────┤          │              │                   ├──────────────────┼──────────────────────────────────────────────────────────────┤
│                       │ CVE-2023-29007 │          │              │                   │                  │ git: arbitrary configuration injection when renaming or      │
│                       │                │          │              │                   │                  │ deleting a section from a...                                 │
│                       │                │          │              │                   │                  │ https://avd.aquasec.com/nvd/cve-2023-29007                   │
├───────────────────────┼────────────────┤          │              │                   ├──────────────────┼──────────────────────────────────────────────────────────────┤
│ git-man               │ CVE-2023-25652 │          │              │                   │                  │ git: by feeding specially crafted input to `git apply        │
│                       │                │          │              │                   │                  │ --reject`, a path...                                         │
│                       │                │          │              │                   │                  │ https://avd.aquasec.com/nvd/cve-2023-25652                   │
│                       ├────────────────┤          │              │                   ├──────────────────┼──────────────────────────────────────────────────────────────┤
│                       │ CVE-2023-29007 │          │              │                   │                  │ git: arbitrary configuration injection when renaming or      │
│                       │                │          │              │                   │                  │ deleting a section from a...                                 │
│                       │                │          │              │                   │                  │ https://avd.aquasec.com/nvd/cve-2023-29007                   │
└───────────────────────┴────────────────┴──────────┴──────────────┴───────────────────┴──────────────────┴──────────────────────────────────────────────────────────────┘

```

## Trivyの`Modules`について


:::note warn
公式ページでも説明されているとおり、この機能は後方互換性を保つことなく変更される可能性があるとのことなので、実運用される場合は注意してください。
:::

https://aquasecurity.github.io/trivy/v0.33/docs/advanced/modules/

Modulesは、Trivyのスキャン結果をWebAssemblyで拡張できる機能です。
この機能がすごいのは、Trivy本体のバイナリを変更せずに、利用側で独自のスキャナーを追加できたり、スキャン結果を上書きできることです。

2023/12時点では、[TinyGo](https://tinygo.org/)のみサポートされています。

TrivyはCGOに依存せずにWebAssemblyを実行するため、[wazero](https://github.com/tetratelabs/wazero)を活用しているそうです。

筆者も、TrivyにWebAssembly拡張機能があることを知ったのは、wazeroの公式ページを見たのがきっかけです。

https://wazero.io/community/users/

以下は、Modulesが提供しているインタフェースです。

`Analyzer`, `PostScanner`のどちらか一方を満たす必要があります。(両方満たすこともできます)
`Modules`はいずれのパターンでも実装します。

```go
type Module interface {
    Version() int
    Name() string
}

type Analyzer interface {
    RequiredFiles() []string
    Analyze(filePath string) (*serialize.AnalysisResult, error)
}

type PostScanner interface {
    PostScanSpec() serialize.PostScanSpec
    PostScan(serialize.Results) (serialize.Results, error)
}
```

Modulesは大きく２つの機能があります。

## 機能1: `Analyzer`

 
`Analyzer`インタフェースを実装すると、正規表現にマッチした名前のファイルを独自のルールでスキャンすることができます。

[公式のサンプル](https://aquasecurity.github.io/trivy/v0.33/docs/advanced/modules/#analyzer-interface)では、`RequiredFiles()`メソッドで正規表現を以下のように指定しています。

```go
func (WordpressModule) RequiredFiles() []string {
    return []string{
        `wp-includes\/version.php`,
    }
}
```

上記の場合、ファイルシステムやコンテナに`wp-includes/version.php`が存在すればスキャン対象とすることができます。

スキャン対象になったファイルのリストを`Analyze(filePath string)`メソッドにて独自のルールでスキャンすることができます。

[公式サンプル](https://aquasecurity.github.io/trivy/v0.33/docs/advanced/modules/#analyzer-interface)では、以下のようにファイル内に設定されているバージョン情報を抜き出して、値をトリミングします。

```php:wp-includes/version.php
$wp_version = '5.7.1';
```

抜き出した情報を、Trivy本体のスキャン結果に追加することができます。

```go
CustomResources: []serialize.CustomResource{
			{
				Type:     typeWPVersion,
				FilePath: filePath, // /usr/src/wordpress/wp-includes/version.php
				Data:     wpVersion, // 5.7.1
			},
		},
```

## 機能2: `PostScanner`

`PostScanner`インタフェースを実装することで、Trivyのスキャン結果に対して、以下３つの操作の１つを選択して実行できます。

:::note
スキャン結果には、前述した`Analyze`メソッドで抜き出した結果も含まれます。
:::

1. スキャン結果を受けて、該当の脆弱性について新しい説明を追加する(`Insert`)
2. スキャン結果を受けて、該当の脆弱性もしくは設定ミスを更新する(`Update`)
3. スキャン結果を受けて、該当の脆弱性もしくは設定ミスを削除する(`Delete`)

上記の操作は、`PostScan(serialize.Results)`メソッドで行います。

[公式サンプル](https://aquasecurity.github.io/trivy/v0.33/docs/advanced/modules/#analyzer-interface)では、`Analyze`メソッドでスキャン結果に追加したWordPressのバージョンチェックを行っています。

具体的には、バージョンが `5.7 <= version < 5.7.2`に含まれるかチェックして、該当すれば独自のスキャン結果を追加する処理を行っています。

サンプルのコードはこちら(便宜上、エラーハンドリングや変数定義を省いています)

```go
func (WordpressModule) PostScan(results serialize.Results) (serialize.Results, error) {
    affectedVersion, err := version.NewConstraint(">=5.7, <5.7.2")
	for _, result := range results {
		for _, c := range result.CustomResources {
			wpPath = c.FilePath
			wpVersion = c.Data.(string)
			wasm.Info(fmt.Sprintf("WordPress Version: %s", wpVersion))

			ver, err := version.NewVersion(wpVersion)
			if err != nil {
				return nil, err
			}

            // NOTE: ver=5.7.1 なので検出される 
			if affectedVersion.Check(ver) {
				vulnerable = true
			}
			break
		}
	}

	if vulnerable {
		// Add CVE-2020-36326 and CVE-2018-19296
		results = append(results, serialize.Result{
			Target: wpPath,
			Class:  types.ClassLangPkg,
			Type:   "wordpress",
			Vulnerabilities: []types.DetectedVulnerability{
				{
					VulnerabilityID:  "CVE-2020-36326",
					PkgName:          "wordpress",
					InstalledVersion: wpVersion,
					FixedVersion:     "5.7.2",
					Vulnerability: dbTypes.Vulnerability{
						Title:    "PHPMailer 6.1.8 through 6.4.0 allows object injection through Phar Deserialization via addAttachment with a UNC pathname.",
						Severity: "CRITICAL",
					},
				},
				{
					VulnerabilityID:  "CVE-2018-19296",
					PkgName:          "wordpress",
					InstalledVersion: wpVersion,
					FixedVersion:     "5.7.2",
					Vulnerability: dbTypes.Vulnerability{
						Title:    "PHPMailer before 5.2.27 and 6.x before 6.0.6 is vulnerable to an object injection attack.",
						Severity: "HIGH",
					},
				},
			},
		})
	}
```

上記のように、独自のスキャン結果を追加すると、最終結果では以下のように表示されます。

```
┌───────────┬────────────────┬──────────┬─────────┬───────────────────┬───────────────┬────────────────────────────────────────────────────────────┐
│  Library  │ Vulnerability  │ Severity │ Status  │ Installed Version │ Fixed Version │                           Title                            │
├───────────┼────────────────┼──────────┼─────────┼───────────────────┼───────────────┼────────────────────────────────────────────────────────────┤
│ wordpress │ CVE-2020-36326 │ CRITICAL │ unknown │ 5.7.1             │ 5.7.2         │ PHPMailer 6.1.8 through 6.4.0 allows object injection      │
│           │                │          │         │                   │               │ through Phar Deserialization via addAttachment...          │
│           ├────────────────┼──────────┤         │                   │               ├────────────────────────────────────────────────────────────┤
│           │ CVE-2018-19296 │ HIGH     │         │                   │               │ PHPMailer before 5.2.27 and 6.x before 6.0.6 is vulnerable │
│           │                │          │         │                   │               │ to an object...                                            │
└───────────┴────────────────┴──────────┴─────────┴───────────────────┴───────────────┴────────────────────────────────────────────────────────────┘
```

このようにTrivy本体のスキャン結果と同様に、独自ルールで定義したスキャナーを拡張機能として用意できました。

## 実践: `PostScanner`でスキャン結果を更新してみる

こちらイメージを掴んでもらうために、独自に`PostScanner`のみ実装したプラグインを用意しました。

:::note
プラグインのみでよかったのですが、よりそれらしいGoプロジェクトとDockerfileを含んでいます。
:::

https://github.com/uh-zz/advent-calendar-2023

用意したプラグイン自体の処理は簡潔で、`CVE-2023-25652`の脆弱性のスキャン結果に対して、Severity(重要度)と、タイトルの変更をしているだけの処理を行うものです。

まずは、プラグイン適用前のスキャン結果です。
対象のCVEのSeverityが`HIGH`であることを確認できます。

```
┌───────────────────────┬────────────────┬──────────┬──────────────┬───────────────────┬──────────────────┬──────────────────────────────────────────────────────────────┐
│        Library        │ Vulnerability  │ Severity │    Status    │ Installed Version │  Fixed Version   │                            Title                             │
├───────────────────────┼────────────────┼──────────┼──────────────┼───────────────────┼──────────────────┼──────────────────────────────────────────────────────────────┤
│ git                   │ CVE-2023-25652 │ HIGH     │ affected     │ 1:2.39.2-1.1      │                  │ git: by feeding specially crafted input to `git apply        │
│                       │                │          │              │                   │                  │ --reject`, a path...                                         │
│                       │                │          │              │                   │                  │ https://avd.aquasec.com/nvd/cve-2023-25652                   │
├───────────────────────┼────────────────┤          │              │                   ├──────────────────┼──────────────────────────────────────────────────────────────┤
│ git-man               │ CVE-2023-25652 │          │              │                   │                  │ git: by feeding specially crafted input to `git apply        │
│                       │                │          │              │                   │                  │ --reject`, a path...                                         │
│                       │                │          │              │                   │                  │ https://avd.aquasec.com/nvd/cve-2023-25652                   │
└───────────────────────┴────────────────┴──────────┴──────────────┴───────────────────┴──────────────────┴──────────────────────────────────────────────────────────────┘

```

ソースコードはこちら

```go
type Plugin struct{}

func (p Plugin) Version() int {
	return 1
}

func (p Plugin) Name() string {
	return "plugin"
}

func (Plugin) PostScanSpec() serialize.PostScanSpec {
	return serialize.PostScanSpec{
		Action: api.ActionUpdate, // Update severity
		IDs:    []string{"CVE-2023-25652"},
	}
}

func (Plugin) PostScan(results serialize.Results) (serialize.Results, error) {
	for i, result := range results {
		for j, vuln := range result.Vulnerabilities {
			if vuln.VulnerabilityID != "CVE-2023-25652" {
				continue
			}

			results[i].Vulnerabilities[j].Severity = "CRITICAL"
			results[i].Vulnerabilities[j].Title = fmt.Sprintf("[ALERT] please alignment to security-team. detail: %s", results[i].Vulnerabilities[j].Title)
		}
	}

	return results, nil
}
```

このプラグインをTinyGoでwasmファイルにコンパイルします。

```
tinygo build -o plugin.wasm -scheduler=none -target=wasi --no-debug plugin.go
```

作成されたwasmファイルをTrivyのModulesディレクトリにコピーします。

:::note
Trivy起動時に、このディレクトリ配下にあるwasmファイルを読み込みます。
:::

```
cp plugin.wasm ~/.trivy/modules
```

コンテナイメージをスキャンします。

```
trivy image advent-calendar-2023-go
```

以下の結果から、Modulesが読み込まれていることに加えて、対象にしたCVEのSeverityが`CRITICAL`に更新されていることを確認できます。
タイトルも独自の文言を追加できていることがわかります。

```
2023-12-25T00:00:00.000+0900    INFO    Reading plugin.wasm...
2023-12-25T00:00:00.000+0900    INFO    plugin.wasm loaded
2023-12-25T00:00:00.000+0900    INFO    Registering WASM module: plugin@v1
2023-12-25T00:00:00.000+0900    INFO    Vulnerability scanning is enabled

┌───────────────────────┬────────────────┬──────────┬──────────────┬───────────────────┬──────────────────┬──────────────────────────────────────────────────────────────┐
│        Library        │ Vulnerability  │ Severity │    Status    │ Installed Version │  Fixed Version   │                            Title                             │
├───────────────────────┼────────────────┼──────────┼──────────────┼───────────────────┼──────────────────┼──────────────────────────────────────────────────────────────┤
│ git                   │ CVE-2023-25652 │ CRITICAL │ affected     │ 1:2.39.2-1.1      │                  │ [ALERT] please alignment to security-team. detail: git: by   │
│                       │                │          │              │                   │                  │ feeding specially crafted input...                           │
│                       │                │          │              │                   │                  │ https://avd.aquasec.com/nvd/cve-2023-25652                   │
├───────────────────────┼────────────────┼──────────┤              │                   ├──────────────────┼──────────────────────────────────────────────────────────────┤
│ git-man               │ CVE-2023-25652 │ CRITICAL │              │                   │                  │ [ALERT] please alignment to security-team. detail: git: by   │
│                       │                │          │              │                   │                  │ feeding specially crafted input...                           │
│                       │                │          │              │                   │                  │ https://avd.aquasec.com/nvd/cve-2023-25652                   │
└───────────────────────┴────────────────┴──────────┴──────────────┴───────────────────┴──────────────────┴──────────────────────────────────────────────────────────────┘
```


## まとめ

TrivyのModulesについて大きく２つの機能があることを紹介しました。
Trivy本体のバイナリに手を入れることなく、Trivy本体のスキャン結果に独自ルールを追加できるので、以下のような目的でも使えると思いました。

- CI で実行しているlinterや、IaCの設定ミスの検知をTrivyのModulesに移行する。
- 指定したCVEを検知した場合は、速やかに特定のチームへエスカレーションしてもらうために、Severityとタイトルを社内向けに更新するModulesを提供する
- 特定のバージョンにアップグレードしているライブラリを、CIで検知するModulesを提供する

個人的には、WebAssemblyで拡張できるようなOSSがこれからどんどん出てくるのでは、という未来を感じる年の瀬でした。

[^1]: https://xn--u9jy52g42am02luma.jp/