メインコンテンツまでスキップ

依存関係の監査自動化

このチュートリアルでは、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 内で発見された依存パッケージの検査/監査を行っています。

info

ワークフローのマニフェストに関しての詳細はこちらをご確認ください。

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