依存関係の監査自動化
このチュートリアルでは、Shisho Cloud が検出した依存関係の監査自動化のための、ポリシーの記述とデプロイ方法を説明します。
ゴール
- Shisho Cloud が検出した依存関係に対する監査ポリシーの書き方を知る
- 既存ポリシーの変更方法・テスト方法を知る
- 変更したポリシーのデプロイ方法を知る
既存のワークフローを書き出す
このページ を参照し、既存のワークフローをローカルに書き出し(shishoctl workflow export
)、同ページ中の ./shisho-cloud-workflows
ディレクトリ同等の構造のディレクトリを用意してください。
具体的には、以下のような構造のディレクトリです:
./shisho-cloud-workflows
├── lib
│ ├── README.md
│ ├── decision
│ │ ├── dependency
│ │ │ ├── package.gen.rego
│ │ │ ├── package.pb.rego
│ │ │ └── vulnerability.rego
│
...
│
├── prebundle-workflow-github-on-push
│ ├── manifest.yaml
│ ├── review-commit-meta
│ │ └── decide
│ │ ├── input.graphql
│ │ ├── policy.rego
│ │ └── policy_test.rego
│ └── review-dependencies
│ └── decide
│ ├── input.graphql
│ ├── policy.rego
│ └── policy_test.rego
│
...
ワークフロー中のポリシーを修正する
デフォルトのポリシーを確認する
以降では、shishoctl workflow export --structured
コマンドにより標準搭載のワークフローを書き出した際に、./shisho-cloud-workflows/prebundle-workflow-github-on-push/
以下に書き出されるファイルを例にとって説明します。
manifest.yaml
このマニフェストは、標準状態では、およそ以下のような内容になっています:
id: prebundle-workflow-github-on-push
name: "Prebundle: Review Contents on GitHub (On Push Event)"
version: 0.1.0
triggers:
github:
- push:
branches:
- :default_branch
jobs:
- id: review-dependencies
name: Review known vulnerabilities of dependencies
decide:
rego: !include review-dependencies/decide/policy.rego
input:
schema: !include review-dependencies/decide/input.graphql
# ...
このマニフェストは、Shisho Cloud に連携された GitHub 組織内の、任意のリポジトリのデフォルトブランチに対する push イベントの発生時に起動します。
その上で、review-dependencies
なるジョブを通して、push された git commit 内で発見された依存パッケージの検査/監査を行っています。
ワークフローのマニフェストに関しての詳細はこちらをご確認ください。
decide/policy.rego
この Rego ポリシーは、標準状態では、およそ以下のような内容になっています:
package policy.github.on_push.review_dependencies
import data.shisho
# Note:
# You can ignore package detections by paths of manifest files (e.g. package-lock.json, Cargo.lock, etc.) by defining `ignored_manifests` map.
#
# The map should look like:
# - key: a repository identifier (e.g. "octocat/Hello-World")
# - value: a list of file patterns to ignore; each pattern should meet the following spec:
# https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-glob-globmatch
#
# Example:
# ignored_manifests := {
# "organization-name/repository-name": [
# "mock/**",
# ],
# }
ignored_manifests := {}
decisions[d] {
event := input.github.event
pkgs := event.headCommit.packages
ignore_map_key := concat("/", [event.repository.ownerLogin, event.repository.name])
filtered_pkgs := filter_packages(pkgs, object.get(ignored_manifests, ignore_map_key, []))
allowed := count(filtered_pkgs) == 0
entries := [shisho.decision.dependency.package_known_vulnerability_entry_v2_with_severity(
event.repository.policyReportId,
shisho.dependency.vuln_severity(v.severity),
{
"advisories": advisories(v.advisories),
"description": v.description,
"found_at": loc,
"name": p.name,
"version": p.version,
"vuln_constraint": v.constraint,
"vuln_id": v.reference.id,
"vuln_namespace": v.reference.namespace,
},
) |
p := filtered_pkgs[_]
v := p.vulnerabilities[_]
loc := p.found_at[_]
]
d := shisho.decision.dependency.package_known_vulnerability({
"allowed": allowed,
"subject": event.repository.policyReportId,
"entries": entries,
})
}
filter_packages(pkgs, patterns) = x {
x = [pkg |
pkg := pkgs[_]
count(ignored_manifest_location(pkg, patterns)) == 0
]
}
ignored_manifest_location(pkg, patterns) = x {
x := [f_at |
f_at := pkg.found_at[_]
count(patterns_matched_with(f_at, patterns)) > 0
]
}
patterns_matched_with(target, patterns) = x {
x := [p |
p := patterns[_]
glob.match(p, ["/"], target)
]
}
advisories(advs) = x {
x := [a.link | a := advs[_]]
}
このポリシーは input.github.event
内のデータを検査し、shisho.decision.dependency.package_known_vulnerability
(定義) を通して依存関係の既知脆弱性情報に関する Decision を発行します。
なお、その際には、ignored_manifests
なる定数に設定された情報を用いて各依存関係が発見された場所(例: npm の package.json
等)の内容をフィルタし、特定のディレクトリから発見された情報を除外する処理も行っています。
decide/input.graphql
なお、このポリシーは、同じディレクトリに含まれる input.graphql
で定義されている、以下の GraphQL クエリにより取得されたデータに対するものです:
query {
github {
event {
... on GitHubPushEvent {
repository {
policyReportId
ownerLogin
name
}
headCommit {
packages(
condition: {
onlyVulnerablePackages: true
vulnerability: { state: [SUnknown, SNotFixed, SFixed, SWontFix] }
}
) {
name
version
found_at
vulnerabilities {
reference {
id
namespace
}
constraint
description
advisories {
link
}
severity
}
}
}
}
}
}
}
この GraphQL クエリによりデータが取得され、input
変数を通してアクセスできるようになっています。
たとえば、Rego ポリシー中で input.github.event
としてアクセスできるのは、GitHubPushEvent 型を持つデータです。
ワークフロー中のポリシーを修正する
上述の Rego ポリシーでは ignored_manifests
変数が空に設定されています。
試しに、GitHub 組織 ignored-org
以下のリポジトリ ignored-repo
内の subdirectory/
以下のマニフェスト(例: npm の package.json
等)から発見された依存関係を検査結果から除外してみましょう:
./shisho-cloud-workflows/prebundle-workflow-github-on-push/decide/policy.rego
を開き、ignored_manifests
の値を以下のように書き換えてください:
ignored_manifests := {"ignored-org/ignored-repo": ["sub-directory/**"]}
単体テストを記述する
ファイルが変更できたら、この変更が適切に動作していることを確かめるために、単体テストを記述してみましょう。
./shisho-cloud-workflows/prebundle-workflow-github-on-push/decide/policy_test.rego
を開き、その中身を以下のように書き換えてください:
package policy.github.on_push.review_dependencies
import data.shisho
import future.keywords
packages := [{
"name": "awesome-package-name",
"version": "0.3.0",
"found_at": ["sub-directory/package.json"],
"vulnerabilities": [{
"reference": {
"id": "CVE-XXXX-XXXX",
"namespace": "nve",
},
"constraint": "xxxx",
"description": "xxxx",
"advisories": {"link": "xxxx"},
"severity": "Critical",
}],
}]
testdata_with_repo(login, name, packages) := x if {
x := {"github": {"event": {
"repository": {
"policyReportId": "dummy",
"ownerLogin": login,
"name": name,
},
"headCommit": {"packages": packages},
}}}
}
test_denied_properly if {
count([d |
decisions[d]
not shisho.decision.is_allowed(d)
d.header.kind == "package_known_vulnerability"
]) == 1 with input as testdata_with_repo("ignored-org", "unignored-repo", packages)
count([d |
decisions[d]
not shisho.decision.is_allowed(d)
d.header.kind == "package_known_vulnerability"
]) == 1 with input as testdata_with_repo("unignored-org", "ignored-repo", packages)
count([d |
decisions[d]
not shisho.decision.is_allowed(d)
d.header.kind == "package_known_vulnerability"
]) == 1 with input as testdata_with_repo("unignored-org", "unignored-repo", packages)
}
test_ignored_properly if {
count([d |
decisions[d]
shisho.decision.is_allowed(d)
d.header.kind == "package_known_vulnerability"
]) == 1 with input as testdata_with_repo("ignored-org", "ignored-repo", packages)
count([d |
decisions[d]
not shisho.decision.is_allowed(d)
d.header.kind == "package_known_vulnerability"
]) == 1 with input as testdata_with_repo("ignored-org", "ignored-repo", [{
"name": "awesome-package-name",
"version": "0.3.0",
"found_at": ["package.json"],
"vulnerabilities": [{
"reference": {
"id": "CVE-XXXX-XXXX",
"namespace": "nve",
},
"constraint": "xxxx",
"description": "xxxx",
"advisories": {"link": "xxxx"},
"severity": "Critical",
}],
}])
}
記述したテストは、Open Policy Agent をインストール した後に以下のようなコマンドを実行することで、実際に実行できます:
# in your ./shisho-cloud-workflows
opa test ./prebundle-workflow-github-on-push ./lib --verbose
ここまでのステップが適切に実行できていれば、以下のように、2 つのテストが PASS するはずです:
prebundle-workflow-github-on-push/review-dependencies/decide/policy_test.rego:
data.policy.github.on_push.review_dependencies.test_denied_properly: PASS (14.350625ms)
data.policy.github.on_push.review_dependencies.test_ignored_properly: PASS (5.418917ms)
--------------------------------------------------------------------------------
PASS: 2/2
ワークフローを適用・実行する
最後に、変更済みのワークフローを Shisho Cloud にデプロイしてみましょう!
これは shishoctl workflow apply
コマンドにより実現できます:
# Run the following in your ./shisho-cloud-workflows
# (need to set the ID of your Shisho Cloud organization to $YOUR_ORG_ID)
shishoctl workflow apply \
-o $YOUR_ORG_ID \
-f ./prebundle-workflow-github-on-push/manifest.yaml