VulnCheck は VulnCheck CLI を通じて Azure Pipelines で動作します。Marketplace から拡張機能をインストールする必要はありません。以下のパイプラインは、バージョンを固定した CLI をインストールし、リポジトリをスキャンし、ポリシーに違反する検出結果があれば実行を失敗させ、結果をパイプラインのアーティファクトとして公開します。
API トークン から VulnCheck API トークンを作成し、シークレット変数として追加します。パイプライン自体に設定する方法(Edit > Variables > New variable で Keep this value secret をオンにする)と、複数のパイプラインで共有するために Pipelines > Library の変数グループに設定する方法があります。
env: ブロックで明示的にマッピングする必要があります。これを忘れると、変数を設定しているにもかかわらず CLI は認証エラーで終了コード 3 を返します。trigger:
- main
pr:
- main
pool:
vmImage: ubuntu-latest
variables:
VC_CLI_VERSION: '1.1.0'
VC_CVSS_THRESHOLD: '7.0'
steps:
- bash: |
set -euo pipefail
ARCH="$(uname -m)"
case "$ARCH" in x86_64) ARCH=amd64 ;; aarch64) ARCH=arm64 ;; esac
curl -sSL "https://github.com/vulncheck-oss/cli/releases/download/v${VC_CLI_VERSION}/vulncheck_${VC_CLI_VERSION}_linux_${ARCH}.tar.gz" | tar -xz -C /tmp
sudo install -m 0755 "/tmp/vulncheck_${VC_CLI_VERSION}_linux_${ARCH}/bin/vulncheck" /usr/local/bin/vulncheck
vulncheck version
displayName: Install the VulnCheck CLI
- bash: |
set -euo pipefail
vulncheck scan . --json > "$(Build.ArtifactStagingDirectory)/scan.json"
jq -r '"\((.vulnerabilities // []) | length) vulnerabilities found"' "$(Build.ArtifactStagingDirectory)/scan.json"
displayName: Scan with VulnCheck
env:
VC_TOKEN: $(VC_TOKEN)
- bash: |
SCAN="$(Build.ArtifactStagingDirectory)/scan.json"
jq -e --argjson max "$VC_CVSS_THRESHOLD" '
[ (.vulnerabilities // [])[]
| select((.cvss_base_score // 0 | tonumber? // 0) >= $max or .in_kev) ] | length == 0
' "$SCAN" > /dev/null || {
echo "##vso[task.logissue type=error]Findings at or above CVSS ${VC_CVSS_THRESHOLD}, or in VulnCheck KEV"
jq -r --argjson max "$VC_CVSS_THRESHOLD" '
(.vulnerabilities // [])[]
| select((.cvss_base_score // 0 | tonumber? // 0) >= $max or .in_kev)
| " \(.cve) \(.name)@\(.version) CVSS \(.cvss_base_score) KEV \(.in_kev) fixed in \(.fixed_versions // "n/a")"
' "$SCAN"
exit 1
}
displayName: Gate on findings
- task: PublishPipelineArtifact@1
condition: always()
inputs:
targetPath: $(Build.ArtifactStagingDirectory)/scan.json
artifact: vulncheck-scan
displayName: Publish scan results
押さえておきたい点があります。
vulncheck scan は重大な脆弱性を検出した場合でも終了コード 0 を返すため、スキャンのステップは常に成功し、ポリシー判定は実行サマリー上で明確に読み取れます。CI/CD での VulnCheck を参照してください。condition: always() により、ゲートが失敗した場合でもアーティファクトが公開されます。証跡が最も必要になるのは、その実行です。curl と jq はプリインストールされています。 Microsoft ホステッドの Ubuntu イメージでの話です。セルフホステッドエージェントでは、ホストに一度インストールしてください。VC_CLI_VERSION を固定してください。 これにより、どの実行も既知のバージョンの CLI でスキャンされ、install.sh が最新リリースの判定に行う未認証の GitHub API 呼び出しも避けられます。Azure Repos のプルリクエストでは、ビルド自身の OAuth トークンを使って REST API 経由でコメントスレッドを作成できます。
- bash: |
if [ "$BUILD_REASON" != "PullRequest" ]; then exit 0; fi
SCAN="$(Build.ArtifactStagingDirectory)/scan.json"
BODY=$(jq -r '
"## VulnCheck scan\n\n" +
(if ((.vulnerabilities // []) | length) == 0
then "No vulnerabilities found."
else "| CVE | Package | CVSS | KEV | Fixed in |\n|---|---|---|---|---|\n" +
([ (.vulnerabilities // [])[]
| "| \(.cve) | \(.name)@\(.version) | \(.cvss_base_score) | \(if .in_kev then "Yes" else "No" end) | \(.fixed_versions // "n/a") |" ]
| join("\n"))
end)' "$SCAN")
jq -n --arg body "$BODY" '{comments: [{parentCommentId: 0, content: $body, commentType: 1}], status: 1}' > thread.json
curl -sS --fail-with-body \
--header "Authorization: Bearer ${SYSTEM_ACCESSTOKEN}" \
--header "Content-Type: application/json" \
--data @thread.json \
"${SYSTEM_COLLECTIONURI}${SYSTEM_TEAMPROJECT}/_apis/git/repositories/${BUILD_REPOSITORY_ID}/pullRequests/${SYSTEM_PULLREQUEST_PULLREQUESTID}/threads?api-version=7.1"
displayName: Comment on the pull request
condition: always()
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
System.AccessToken は、マッピングしない限りスクリプトには渡されません。上記の env: ブロックがその役割を果たしています。さらに、その背後にある ID(Project Collection Build Service またはプロジェクトのビルドサービスアカウント)には、対象リポジトリに対する Contribute to pull requests の権限が必要です。権限がない場合、API は 403 を返します。実行を止めずに検出結果を可視化したい場合は、警告をログに出力してステップは成功させます。
- bash: |
SCAN="$(Build.ArtifactStagingDirectory)/scan.json"
jq -e --argjson max "$VC_CVSS_THRESHOLD" '
[ (.vulnerabilities // [])[]
| select((.cvss_base_score // 0 | tonumber? // 0) >= $max or .in_kev) ] | length == 0
' "$SCAN" > /dev/null || echo "##vso[task.logissue type=warning]VulnCheck policy breach — see the vulncheck-scan artifact"
displayName: Report findings
よく使われる構成は、プルリクエストの実行では確実に失敗させ、ブランチのビルドでは警告に留める方法です。これにより、新たに公開された CVE が無関係な作業を止めることはありません。両者は condition: eq(variables['Build.Reason'], 'PullRequest') で切り分けます。
Windows エージェントでは、windows_amd64.zip のリリースをダウンロードし、展開した bin ディレクトリを PATH に追加したうえで、同じ vulncheck scan と jq のステップを PowerShell から実行します。セルフホステッドエージェントには api.vulncheck.com への HTTPS 通信と、CLI をダウンロードするための github.com への通信が必要です。外向き通信が制限されている場合は、リリースアーカイブを社内にミラーしてください。完全な閉域環境のエージェントについては オフラインモード を参照してください。
| 症状 | 原因 |
|---|---|
| シークレット変数を設定しているのに終了コード 3 になる | タスクに env: のマッピングがない。Azure Pipelines はシークレットを自動的に環境変数へ展開しません。 |
| プルリクエストの実行だけ終了コード 3 になる | フォークからのプルリクエストのビルドにはシークレット変数が渡されません。condition: eq(variables['System.PullRequest.IsFork'], 'False') でスキャンをフォーク以外の実行に限定し、マージ後の状態はブランチのビルドでカバーしてください。 |
| プルリクエストの threads API が 403 を返す | ビルドサービスの ID に、対象リポジトリの Contribute to pull requests 権限がありません。 |
| 終了コード 5 | レート制限。並列ジョブが 1 つのトークンを共有している場合が多いです。 |
jq が Cannot iterate over null を返す | 検出結果がないスキャンには vulnerabilities キーが含まれません。上記のように (.vulnerabilities // [])[] を使用してください。 |
jq が null (null) cannot be parsed as a number を返す | cvss_base_score を持たない検出結果があります。上記のように .cvss_base_score // 0 | tonumber? // 0 を使用してください。 |