VulnCheck は VulnCheck CLI を通じて Jenkins で動作します。プラグインのインストールは不要です。以下のパイプラインは、バージョンを固定した CLI をインストールし、ワークスペースをスキャンし、ポリシーに違反する検出結果があればビルドを失敗させ、結果をアーカイブします。
API トークン から VulnCheck API トークンを作成し、Secret text の資格情報として Jenkins に追加します。
vulncheck-api-token を設定する。パイプラインはこの ID を参照しますパイプラインは withCredentials でこの資格情報を VC_TOKEN にバインドします。これによりトークンはビルドログ上でマスクされ、無関係なステップの環境に残ることもありません。
pipeline {
agent {
docker {
image 'debian:bookworm-slim'
args '-u root'
}
}
environment {
VC_CLI_VERSION = '1.1.0'
VC_CVSS_THRESHOLD = '7.0'
}
stages {
stage('Install the VulnCheck CLI') {
steps {
sh '''
apt-get update -qq && apt-get install -y -qq --no-install-recommends ca-certificates curl jq
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
install -m 0755 "/tmp/vulncheck_${VC_CLI_VERSION}_linux_${ARCH}/bin/vulncheck" /usr/local/bin/vulncheck
vulncheck version
'''
}
}
stage('Scan') {
steps {
withCredentials([string(credentialsId: 'vulncheck-api-token', variable: 'VC_TOKEN')]) {
sh '''
vulncheck scan . --json > scan.json
jq -r '"\\((.vulnerabilities // []) | length) vulnerabilities found"' scan.json
'''
}
}
}
stage('Gate on findings') {
steps {
sh '''
jq -e --argjson max "$VC_CVSS_THRESHOLD" '
[ (.vulnerabilities // [])[]
| select((.cvss_base_score // 0 | tonumber? // 0) >= $max or .in_kev) ] | length == 0
' scan.json > /dev/null || {
echo "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.json
exit 1
}
'''
}
}
}
post {
always {
archiveArtifacts artifacts: 'scan.json', allowEmptyArchive: true
}
}
}
jq のプログラム内でバックスラッシュを二重にしている点に注意してください。\( ではなく \\( と書きます。Jenkins はシングルクォートの Groovy 文字列でもエスケープシーケンスを解釈するため、エスケープしていない \( はシェルに渡る前に不正なエスケープ文字として拒否されます。その他に押さえておきたい点があります。
vulncheck scan は重大な脆弱性を検出した場合でも終了コード 0 を返すため、スキャンのステージは常に成功し、ポリシー判定はビルド内の独立したステージとして見えるようになります。CI/CD での VulnCheck を参照してください。args '-u root' により、インストールのステップが /usr/local/bin に書き込めるようになります。指定しない場合は、書き込み可能な場所にインストールして PATH に追加してください。post { always { … } } 内の archiveArtifacts により、失敗したビルドでも scan.json が保持されます。証跡が最も必要になるのは、その実行です。VC_CLI_VERSION を固定してください。 これにより、どのビルドも既知のバージョンの CLI でスキャンされ、install.sh が最新リリースの判定に行う未認証の GitHub API 呼び出しも避けられます。ビルドを止めずに検出結果を可視化したい場合は、失敗ではなく unstable として扱います。
stage('Gate on findings') {
steps {
script {
def breaches = sh(returnStatus: true, script: '''
jq -e --argjson max "$VC_CVSS_THRESHOLD" '
[ (.vulnerabilities // [])[]
| select((.cvss_base_score // 0 | tonumber? // 0) >= $max or .in_kev) ] | length == 0
' scan.json > /dev/null
''')
if (breaches != 0) {
unstable('VulnCheck policy breach — see scan.json')
}
}
}
}
よく使われる構成は、変更リクエストでは失敗させ、ブランチのビルドでは unstable に留める方法です。これにより、新たに公開された CVE が無関係な作業を止めることはありません。
if (breaches != 0) {
if (env.CHANGE_ID) {
error('VulnCheck policy breach on this change request')
} else {
unstable('VulnCheck policy breach on the branch build')
}
}
通常のエージェントでは、構成管理または一度きりの管理作業としてホストに CLI をインストールし、インストールのステージを完全に削除します。
pipeline {
agent { label 'linux' }
environment {
VC_CVSS_THRESHOLD = '7.0'
}
stages {
stage('Scan') {
steps {
withCredentials([string(credentialsId: 'vulncheck-api-token', variable: 'VC_TOKEN')]) {
sh 'vulncheck version'
sh 'vulncheck scan . --json > scan.json'
}
}
}
}
}
どのバージョンの CLI が結果を生成したかがログに残るよう、ビルド内に vulncheck version を残しておいてください。
| 症状 | 原因 |
|---|---|
パイプラインの読み込み時に illegal escape character が出る | Groovy 文字列の中で jq の文字列補間を \( と書いている。\\( のようにエスケープしてください。 |
インストールのステージが成功したのに vulncheck: command not found になる | root で実行しているコンテナで install.sh --sudo を使用している。sudo が存在しないにもかかわらず、スクリプトは成功として報告します。上記の tarball インストールを使用してください。 |
| ビルドが終了コード 3 で失敗する | 資格情報が存在しないか、バインドが適用されていない。vulncheck を実行する sh ステップが withCredentials ブロックの内側にあるか確認してください。 |
| ビルドが終了コード 5 で失敗する | レート制限。多数の Executor が 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 を使用してください。 |