はじめに
はじめまして、スリーシェイクのSreake 事業部インターン生の鈴木友也と永井隆介です。Sreake 事業部は SRE関連技術に強みを持つエンジニアによるコンサルテーションサービスを提供する事業部であり、私たちも SRE 技術の調査と研究を行う目的で2022年10月11日 ~ 24日に開催された短期インターンに参加しました。2週間という期間を使って、Trivy Operator の技術検証と運用方法の提案を行いました。以下では、その成果をまとめたいと思います。
Trivy Operatorとは
Trivy Operatorは、Kubernetes ネイティブな統合セキュリティプラットフォームです。具体的には、Kubernetes Operator を用いてコンテナイメージの脆弱性を自動でスキャンしたり、デプロイされる Kubernetes リソースの設定不備を自動で検証したりできます。
Trivy Operator の導入を検討している人の多くはすでに Trivy を使っている / 使っていないけど Trivy との違いが気になっていると思うので、以下では Trivy (以下、Trivy Operator と区別するため Trivy CLI と表記)と Trivy Operator の違いに着目しつつ、Trivy Operator の基本的な特徴について整理してみます。
Trivy Operator の代表的な特徴は以下の通りです。
- 継続的なバッググラウンドスキャン
- 変更された Kubernetes リソースの自動検知とスキャンの再実行
- マニフェストによる宣言的な管理
- Webhook による外部ツールとの連携
継続的なバッググラウンドスキャン
Trivy CLI を利用する場合、Kubernetes クラスタ内で動作しているリソースやコンテナイメージをスキャンするためには、手動でコマンドを実行するか、シェルスクリプトを書いて定期的に実行するような仕組みを作る必要がありました。しかし、Trivy Operator は指定された間隔でコンテナイメージや Kubernetes リソースのスキャンを自動で実行することができます。動作中のクラスタの設定や脆弱性を、 継続的にスキャンしたい場合 は、Trivy Operator が向いています。
✅ 嬉しいポイント:継続的にスキャンを自動で実行できる!
変更された Kubernetes リソースの自動検知とスキャンの再実行
Trivy CLI を使っている場合「Deployment が更新されたタイミングでスキャンを再実行する」ということは標準機能としては提供されていませんでした。そのため、設定に不備があったKubernetes リソースの発見が遅れるなどの問題がありました。Trivy CLI には trivy k8s
というコマンドがありますが、これはあくまで手動で実行する前提のコマンドであり、リソースのライフサイクルに合わせて実行することはできません。
✅ 嬉しいポイント:脆弱性や設定不備にすぐに気付ける!
マニフェストによる宣言的な管理
CIでTrivy を使う場合には微妙にリポジトリごとに設定やオプションが異なり、全体として Trivy をどのような設定で使っているのか把握しづらいことがあります。Kubernetes のリソースとして Trivy を動かすことで、Trivy に関するほとんどすべての情報をマニフェストとして宣言的に管理できるメリットがあります。
✅ 嬉しいポイント:Trivy に関する設定をマニフェストで管理できる!
Webhookによる外部ツールとの連携
Trivy Operator は Webhook に対応しているため、生成したレポートを外部ツールに簡単に送信できます。例えば、Lambda でレポートをフィルタリングする中継サーバーを用意して、最終的にはそこから Slack に通知を飛ばす、ということも実現可能です。今後このような Trivy Operator を使いやすくするOSSが多く開発されれば一番嬉しいですし、そうでなくても自分たちで実装することで脆弱性を柔軟に管理することができます。
✅ 嬉しいポイント:外部ツールや自作ツールと簡単に連携できる!
Trivy Operatorの導入
まず、Trivy Operator のインストール方法について説明します。詳細は 公式ドキュメントに書かれているため、詳しくはそちらを参照してください。導入に関しては、kubectl を使って外部リポジトリに置かれているマニフェストを apply する方法と、Helm Chart を用いる方法があります。既にインストールが完了している人 or 公式ドキュメント読むよって人は、この章は読み飛ばしてもらって構いません。
kubectl を使う方法
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/trivy-operator/v0.3.0/deploy/static/trivy-operator.yaml
Helm Chart を使う方法
1,helm レポジトリを追加する
$ helm repo add aqua <https://aquasecurity.github.io/helm-charts/>
$ helm repo update
2,クラスタにインストールする
$ helm install trivy-operator aqua/trivy-operator \\
--namespace trivy-system \\
--create-namespace \\
--set="trivy.ignoreUnfixed=true" \\
--version 0.3.0
3,trivy-operator という名前の Release がデプロイされていることを確認する
$ helm list -n trivy-system
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
trivy-operator trivy-system 1 2021-01-27 20:09:53.158961 +0100 CET deployed trivy-operator-0.3.0 0.3.
Trivy Operator を使ってみる
Trivy Operator には、以下の2つの主要な機能があります。まずはこれらの機能を理解することで、Trivy Operator に対する理解を深めていきます。 今回はv0.3.0について機能検証を行いました。
- Vulnerability Scanning — 脆弱性スキャン
- Configuration Auditing — 設定監査
Vulnerability Scanning — 脆弱性スキャン
Vulnerability Scanning は Kubernetes クラスターで利用されるコンテナイメージの脆弱性をスキャンする機能です。クラスタ内で使用されているコンテナイメージを自動的に検出し、Trivy を用いて脆弱性スキャンを行ってVulnerabilityReport リソースを生成します。
このとき、生成された VulnerabilityReport リソースはスキャン対象の ReplicaSet リソースに所有されている状態になります。例えば、特定のアプリケーション( Deployment )で利用されているコンテナイメージをスキャンすると、そのコンテナイメージを利用している ReplicaSet リソースが、対応する VulnerabilityReport リソースの所有者(Owner)として登録されます。リソースの所有/被所有関係は、以下のようになります。この仕組みにより、VulnerabilityReport リソースのライフサイクルを ReplicaSetリソースのライフサイクルと一致させることができます。
さらに、Trivy Operator は ReplicaSet リソースの更新を自動で検出して再スキャンを実行します。つまり、アプリケーションの更新に合わせて脆弱性スキャンを再実行してくれます。これにより、脆弱性や設定不備をデプロイしてしまっても、すぐに気づくことができます。
脆弱性のスキャンはStandaloneとClient Serverの2つのモードがあります。デフォルトはStandaloneで組み込みのTrivy Scannerを用いて、GitHubからダウンロードした脆弱性DBに基づいてスキャンをします。Client Serverはスキャナーを別システムに分離する設定で、対応するAPIを持ったサーバーを用意することでサードパーティの脆弱性スキャナーを利用することができます。
Trivy Scannerでは trivy-operator-trivy-config
というConfigMapとSecretによる設定で、検出したい脆弱性の重要度や、無視する脆弱性及びファイルやディレクトリ、Rate limitの影響を緩和するためにGitHubトークンなど設定できます。設定項目は公式ドキュメントのVulnerability Scanning Configurationで確認可能です。
Trivy Scannerで生成されるVulnerabilityReport リソースでは脆弱性は次の項目が確認できます。オプションの項目はConfigMapによる設定の additionalVulnerabilityReportFields
で指定できます。
Resource | 脆弱性の存在するリソース(利用しているパッケージ等) |
---|---|
Vulnerability ID | 脆弱性ID |
Severity | 重要度 (CRITICAL > HIGH > MEDIUM > LOW > NONE, UNKNOWN) |
Installed Version | インストールされたリソースのバージョン |
Fixed Version | 脆弱性の修正されたバージョン |
Title | 脆弱性のタイトル |
Primary Link | 最重要関連リンク |
Score | CVSSスコア |
Description | 脆弱性の説明 [オプション] |
Links | 関連リンク集 [オプション] |
Target | 検査対象のコンテナ [オプション] |
Cvss | CVSS情報 [オプション] |
Refs: 共通脆弱性評価システム CVSS https://www.ipa.go.jp/security/vuln/CVSS.html
kubectl describe
コマンドでVulnerabilityReportリソースを確認した際の実際の脆弱性情報の出力例は以下になります。
Cvss:
Nvd:
V2Score: 5
V2Vector: AV:N/AC:L/Au:N/C:N/I:N/A:P
V3Score: 7.5
V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Redhat:
V3Score: 8.2
V3Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H
Description: zlib before 1.2.12 allows memory corruption when deflating (i.e., when compressing) if the input has many distant matches.
Fixed Version: 1:1.2.8.dfsg-5+deb9u1
Installed Version: 1:1.2.8.dfsg-5
Links:
<http://seclists.org/fulldisclosure/2022/May/33>
<http://seclists.org/fulldisclosure/2022/May/35>
...
Primary Link: <https://avd.aquasec.com/nvd/cve-2018-25032>
Resource: zlib1g
Score: 8.2
Severity: HIGH
Target: nginx:1.15 (debian 9.9)
Title: zlib: A flaw found in zlib when compressing (not decompressing) certain inputs
Vulnerability ID: CVE-2018-25032
Configuration Auditing — 設定監査
Configuration Auditingはポリシーに基づいてクラスターの設定監査を行う機能です。Vulnerability Scanningと同様に、監査対象のリソースが追加されると自動で監査が実行され、そのリソースの所有下にConfigAuditReportリソースが対応づけられます。
デフォルトでは組み込みポリシーを用いてセキュリティ上のリスクとなる設定を検出しますが、OPA/Regoを用いてカスタムポリシーを記述しConfigMapとして登録することで、任意の設定監査が可能になります。
ConfigAuditReportリソースで確認できる監査項目は次のようになっています。
Severity | 重要度 (CRITICAL > HIGH > MEDIUM > LOW > NONE, UNKNOWN) |
Success | 監査結果 (正常: true, 問題あり: false) |
Title | 監査項目名 |
Category | 監査カテゴリ名 |
Check ID | 監査項目ID |
Description | 監査項目説明 |
Messages | 監査結果メッセージ |
kubectl describe
コマンドでConfigAuditReportリソースを確認した際の実際の脆弱性情報の出力例は以下になります。
Severity: HIGH
Success: true
Title: Access to host IPC namespace
Category: Kubernetes Security Check
Check ID: KSV013
Description: It is best to avoid using the ':latest' image tag when deploying containers in production. Doing so makes it hard to track which version of the image is running, and hard to roll back the version.
Messages: Container 'nginx' of Pod 'pod-nginx' should specify an image tag
Advanced Setting ⚙️ : Custom Policy の設定
Configuration Auditing ではカスタムポリシーを利用することもできます。
カスタムポリシー登録の手順は以下のようになります。
- Rego によるカスタムポリシーの記述
trivy-operator-policies-config
という ConfigMap への登録
例えば「指定されたラベルがない場合に警告する」といったカスタムポリシーはRegoで次のように記述できます。
package trivyoperator.policy.k8s.custom
// 指定されたラベルがない場合に警告するカスタムポリシー
__rego_metadata__ := {
"id": "recommended_labels", // Check IDに対応
"title": "Recommended labels", // Titleに対応
"severity": "LOW", // Severityに対応
// レポートには反映されない (Trivy CLIではCategoryに対応)
"type": "Kubernetes Security Check",
// Descriptionに対応
"description": "A common set of labels allows tools to work interoperably, describing objects in a common manner that all tools can understand.",
// レポートには反映されない (Trivy CLIでは有効)
"recommended_actions": "Take full advantage of using recommended labels and apply them on every resource object.",
// レポートには反映されない (Trivy CLIでは有効)
"url": "<https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/>",
}
// 必要なラベルの定義
recommended_labels := [
"app.kubernetes.io/name",
"app.kubernetes.io/version",
]
// 拒否条件の定義
// deny または warn を定義
deny[res] {
provided := {label | input.metadata.labels[label]} // [定義] 入力リソースのラベル
required := {label | label := recommended_labels[_]} // [定義] 必要なラベル
missing := required - provided // [定義] 足りないラベル
count(missing) > 0 // [条件] 足りないラベルが0個より多い
msg := sprintf("You must provide labels: %v", [missing])
res := {"msg": msg} // レポートにメッセージを書き込む
}
このポリシーをTrivy Operatorに適用させるためにはConfigMapに登録する必要があり、マニュフェストとして記載すると次のようになります。
---
apiVersion: v1
kind: ConfigMap
metadata:
name: trivy-operator-policies-config
namespace: trivy-system
labels:
app.kubernetes.io/name: trivy-operator
app.kubernetes.io/instance: trivy-operator
app.kubernetes.io/version: "0.3.0"
app.kubernetes.io/managed-by: kubectl
data:
policy.recommended_labels.kinds: "*"
policy.recommended_labels.rego: |
package trivyoperator.policy.k8s.custom
__rego_metadata__ := {
"id": "recommended_labels",
"title": "Recommended labels",
"severity": "LOW",
"type": "Kubernetes Security Check",
"description": "A common set of labels allows tools to work interoperably, describing objects in a common manner that all tools can understand.",
"recommended_actions": "Take full advantage of using recommended labels and apply them on every resource object.",
"url": "<https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/>",
}
recommended_labels := [
"app.kubernetes.io/name",
"app.kubernetes.io/version",
]
deny[res] {
provided := {label | input.metadata.labels[label]}
required := {label | label := recommended_labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("You must provide labels: %v", [missing])
res := {"msg": msg}
}
Advanced Setting ⚙️: レポートのアクセス権限の設定
ここでは、脆弱性レポートや設定監査レポートへのアクセスを制限する方法について紹介します。これらのレポートは**「 システムの弱点に関する情報を多分に含んでおり 、万が一攻撃者がレポートにアクセスできた場合、被害が大きく拡大する恐れがある 」** ため、なにかしらの方法でアクセス制御を行う必要があるからです。
公式ドキュメントで具体的なアクセス制御の方法が紹介されています。
Trivy Operator を使うユーザーは、以下の3つの権限のどれかを選択することができます。
- administrative (管理者 = クラスタレベル)
- coarse-grained (粗い粒度 = namespace レベル)
- fine-grained (細かい粒度 = 特定のリソースレベル)
以下は、具体的な権限の対応表です。
fine-grained はちょっと細かすぎるので、公式ドキュメントでは administrative か coarse-grained のどちらかを使うことが推奨されています。例えば、システムの脆弱性をすべて把握する必要があるセキュリティエンジニアには administrative 権限を付与して、その他の開発エンジニアには関わっているプロダクトの脆弱性だけにアクセスを絞る(= coarse-grained 権限を付与する)という運用にしてもいいかもしれません。このあたりは企業やチームによって大きく変わるところだと思うので、導入時に議論・検討することをオススメします。
✅ administrative と coarse-grained を要件に合わせて上手く使う。fine-grained は使わない。
スキャンの定期実行と手動実行について
VulnerabilityScanningについては、TTL(生存時間)が存在し、TTLが過ぎたVulnerabilityReportリソースは自動で削除され再スキャンが実行されます。
VulnerabilityReportのTTLはTrivy Operatorの環境変数 OPERATOR_VULNERABILITY_SCANNER_REPORT_TTL
の値が設定され、デフォルトは24時間(24h
)です。Helmでインストールする場合は operator.vulnerabilityScannerReportTTL
で設定できます。
あるVulnerabilityReportを手動で即座に再スキャンしたい場合は該当レポートの metadata.annotation.trivy-operator.aquasecurity.github.io/report-ttl
を 0s
にすることで実現可能です。
TTL設定はv0.3.0ではVulnerabilityScanningにのみ存在し、ConfigurationAuditでは機能しません。ConfigurationAuditを手動で行いたい場合、該当レポートを削除した後に kubectl rollout restart deployment trivy-operator
などでTrivy Operatorを再起動することで再スキャンを行うことができます。
スキャンを行うnamespace範囲の設定
Trivy Operatorの環境変数 OPERATOR_TARGET_NAMESPACES
にスキャン対象にしたいnamespaceをカンマ区切りで設定することができます。空文字の場合はすべてのnamespaceが対象になります。
Trivy Operator を用いた脆弱性管理の提案
以下では、GAS と Trivy Operator を用いた脆弱性管理について提案します。ここでは脆弱性管理について絞って提案しますが、ほとんど同様の仕組みを用いて設定監査レポートも管理できます。
提案する脆弱性管理の構成は以下の図の通りです。
Trivy Operator は Webhook 機能に対応しており、生成した脆弱性レポートを特定のエンドポイントにPOSTすることができます。この機能を利用して、脆弱性情報を Google Spread Sheet (以下、スプレッドシート)で管理することができます。この構成の主なメリットは、実装が簡単であり、自前でUIを作りこまなくても良いことです。スプレッドシートの基本機能を使うだけで、それなりに見やすいUIを構築できますし、フィルタリングなどもできます。
一方で、対応する脆弱性と対応しない脆弱性を上手くグルーピングしたり、脆弱性情報とGitHubリポジトリを関連付けたりすることはできないため、より柔軟に管理したい場合は、Postee やその他の脆弱性管理ツールを導入・自作するのがオススメです。とりあえず導入してみたい、という場合にはこれぐらいシンプルな構成の方が向いていると思います。
以下は提案運用のためのGoogle Apps ScriptのPoCになります。
VulnerabilityReportを受け取った際にリソース名のシートを作成して脆弱性情報をスプレッドシートに書き込みます。また各脆弱性にunchecked, ignoreなどの対応状況(status)を設定でき、再スキャンで同じリソースのレポートを受け取った場合でも既知の脆弱性に対する対応状況を保持できるようにしています。
スプレッドシートに脆弱性を書き込んだ例
2022_3shake_autumn_intern/TrivyReportManager_PoC at main · 3-shake/2022_3shake_autumn_intern
アラートをフィルタリングするには? — Postee を具体例に
GASを使う構成はDBとUIが一体となっており導入も手軽であることがメリットでした。しかし、脆弱性管理をする上では アラートを設定したくなる ことがあります。
例えば、スプレッドシートでの運用では、対応状況を保持しているため未知の脆弱性が新たに発見されたときのみ Slack でアラートを出すといった運用が考えられます。
しかし、脆弱性アラートの文脈でたびたび問題になるのが、アラート多すぎ問題 です。
Trivy のような脆弱性スキャンツールはデフォルトで登録された脆弱性をすべて報告してしまいます。しかし、実際の現場では「特定の (複数の) 条件を満たしている場合はあえて対応していない」といったルールのもと脆弱性を運用しているところもあります。
それにも関わらず、対応しなくていい脆弱性もアラートされてしまう状況が続くと、運用が大変すぎたり、場合によってはアラートを無視するモチベーションにつながったりしてしまいます。設定ファイルで特定の脆弱性を無視するような設定はできなくはないですが、なにかしらルールに基づいて無視するような、凝ったことをGASで保守性を維持しつつ実現するのは困難です。
このような状況では、Postee というツールが役に立つ可能性があります。今回のインターンでは実際に検証することはできませんでしたが、非常に強力なツールだと思うので、最後に簡単に紹介してみたいと思います。
Postee とは?
Postee とは、aqua security 社が開発しているメッセージルーティングシステムです。メッセージルーティングシステムとはなんぞや、と思うかもしれませんが、簡単にいえば「メッセージを受信してそれをどこか別の場所にルーティングする」システムです。
(Postee の紹介スライド を一部変更して引用)
具体的には、以下のようなイメージです。
Postee の面白い点 — Policy based routing
Postee がただのメッセージルーティングシステムだったらあまり新鮮味はありません。しかし、Postee は「 Policy based routing 」という面白い機能を備えています。
この機能は OPA/Rego を用いてルーティングを制御できる機能です。具体的には、これを利用することで「特定のルールに合致する脆弱性のみをSlackにルーティングする」といった機能が実現できる可能性があります。
またPosteeを介して対応状況管理用のDBを連携させることで、ユーザーが快適に脆弱性をチェックし、その対応状況からルールベースで柔軟に通知の可否を決定することもできると考えられます。
ただ、今回のインターンでは検証までできなかったため、また機会を見つけて検証してみたいと思っています。
Trivy Operator の総評
結論としては「現状単体での運用は厳しいものの、必要な機能はある程度揃っている」というところだと思います。Trivy Operator 導入によるメリットはあるものの、単体では出力レポートや検出情報の管理、通知機能などが不足しており、追加でツールを導入または自作する必要があります。また、ドキュメントもまだまだ少ないため、今後利用者が増えて Trivy Operator を実用レベルまで引き上げてくれる周辺ツールが生まれ、ドキュメントやコミュニティが安定してくれれば嬉しいなと思っています。
おわりに
(鈴木) 非常に楽しい2週間でした!Kubernetes に関してだけじゃなく、脆弱性を管理・運用していく上でなにが大変なのか、どういったことを実現したいのか、といった、趣味として手を動かしているだけでは得られないような、リアルな話が沢山聞けたのが特に学びになりました。また、ちゃんと企業が開発しているOSSでも色々と直したいところがあって、コントリビュートの機会も沢山ありそうなのも良い発見でした。
(永井) 非常に充実した2週間でした。
私はセキュリティやSREどころかKubernetes自体もほとんど触ったことがない完全初心者の状態から学び始めたのですが、その分今回のインターンシップで大きく成長できたと感じています。始めは分からない概念が知らない単語で説明されているなど暗中模索でありましたが、メンターの方々のサポートもあって大きく躓くことなくKubernetesの概念や制御方法を把握していき、Trivy Operatorの機能検証や運用方法検討ができるまでにいたることができたと思います。
また実際の業務上での脆弱性管理におけるToilについて、非常に心のこもったお話も伺うことができ、貴重な経験でした。加えてインターンシップ全体としても、ただ経験を積めるだけでなく、事前講義や参考書籍をいただけた上、有給インターンシップという形で開催していただき、より責任感を持って真剣に取り組むことができました。
未知の技術に触れて引き出しを増やすという意味でも、社員の方々と交流しながら実務についてのお話を伺うという意味でも、そして研究課題に真剣に取り組んだという意味でも非常に意義のある経験をすることができたと思います。